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

gravatar Matthieu  — April 16, 2009 15:12   #1
Hello Fabien,

Merci pour cet article - mais il y a une typo au 3ème mot (exiting new features -- il manque le c de exciting)
gravatar Anton  — April 16, 2009 15:13   #2
Hi Fabien,
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.
gravatar Fabien  — April 16, 2009 15:32   #3
@Matthieu: Thanks, it is fixed now
gravatar Fabien  — April 16, 2009 15:41   #4
@Anton: Lambda functions are indeed very fast. I have just made a small test with three different implementations of the max filter example:

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.
gravatar RFS  — April 16, 2009 17:44   #5
@Fabien #4 -

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.

gravatar Fabien  — April 16, 2009 19:03   #6
Fabien - Regarding your earlier DI examples with the sfServiceContainerInterface and sfServiceContainerBuilder, I wanted to see if you had explored ways to make defining injection points easier (such as can be done with a simple @Inject annotation using Guice).

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.
gravatar devsmt  — April 21, 2009 14:35   #7
I share with you the exitation for such revulotionary features of the language.
thanks for your extremely clear and detailed explanation
gravatar Fabien  — April 22, 2009 08:51   #8
@Fabien: I have of course explored these possibilities but I think they do not make sense in a PHP context. Even in a Java container like Spring, they provide a way to explicitly define the arguments, and some/a lot of people prefer this way of doing things. I am quite a fan of the "explicit is better than implicit" motto.
gravatar Jonathan  — April 22, 2009 18:14   #9
@#8 - Sorry, I'm not sure why my post #6 showed up as Fabien.

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-
gravatar Fabien  — April 22, 2009 18:24   #10
@Jonathan: The big advantage of being explicit is that you can manage any class, even the ones that are not annotated. Moreover, reading annotations means that you need to introspect the class, which is not that a great idea performance wise in PHP. The only solution is to cache the annotations, which becomes more complex. But that's possible of course. As the design of the container is quite flexible, I think implementing such a feature on top of the existing code is quite simple ;)
gravatar Jonathan  — April 22, 2009 23:07   #11
@Fabien

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.
gravatar Jonathan  — April 28, 2009 19:01   #12
You've probably already seen this, but I just came across DI framework implementation that draws inspuration directly from Guice:

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).
gravatar brendan baldwin  — June 05, 2009 06:19   #13
i find it amusing how many php developers are not yet hip to the MAJOR win that having lambdas in the language is. coming from ruby, myself, the lack of first class functions in php always felt like a major hinderance. building iterators without them is always impossibly convoluted. this is SO exciting :-)