PHP, Anonymous Functions, Closures, and QueryPath

When I first started using jQuery I was pulled into the pattern of using anonymous functions and closures. After learning how to properly use these language features (as opposed to overusing them which can lead to untestable code) I wanted to use them in PHP. Fast forward several years and they are available in all supported PHP versions.

QueryPath, put simply, is jQuery written in PHP for server side manipulations. Now that I can easily use anonymous functions and closures I can write and read functionality that looks more like the jQuery I've been writing for years.

Anonymous Functions and QueryPath

Before PHP 5.3 was around a form of anonymous (a.k.a. lambda) functions were around through the use of create_function(). Unfortunately, this method had a number of drawbacks making it something I avoided using.

In PHP 5.3 anonymous functions were introduced in a familiar manner. I find the easiest way to look at these is by example.

$foo = function($bar) {
  print $bar . "\n";
}

$foo('Hello World');

Now, let's apply this style function to QueryPath.

$doc = qp($file);

$doc->find('.foo')
  ->each(function($index, $item) {

    // $item is a DOMElement
    $bar = qp($item);

    // Do something to each item in here.
  });

The each method can accept a number of things. This can include a callback function, an anonymous function, or anything that's callable.

In this case using an anonymous function turns out to be fast. In my case and tests it ran faster than a normal callback function.

Closures and QueryPath

According to Wikipedia:

A closure is a function or reference to a function together with a referencing environment

The existing environment is a really important part here. Let's look at an example.

function get_iterator($foo) {

  $bar = $foo * 2;

  return function ($index, $item) use ($bar) {
    // Do something here.

    // $bar is available from the parent environment.
  };
}

$a = get_iterator(2); // $bar is available when $a() is called as 4
$b = get_iterator(4); // $bar is available when $b() is called as 8

$doc = qp($file);
$doc->find('.foo')
  ->each($a);  // $bar inside $a() is available as 4.

The environment from get_iterator() is available to the function it returns even outside the original context. It's important to note that this is the environment at the time get_iterator() was called to create the function.

There are many ways closures can be used. For example, they can help setup an environment for anonymous functions to be used and passed around.

QueryPath and Drupal

The context for my QueryPath code has been within Drupal and there is a module for that.