I’m currently working with Lithium (aka li3 or Cake 3) mostly for more than a month and would like to share some structure they’ve been putting it and that I like.
I like them because they made that framework so much flexible in a way you can plug anything at almost any places or add — what they call — filters to certain key methods or functions to tweak their behaviour at your needs.
Let’s start with the mentioned filters. A filter will wrap the body of a (static) function or method and let you act before / after the initial work, very much like monkeypatching.
<?php namespace monkey; class Math extends \lithium\core\StaticObject { static function add($a, $b) { $params = compact('a', 'b'); return static::_filter(__FUNCTION__, $params, function($self, $params) { extract($params); return $a + $b; }); } } Math::add(2, 2); // 4
As you can see we are using PHP 5.3 features like namespace, closures and late static binding.
- The namespace enable you to avoid Zend Framework alike naming, here we are in the
monkey
namespace. - A closure is used to describe the body of the function, what it’s gonna do. Before that PHP had only eval-like function (via create_function) that were very ugly to create and use.
- And last but not least, the late static binding lives in
static::
(which is close toself::
) but will always be the class where it’s written and not any sub classes (that’s whatself::
is for)
Just in case you didn’t know, PHP 5.2 is not supported anymore. At least before its next release. Jamais deux sans trois. (never two times without a third time).
Now, let’s hack this.
<?php use lithium\util\collection\Filters; Filters::apply('\monkey\Math', 'add', function ($self, $params, $chain) { extract($params); $ret = $chain->next($self, $params, $chain); fwrite(STDERR, "$a + $b = $ret"); return $ret; }); use monkey\Math; Math::add(1, 2); // 3 // stderr: 1 + 2 = 3
For those who don’t know, stderr is the error output on UNIX. You can pipe / redirect it to another location using 2>
.
Inside lithium this kind of filter are mostly used in the Dispatcher. The Dispatcher is the class that handles all the requests, matching a route with an existing controller. So you can do exactly what you would do with WSGI/Rack/… middlewares.
Methods can aslo be written in a way you can filter them afterwards.
<?php namespace monkey; class Tweeter extends \lithium\core\Object { function tweet($message) { $params = compact('message'); return $this->_filter(__METHOD__, $params, function ($self, $params) { echo 'tweet: ', $params['message'], "\n"; }); } } $t = new Tweeter(); $t->tweet(’Hello’); // Hello $t->applyFilter('tweet', function ($self, $params, $chain) { $params['message'] .= ' ^YB'; return $chain->next($self, $params, $chain); }); $t->tweet('Hello') // Hello ^YB
Pardon my examples to be simplistic. A great usage of all this are tests. By performing surgerical modification inside your objects, it becomes very easy to tweak everything without bloating the initial object much. I agree that filterable methods or functions aren’t as readable as normal ones.
And because on major thing you do in Object Oriented Programming is to link objects, instances together here comes the last pattern, kind of, you may find into Lithium. I dunno exactly if it’s Inversion of Control or Dependencies Injection. I’ve stayed away from Java a too long time to know.
<?php namespace monkey; class MyObject extends \lithium\core\Object { protected $_classes = array( 'math' => '\monkey\Math' ); protected $_autoConfig = array('classes'); public function plus1($n) { $math = $this->_classes['math']; return $math::add($n, 1); } } $obj = new MyOject(array('classes' => array( 'math' => '\ape\Math' ))); $obj->plus1(2); // 3
I always feel silly having such useless example but I hope that you get the point. MyObject isn’t linked to a particular Math class but can use a different one if needed with no code modifications.
By the way, Symfony2 uses something similar with a _class
suffix in the options.
From a JavaScript, Ruby or even Python point of view, this is the poor’s man monkeypatching and it involves a really heavy syntax for few gains. As I’m still complaining for every array
I have to type, this is no big deal.
Don’t forget to join #li3 on freenode.