On PHP 5.3, Lambda Functions, and Closures
Fabien Potencier
April 16, 2009
PHP 5.3 will have a lot of exciting new features. One of the most important one for me is the introduction of lambda functions and closures support. I won't talk too much about what lambda functions or closures are, as you can find many good blog posts describing them in great details. To sum up, a lambda function is an anonymous PHP function that can be stored in a variable and passed as an argument to other functions or methods. A closure is a lambda function that is aware of its surrounding context.
The first obvious use for lambda functions and closures is in conjunction with
the array_map(), array_reduce(), and array_filter() native PHP
functions:
$input = array(1, 2, 3, 4, 5); $output = array_filter($input, function ($v) { return $v > 2; });
The above example filters the input array by removing all values greater than 2:
$output == array(2 => 3, 3 => 4, 4 => 5)
function ($v) { return $v > 2; } is the lambda function definition and it
can be stored in a PHP variable to be reusable:
$max_comparator = function ($v) { return $v > 2; }; $input = array(1, 2, 3, 4, 5); $output = array_filter($input, $max_comparator);
But what if I want to change the maximum number allowed in the filtered array? I can either create another lambda function or use a closure:
$max_comparator = function ($max) { return function ($v) use ($max) { return $v > $max; }; }; $input = array(1, 2, 3, 4, 5); $output = array_filter($input, $max_comparator(2));
Now, the $max_comparator function takes the maximum allowed number and
returns a function that is different according to this maximum. Even for such
a contrived example, I hope you see the great power it gives you!
Closures also opens up a lot of great possibilities, like the implementation of the cryptic Y-combinator, as demonstrated by Stanislav Malyshev in one of his recent blog post:
function Y($F) { $func = function ($f) { return $f($f); }; return $func(function ($f) use($F) { return $F(function ($x) use($f) { $ff = $f($f); return $ff($x); }); }); }
Today, I want to talk about yet great another example on how to use lambda functions and closures. You will see that it can simplify your code a lot when used appropriately.
If you have read my blog recently, you know that I am quite obsessed with dependency injection these days. This post will show you how to implement a very simple but still full-featured dependency injection container, thanks to the new features of PHP 5.3.
A dependency injection container must be able to manage two different kind of data: objects and parameters. And objects must be created on-demand the first time they are accessed from the container.
Using a simple class that implements the magic __get() and __set()
methods, we can easily manage both the objects and the parameters:
class DIContainer { protected $values = array(); function __set($id, $value) { $this->values[$id] = $value; } function __get($id) { return is_callable($this->values[$id]) ? $this->values[$id]($this) : $this->values[$id]; } }
Using the container is quite simple:
$container = new DIContainer(); // define the parameters $container->cookie_name = 'SESSION_ID'; $container->storage_class = 'SessionStorage'; // defined the objects $container->storage = function ($c) { return new $c->storage_class($c->cookie_name); }; $container->user = function ($c) { return new User($c->storage); }; // get the user object $user = $container->user; // the above call is roughly equivalent to the following code: // $storage = new SessionStorage('SESSION_ID'); // $user = new User($storage);
The examples I use in this article are the same as the one I have used in my series on dependency injection.
The definition of an object is done by defining a lambda function that returns an instance of the object.
The __get() method checks if the value associated with a key is a PHP
callable (and lambda functions are callables) to make the difference between
an object definition and a parameter.
Also notice how we call the lambda:
$this->values[$id]($this)
The trick here is to pass the container as an argument of the lambda function so that it can use the container to access parameters and other objects managed by the container.
We can make the container a bit better by adding error support:
class DIContainer { protected $values = array(); function __set($id, $value) { $this->values[$id] = $value; } function __get($id) { if (!isset($this->values[$id])) { throw new InvalidArgumentException(sprintf('Value "%s" is not defined.', $id)); } return is_callable($this->values[$id]) ? $this->values[$id]($this) : $this->values[$id]; } }
Defining objects with lambda functions is great because the developer can do whatever he wants to actually create and configure instances. But we still need to implement one important feature of any dependency injection container: shared instances. This can be done manually like this:
$container->user = function ($c) { static $object; if (is_null($object)) { $object = new User($c->storage); } return $object; };
By declaring a static variable in the lambda function, the first time it is called, the object is created and returned. For all subsequent calls, the same instance will always be returned.
It works as expected, but this is quite repetitive, tedious, and error prone. For all instances that must be unique, we need to add this boilerplate code. Instead, I want to support shared instances as a native feature of the container itself. Thanks to closures, that's quite easy to accomplish:
class DIContainer { // ... function asShared($callable) { return function ($c) use ($callable) { static $object; if (is_null($object)) { $object = $callable($c); } return $object; }; } }
The asShared() method wraps the given lambda function to add the needed
logic we have seen before. Declaring an object as unique for a given container
is now dead simple:
$c->user = $c->asShared(function ($c) { return new User($c->storage); });
In less than 30 lines of PHP, we have coded a full-featured dependency injection container. Quite impressive!
If we remove the need for shared instance, and if we define parameters and objects as lambdas, we can even compact the code so that it fits in a tweet:
class Container { protected $s=array(); function __set($k, $c) { $this->s[$k]=$c; } function __get($k) { return $this->s[$k]($this); } }
I have called this tweet container twittee, and it has its own dedicated website and github repository.
PHP 5.3 is really a great step forward for the PHP language.




Discussion
Merci pour cet article - mais il y a une typo au 3ème mot (exiting new features -- il manque le c de exciting)
yes it's cool features simply like in JavaScript, but i'm afraid this lambda function implementation or incautious use of them can affect performance.
1 - with a "standard" function
2 - with a lambda function
3 - by using create_function()
For 50, 000 iterations on my laptop, here are the time spent for each implementation:
1 - 240 ms
2 - 200 ms
3 -1200 ms
So, lambda functions are as fast as "standard" PHP functions.
Its great to see the speed comparison and know that those functions can be used without having a performance impact.
Though I would wager the real impact of overuse of lambda functions and closures as shown in the Y-combinator example is maintainability. Which imho is the most cost intensive portion of a program's life cycle.
Also I'm wondering if you've looked into using reflection to read constructor and method arguments for an injection point, as opposed to making the user explicitly call functions like addArgument.
thanks for your extremely clear and detailed explanation
As for explicit versus implicit, that's fair enough. I should mention that one of the reasons Guice has been so popular is because it saved users from having to explicitly write up a bunch of XML config, as one used to have to do with Spring (they've now too embraced annotations).
Perhaps the best of both worlds would be to allow the user two options for defining injection points, explicit or implicit. As you said, I'm not sure how well these would work out in a PHP context, but as a Guice user, defining injection points explicitly does look rather verbose.
Regards-
It looks like I might be starting up a large symfony based project soon, so perhaps I will get to contribute something here.
I agree that when introspecting a class to obtain arguments for an injection point, we'd definitely want to cache this information across requests. Introspecting a class on each request is out of the question, so I think this is a legitimate use of caching.
As for how to go about marking injection points, annotations is just an idea, but I'm not sure I'd even go there until they (or something similar) become part of the core language. I'll certainly be keeping an eye on your work though.
http://www.beberlei.de/sphicy/
I've only glanced at it, but the one difference I notice is that it allows arguments to be explicitly passed to a constructor, which is nice (and which Guice doesn't directly allow).