Introduction to PHP 5.3 closures is one of its main innovations, and although several years have passed since the release, there is still no standard practice for using this language feature. In this article, I tried to collect all the most interesting possibilities for applying closures in PHP.
To begin, consider what it is - a closure and what are its features in PHP.
$g = 'test'; $c = function($a, $b) use($g){ echo $a . $b . $g; }; $g = 'test2'; var_dump($c);
As you can see, the closure, like the lambda function, is an object of the Closure class, which coordinates the passed parameters. In order to call an object as a function, the magic method __invoke was introduced in PHP5.3.
function getClosure() { $g = 'test'; $c = function($a, $b) use($g){ echo $a . $b . $g; }; $g = 'test2'; return $c; } $closure = getClosure(); $closure(1, 3);
Using the use construct, we inherit the variable from the parent scope to the local scope of the Lambda function.
The syntax is simple and straightforward. The use of such functionality in the development of web applications is not entirely clear. I looked at the code of several modern frameworks that use the new features of the language and tried to bring together their various uses.
')
Callback functions
The most obvious use of anonymous functions is to use them as callbacks. In PHP, there are many standard functions that accept a callback type or its synonym callable as entered in PHP 5.4. The most popular ones are array_filter, array_map, array_reduce. The array_map function is used for iterative processing of array elements. The callback function is applied to each element of the array and the result is the processed array. I immediately had a desire to compare the performance of the usual array processing in a loop using the built-in function. Let's experiment.
$x = range(1, 100000); $t = microtime(1); $x2 = array_map(function($v){ return $v + 1; }, $x);
As you can see, the overhead of a large number of function calls gives a noticeable drop in performance, which is to be expected. Although the test is synthetic, the task of processing large arrays often arises, and in this case, the use of data processing functions can become the place where your application will significantly slow down. Be careful. However, in modern applications, this approach is used very often. It allows you to make the code more concise, especially if the handler is declared somewhere else, and not when called.
In fact, in this context, the use of anonymous functions is no different from the old way of passing a string name of a function or a callback array, except for one particular feature - now we can use closures, that is, save variables from the scope when creating a function. Consider an example of processing an array of data before adding them to the database.
It is very convenient to use anonymous functions and to filter
$x = array_filter($data, function($v){ return $v > 0; });
Developments.
Closures are ideal as event handlers. for example
Putting logic into event handlers on the one hand makes the code cleaner, on the other hand complicates the search for errors — sometimes the behavior of the system becomes unexpected for a person who does not know which handlers are hung at the moment.
Validation
Closures essentially save some logic in a variable that can be executed or not executed in the course of the script. This is what you need to implement validators:
$notEmpty = function($v){ return strlen($v) > 0 ? true : “ ”; }; $greaterZero = function($v){ return $v > 0 ? true : “ ”; }; function getRangeValidator($min, $max){ return function($v) use ($min, $max){ return ($v >= $min && $v <= $max) ? true : “ ”; }; }
In the latter case, we apply a higher order function that returns another function — a validator with predefined boundaries of values. You can use validators, for example, as follows.
class UserForm extends BaseForm{ public function __constructor() { $this->addValidator('email', Validators::$notEmpty); $this->addValidator('age', Validators::getRangeValidator(18, 55)); $this->addValidator('custom', function($v){
Using forms is a classic example. Also, validation can be used in setters and getters of ordinary classes, models, etc. It is true that declarative validation is considered to be a good way, when the rules are described not in the form of functions, but in the form of rules in configuration, however, sometimes this approach is very useful.
Expressions
Symfony has a very interesting use for closures. The
ExprBuilder class
defines an entity that allows you to build expressions like
... ->beforeNormalization() ->ifTrue(function($v) { return is_array($v) && is_int(key($v)); }) ->then(function($v) { return array_map(function($v) { return array('name' => $v); }, $v); }) ->end() ...
In symfony, as I understand it, this is an internal class that is used to create processing of nested configuration arrays (correct me if not right). An interesting idea is the implementation of expressions in the form of chains. In principle, it is possible to implement a class that would describe expressions in the following form:
$expr = new Expression(); $expr ->if(function(){ return $this->v == 4;}) ->then(function(){$this->v = 42;}) ->else(function(){}) ->elseif(function(){}) ->end() ->while(function(){$this->v >=42}) ->do(function(){ $this->v --; }) ->end() ->apply(function(){}); $expr->v = 4; $expr->exec(); echo $expr->v;
Application, of course, experimentally. In essence, this is a record of some algorithm. The implementation of such a functional is quite complicated - the expression in the ideal case should store a tree of operations. An interesting concept, maybe somewhere such a construction would be useful.
Routing
In many mini frameworks, routing now works on anonymous functions.
App::router('GET /users', function() use($app){ $app->response->write('Hello, World!'); });
Enough convenient and concise.
Caching
On Habré, this has already been discussed, nevertheless.
$someHtml = $this->cashe->get('users.list', function() use($app){ $users = $app->db->table('users)->all(); return $app->template->render('users.list', $isers); }, 1000);
Here, the get method checks the validity of the cache using the 'users.list' key and if it is not valid, then it accesses the function for data. The third parameter determines the duration of data storage.
Initialization On Demand
Suppose we have the Mailer service, which we call in some methods. It must be configured before use. In order not to initialize it every time, we will use lazy object creation.
Object initialization will occur only before the very first use.
Change the behavior of objects
Sometimes it is useful to override the behavior of objects during script execution — add a method, override an old one, and so on. The closure will help us here too. In PHP5.3, you had to use different workarounds for this.
class Base{ public function publicMethod(){echo 'public';} private function privateMethod(){echo 'private';}
In principle, it is possible to override the old method, but only if it was defined in a similar way. Not quite comfortable. Therefore, in PHP 5.4, it became possible to associate a closure with an object.
$closure = function(){ return $this->privateMethod(); } $closure->bindTo($b, $b);
Of course, the modification of the object failed, however, the closure gains access to private functions and properties.
Passing as default parameters to data access methods
An example of getting a value from a GET array. In case of its absence, the value will be obtained by calling the function.
$name = Input::get('name', function() {return 'Fred';});
Higher order functions
There was already an example of creating a validator. I will give an example from the
lithium framework
public function write($type, $message) { $config = $this->_config + $this->_classes; return function($self, $params) use ($config) { $params += array('timestamp' => strtotime('now')); $key = $config['key']; $key = is_callable($key) ? $key($params) : String::insert($key, $params); $cache = $config['cache']; return $cache::write($config['config'], $key, $params['message'], $config['expiry']); }; }
The method returns a closure that can then be used to write the message to the cache.
Transfer to templates
Sometimes it is convenient to transfer not just data to a template, but, for example, a configured function that can be called from the template code to get any values.
In this case, the template generated several links to the user's entities, and his login appeared in the addresses of these links.
Recursive closure definition
Finally, how to set recursive closures. To do this, pass a reference to the closure in use, and call it in code. Do not forget about the condition of the termination of recursion
$factorial = function( $n ) use ( &$factorial ) { if( $n == 1 ) return 1; return $factorial( $n - 1 ) * $n; }; print $factorial( 5 );
Many of the examples look stiff. How many years lived without them - and nothing. However, sometimes using closures is natural enough for PHP. Skillful use of this feature will make the code more readable and increase the efficiency of the programmer. You just need to adjust your thinking a little under the new paradigm and everything will fall into place. In general, I recommend to compare how such things are used in other languages like Python. I hope that someone has found something new here. And of course, if someone else knows any interesting applications of closures, then I’m really looking forward to your comments. Thank!