Closures and Callbacks - Zend PHP 5 Certification Study Guide (2014)

Zend PHP 5 Certification Study Guide (2014)

Closures and Callbacks

With each new version of PHP, more and more advanced features are added. One feature that can have a major impact on how you design code is Closures and callbacks.

Closures

PHP 5.3 added anonymous functions, otherwise known as Closures. Closures allow you to define anonymous functions that can be passed around as callbacks. Closures can be used as callbacks for any function that accepted traditional callbacks prior to 5.3, such as usort().

Closures are created simply by defining a function with no name, and assigning it to a variable:

$closure = function($who) {

echo "Hello $who";

}

You then call a closure in the same way you would a regular function, using the variable instead of its name:

$closure("World"); // Hello World

Under the hood, the $closure variable is actually an instance of the Closure class. Until PHP 5.4, this was considered an implementation detail and was viewed as unreliable. With the release of PHP 5.4, this has changed, and the Closure class is used to bring additional functionality to closures.

Scope

A closure encapsulates its scope, meaning that it has no access to the scope in which it is defined or executed. It is, however, possible to inherit variables from the parent scope (where the closure is defined) into the closure with the use keyword:

Listing 10.1: Creating a closure

function createGreeter($who) {

return function() use ($who) {

echo "Hello $who";

};

}

$greeter = createGreeter("World");

$greeter(); // Hello World

This inherits the variables by-value, that is, a copy is made available inside the closure using its original name.

It is also possible to inherit the variables by-reference, using the reference operator, &:

Listing 10.2: Creating a closure with a reference

// Make sure to use reference here also

function createGreeter(&$who) {

return function() use (&$who) {

echo "Hello $who";

$who = null;

};

}

$who = "world";

$greeter = createGreeter($who); // Passed in by-reference

$who = ucfirst($who); // changes to World,

// including the closure reference

$greeter(); // Hello World, changes $who to null

var_dump($who); // null

Using $this

When closures were first introduced in PHP 5.3, they did not allow access to $this, even when it was created inside of an object scope. However, with PHP 5.4 this has been changed.

Listing 10.3: Using $this in closures

class foo

{

public function getClosure() {

return function() { return $this; };

}

}

class bar

{

public function __construct() {

$foo = new foo();

$func = $foo->getClosure();

$obj = $func(); // PHP 5.3: $obj == null

// PHP 5.4: $obj == foo, not bar

}

}

In PHP 5.4 and later, when a closure is defined within an object scope, this is determined by the object within whose scope it is defined (or in which it is inherited for child classes). This can be changed after the fact by using the bindTo and (static) bind methods of the closure class.

Using either of these methods will not modify the closure itself; instead it will create a clone of the closure with $this modified.

It is possible to change the $this independently from the scope, meaning that unlike a regular method you may not have access to all of the methods on $this unless the scope is also the same class of which $this is an instance.

Listing 10.4: Changing $this dynamically

class Greeter

{

public function getClosure() {

return function() {

echo $this->hello;

$this->world();

};

}

}

class WorldGreeter

{

public $hello = "Hello ";

private function world() { echo "World"; }

}

Here we have a class Greeter, which returns a closure that outputs a property, $this->hello, and then calls a method $this->world(), neither of which exist in the Greeter class.

We then create another—completely separate—class, WorldGreeter, which has both the property and method we wish to call.

To allow this to work, we can rebind $this to an instance of WorldGreeter by calling the Closure->bindTo() method on our closure.

Listing 10.5: Using bindTo()

$greeter = new Greeter();

$closure = $greeter->getClosure();

$worldGreeter = new WorldGreeter();

// Rebind $this to $worldGreeter

$newClosure = $closure->bindTo($worldGreeter);

$newClosure();

When we call this, we get a fatal error on the call to $this->world():

Hello

Fatal error: Call to private method WorldGreeter::world()

from context 'Greeter'

This is because the world method is private and we only changed the object to which $this is pointing, not the scope—it remains as it was before: Greeter.

To fix this, we also need to pass in the class from which our scope should be taken. We can pass either the string 'WorldGreeter' or an instance of the WorldGreeter class as the second argument to bindTo():

Listing 10.6: Specifying a class with bindTo()

$greeter = new Greeter();

$closure = $greeter->getClosure();

$worldGreeter = new WorldGreeter();

// Rebind $this and scope to $worldGreeter

$newClosure = $closure->bindTo(

$worldGreeter, 'WorldGreeter'

);

$newClosure(); // Hello World

We can also rebind using the static bind() method of the Closure class:

Listing 10.7: Using static bind()

$greeter = new Greeter();

$closure = $greeter->getClosure();

$worldGreeter = new WorldGreeter();

// Rebind $this and scope to $worldGreeter

$newClosure = Closure::bind(

$closure, $worldGreeter, 'WorldGreeter'

);

$newClosure(); // Hello World

To unbind a closure, pass in null for the new $this.

Callbacks

Internal Functions

Prior to PHP 5.3, PHP supported limited types of callbacks. The most simple is a string containing a valid function name:

$callback = "myFunction";

usort($array, $callback);

You can also use arrays to denote object or static class method calls:

Listing 10.8: Using arrays to specify callbacks

// object method call:

$callback = [$obj, 'method']; // $obj->method() callback

usort($array, $callback);

// or static method:

$callback = ['SomeClass', 'method']; // SomeClass::method()

usort($array, $callback);

With the introduction of Closures in 5.3, you can now use a closure itself, as well as using an object as a callback if it defines the __invoke() magic method.

Listing 10.9: Using a class as a callback

class Sorter()

{

public function __invoke($a, $b) {

// Sort

}

}

$sorter = new Sorter();

usort($sorter);

Userland Functions

Prior to PHP 5.4, you could only use the simple string callbacks in userland—this was known as a dynamic function call—however, in PHP 5.4 all valid callbacks can be used.

Listing 10.10: Userland callbacks

// Variable Functions

$callback = "myFunction";

$callback();

// object method call:

$callback = [$obj, 'method']; // $obj->method() callback

$callback();

// or static method:

$callback = ['SomeClass', 'method']; // SomeClass::method()

$callback();

// Closures

$callback = function() { }

$callback();

// Objects with Invoke magic method:

class invokeCallback

{

public function __invoke() { }

}

$callback = new invokeCallback();

$callback()

Summary

Closures and callbacks are both powerful tools that can entirely change how you architect an application.

From using them with internal functions to creating and using them yourself, closures and callbacks can be used in many different situations.

Together they form the basis of most modern implementations of dependency injection and service locators.

Do not underestimate the power of these two simple features.