Having been in Web development for a while, we all know or understand that there is a very powerful syntax in JavaScript, and that is closures. In fact, closure functions have long been available in PHP. Closure functions have been around since version 5.3 of PHP. By 7 and later in modern frameworks, the use of closures has become even more ubiquitous. Here, let’s start with the basics of using closures in PHP.


Closures in PHP are all converted to instances of the Closure class. If you assign a value to a variable, you need to add curly braces at the end of the definition; A semicolon. Closures inherit variables from their parent scope, and any such variables should be passed in using the use construct. Since PHP 7.1, you cannot pass variables such as superglobals, $this, or the same name as the argument


Basic grammar


Closures are very simple to use and very similar to JavaScript. Because they all have another alias, called anonymous functions.


 1$a = function() {2echo "this is testA";
 3};
 4$a(a); // this istestA
 5
 6
 7function testA ($a) {
 8    var_dump($a); 
 9}
10testA($a); // class Closure# 1 (0) {}11 to 12$b = function ($name13) {echo 'this is ' . $name; 14}; 15 to 16$b('Bob'); // this is Bob
Copy the code


We directly assign the variables $a and $b to two functions. This allows us to call both functions using a variable (). With the testA() method, we can see that the Closure function can be passed as a normal argument because it is automatically converted to an instance of the Closure class.


 1$age = 16;
 2$c = function ($name3) {echo 'this is ' . $name . ', Age is ' . $age; 4}; 5 and 6$c('Charles'); // this is Charles, Age is
 7
 8$c = function ($name) use ($age9) {echo 'this is ' . $name . ', Age is ' . $age; 10}; 11 to 12$c('Charles'); // this is Charles, Age is 16
Copy the code


If we need to call an external variable, we need to use the use keyword to reference the external variable. This is different from normal functions because closures have strict scoping problems. For global variables, we can use either use or global. However, only use can be used for local variables (variables within a function). We’ll come back to that later.


scope

 1function testD(){
 2    global $testOutVar;
 3    echo $testOutVar;
 4}
 5$d = function () use ($testOutVar6) {echo $testOutVar;
 7};
 8$dd = function () {
 9    global $testOutVar;
10    echo $testOutVar;
11};
12$testOutVar = 'this is d';
13$d(a); // NULL 14testD(); // this is d
15$dd(a); // this is d 16 17$testOutVar = 'this is e';
18$e = function () use ($testOutVar19) {echo $testOutVar;
20};
21$e(a); // this is e 22 23$testOutVar = 'this is ee';
24$e(a); // this is e 25 26$testOutVar = 'this is f';
27$f = function () use (&$testOutVar28) {echo $testOutVar;
29};
30$f(a); // this is f 31 32$testOutVar = 'this is ff';
33$f(a); // this is ffCopy the code


In scope, the variables passed by use must be defined before the function is defined, as shown in the example above. If the closure ($d) was defined before the variable ($testOutVar), then the variable passed in by use in $d is empty. Similarly, we use global to test that $testOutVar is normal for both the normal function (testD()) and the closure function ($dd).


Variables in the $e function that are modified after the function definition do not affect variables in the $e closure. In this case, you must use pass-by-reference ($f) to change the variables in the closure. This is the same concept as passing by reference and passing by value for ordinary functions.


Except for the use of variables, closures are almost identical to normal functions, such as class instantiation:


1class G
2{}
3$g = function () {
4    global $age;
5    echo $age; / / 16 June$gClass = new G();
7    var_dump($gClass); // G info
8};
9$g(a);Copy the code

Class scope


With regard to global scope, there is little difference between a closure function and a normal function. The main difference is the state of use when passing variables as a bridge. Is there anything different in a class method?


 1$age = 18;
 2class A
 3{
 4    private $name = 'A Class';
 5    public function testA()
 6    {
 7        $insName = 'test A function';
 8        $instrinsic = function () {
 9            var_dump($this); // this info
10            echo $this->name; // A Class
11            echo $age; // NULL
12            echo $insName; // null
13        };
14        $instrinsic(a); 15 to 16$instrinsic1 = function () {
17            global $age.$insName;
18            echo $age; / / 18 19echo $insName; // NULL
20        };
21        $instrinsic1(a); 22 23 global$age;
24        $instrinsic2 = function () use ($age.$insName25) {echo $age; / / 18 26echo $insName; // test A function27}; 28$instrinsic2();
29
30    }
31}
32
33$aClass = new A();
34$aClass->testA();
Copy the code


  • The $insName variable in the testA() method is available only through use.

  • The $this in the closure function is the context in which it is called, which in this case is class A itself. The parent scope of a closure is the function that defines the closure (not necessarily the function that calls it). Static closures cannot get $this.

  • Global variables can still be obtained using global.


tip

With these features of closures in mind, let’s look at a few tips:


 1$arr1= [2 ['name'= >'Asia'3 [],'name'= >'Europe'4 [],'name'= >'America'], 5]; 6, 7$arr1Params = ' is good! ';
 8// foreach($arr1 as $k= >$a9) {/ /$arr1[$k] = $a . $arr1Params;
10// }
11// print_r($arr1);
12
13array_walk($arr1.function (&$v) use ($arr1Params14) {$v. =' is good! ';
15});
16print_r($arr1);
Copy the code


Kill foreach: Many array-like functions, such as array_map, array_walk, and so on, need to be handled using closures. In the example above, we use array_walk to process the contents of an array. Is it very functional programming, and very clear.


1function testH()
2{
3    return function ($name4) {echo "this is " . $name; 5}; 6} 7testH()("testH's closure!"); // this is testH's closure!
Copy the code


Don’t get confused when you see this code. PHP7 supports the immediate execution syntax, which is the IIFE(Immediately- Invoked function expression) in JavaScript.


Here’s another one to calculate the Fibonacci sequence:


1$fib = function ($n) use (&$fib2) {if ($n= = 0 | |$n == 1) {
3        return1; 4} 5 6return $fib($n - 1) + $fib($n- 2); 7}; 8 and 9echo $fib(10);
Copy the code


Again, recursively. Instead, I’m using a recursive implementation of closures. Finally, it is important to note that the variable name passed in use cannot be an array item with an object:


1$fruits = ['apples'.'oranges'];
2$example = function () use ($fruits[0]) { // Parse error: syntax error, unexpected '[', expecting ', ' or ') '
3    echo $fruits[0]; 
4};
5$example(a);Copy the code


Writing like this is directly a syntax error and will not run successfully.


eggs


Closure capabilities are heavily used in the IoC service container in Laravel, and we simulate one for you to understand. A better solution, of course, is to look through Laravel’s source code yourself.


 1class B
 2{}
 3class C
 4{}
 5class D
 6{}
 7class Ioc
 8{
 9    public $objs = [];
10    public $containers = [];
11
12    public function __construct()
13    {
14        $this->objs['b'] = function() {15returnnew B(); 16}; 17$this->objs['c'] = function() {18returnnew C(); 19}; 20$this->objs['d'] = function() {21returnnew D(); 22}; 23 } 24 publicfunction bind($name) 25 {26if(! isset($this->containers[$name]) {27if (isset($this->objs[$name]) {28$this->containers[$name] = $this->objs[$name]();
29            } else{30returnnull; 31} 32} 33return $this->containers[$name]; 34} 35} 36 37$ioc = new Ioc();
38$bClass = $ioc->bind('b');
39$cClass = $ioc->bind('c');
40$dClass = $ioc->bind('d');
41$eClass = $ioc->bind('e');
42
43var_dump($bClass); // B
44var_dump($cClass); // C
45var_dump($dClass); // D
46var_dump($eClass); // NULL
Copy the code

conclusion

Closure features often appear in the functionality of event callback classes, as well as in implementations of IoC like egg Eggs. Closures have a powerful ability to lazily load. In the IoC example, our closure returns an object that comes out of new. If $ioc->bind(‘b’) is not called when the program is running, then the b object will not be created. We use closures to actually create objects when we need them out of the service container. Similarly, event callbacks are the same concept. When the event occurs, we execute the code in the callback when we need to handle it. If there were no concept of closures, then the $objs container would write:


1$this->objs['b'] = new B();
2$this->objs['c'] = new C();
3$this->objs['d'] = new D();
Copy the code


All classes must be instantiated when the container instantiates them. In this way, a lot of unnecessary objects are created for the program, resulting in a huge waste of resources.


Based on the power of closures, closure functions are now ubiquitous in Laravel, TP6, and other frameworks. There is no end to learning, and it is often more efficient to learn the framework after mastering the principle.


Test code: https://github.com/zhangyue0503/dev-blog/blob/master/php/201911/source/%E8%BF%98%E4%B8%8D%E7%9F%A5%E9%81%93PHP%E6%9C%89 %E9%97%AD%E5%8C%85%EF%BC%9F%E9%82%A3%E4%BD%A0%E7%9C%9FOUT%E4%BA%86.php

Reference documents: https://www.php.net/manual/zh/functions.anonymous.php https://www.php.net/manual/zh/functions.anonymous.php#100545 https://www.php.net/manual/zh/functions.anonymous.php#119388