Notes about closures in PHP
Posted by John Kleijn • Saturday, March 27. 2010 • Category: PHPWhen PHP 5.3 came out, I was ecstatic. Namespaces, finally! Actually some parts of the implementation were a bit disappointing, but we'll leave that for another time. In that same enthousiasm, I jumped on closures like a hungry dog on a steak. Only to find the steak to be an old shoe.
It's easy to get exited about lambdas/closures as some opportunity to flexibly handle pieces of behaviour. My first instinct was to see if we could do something like mixins in Ruby. And you can, at least to a degree:
class SomeModule
{
public static function append($object)
{
$object->foo = self::foo();
$object->bar = self::bar();
}
public static function foo()
{
return function(){
return 'fooshizzle';
};
}
public static function bar()
{
return function(){
return 'barrrtastic';
};
}
}
class SomeClass
{
public function __construct()
{
SomeModule::append($this);
}
public function __call($method, $args)
{
if(!property_exists($this, $method))
{
throw new \BadFunctionCallException("Call to undefined method SomeClass::$method()");
}
$closure = $this->$method;
return call_user_func_array($closure, $args);
}
}
$object = new SomeClass();
var_dump($object->foo()); //string(10) "fooshizzle"
Aesthetics aside, it does work and effectively we're using "duck typing" instead of static typing now. Typical enforcement uses "Easier to Ask Forgiveness than Permission" which could look like this in Python:
try:
object.meh()
except (AttributeError, TypeError):
print "meh() not supported"
..could be used in PHP like this:
Every closure has it's own scope, which is why the methods in the "module" are static: there's no point in instantiating it. The closures do not operate on the state of any object, thus can be considered static as well. At the thought of the word "static", the little green gnome on my right shoulder starts screaming panicky in an Irish accent. No offence to the Irish, but you guys aren't the easiest to understand. Anyway, I can't really make out what he is trying to say yet, so I try to pass the object to the closure, so it will at least be able to operate on the subject.
But arriving at the point of adding some actual state to the test subject class, I suddenly get what the gnome is trying to say. For the closure to be able to use the state, I have to break encapsulation. I can make the property public or create an accessor, the result is the same: data is made accessible for the sole purpose of using the closure. So I am faced between the options of limiting myself to static operations, or breaking encapsulation. It works, but lets face it, it doesn't taste like steak.
Screw this, bye bye closures, composition, lets make up:
class NerdyCoolnessPrinter
{
public function foo()
{
return 'fooshizzle';
}
public function bar()
{
return 'barrrtastic';
}
}
class SomeOtherClass
{
private $_coolnessPrinter;
public function __construct()
{
$this->_coolnessPrinter = new NerdyCoolnessPrinter();
}
public function foo()
{
return $this->_coolnessPrinter->foo();
}
public function bar()
{
return $this->_coolnessPrinter->bar();
}
}
A gasp of relief.
Ok, so dynamically adding methods is not for me. So what can we use closures for? The answer is "very simple and short operations that won't likely be reused" operations. If the clients can not be expected to care about the type of an argument, and the operation is extremely simple, you might as well use a closure: the syntax is shorter than creating a class for it. As arguments for sort functions for example. But these situation are every rare. You may not believe me, so I'll give an example.
Take Eli White's example, labelled as "An intriguing use of lambda functions". I was pointed to this post by Herman Radtke, as being "an inspirational post". No offence to either of these fine gentlemen, but frankly there's little intriguing or inspirational about it. I'll spare you the full post, all you need to know is this bit:
if ($jsfunc) {
$url = function ($p) use ($jsfunc) { return "javascript:{$jsfunc}({$p})"; };
} elseif ($baseurl) {
$url = function ($p) use ($baseurl) { return "{$baseurl}/page:{$p}"; };
}
Now I could simply rewrite my original template, to use this lambda function $url to generate it’s URLs.
<div class="pagination">
<a href="<?= $url($page - 1) ?>">← Previous</a>
Page <?= $page ?> of <?= $total ?>
<a href="<?= $url($page - 1) ?>">Next →</a>
</div>
As I mention on Herman Radtke's blog, it is easy to initially be taken by the simplicity and recognize that this is massive simplification compared to using purely conditionals. But on second glance this is simply a View Helper. The same can be implemented like this:
class ElisUrlViewHelper
{
private $_baseUrl;
private $_jsFunc;
public function __construct($baseurl, $jsfunc)
{
$this->_baseUrl = $baseurl;
$this->_jsFunc = $jsfunc;
}
public function __invoke($p)
{
if($this->_jsFunc)
{
return "javascript:{$this->_jsFunc}({$p})";
}
elseif($this->_baseUrl)
{
return "{$this->_baseUrl}/page/$p";
}
}
}
$urlHelper = new ElisUrlViewHelper('http://johnkleijn.nl', false);
$page = 10;
Which allows reuse of the procedures (you may need these specific ones in a controller, for example), but most importantly provides the proper structure that closures lack. Could be that this helper gets more complicated over time, and then a closure simply won't do. There's also the issue of testability. If your closure uses other components, how do you mock those? You can't, because a closure is stateless.
In summary, closures, I know it seems cool and all, but please use them wisely.
The scriblings are found in a file here, btw.



ShareThis