归档文章 (2011-2017)

date
2017/01/01
匿名函数(Anonymous functions),也叫闭包函数(closures),允许临时创建一个没有指定名称的函数。闭包和匿名函数在 PHP5.3.0 中引入的。
闭包和匿名函数看起来像是函数,实际上他们是 Closure 类的一个实例,数据类型是对象。
参考链接:[匿名函数][1]

一、闭包的基本用法

1.1 示例1:作为变量的值来使用

闭包可以作为变量的值来使用,因为闭包实际是 PHP 内置类 Closure 的对象实例,数据类型是对象,因此我们可以把一个 closure 对象赋值给一个变量。
// 闭包赋值给 $clousre 变量$clousre = function ($name) { return 'Hello ' . $name;};// 可以用"变量名+括号"的形式调用,是因为 Closure 类中有 __invoke() 魔术方法// 当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。// nesfo 会传递给 $name 变量echo $closure('nesfo');
参考链接: - [__invoke() 魔术方法][2] - [Closure 类 ][3]

1.2 示例2:作为回调函数参数的值

闭包经常作为回调函数(callback)参数的值来使用
// 根据第一个参数给出的规则,使用一个回调函数替换第三个参数给出的内容。// 注意回调函数的参数($match)是一个数组,其最后返回真正参与替换的字符串// $match[1] 的值是 -wecho preg_replace_callback('~-([a-z])~', function ($match) { // 将 -w 转换成大写 W return strtoupper($match[1]);}, 'hello-world');// 输出 helloWorld
参考链接: - [preg_replace_callback 函数][4]

1.3 示例3:从父作用域继承变量

闭包可以从父作用域中继承变量,但是需要使用 use 来传递进去。
$message = 'hello';// 没有 "use"$example = function () { var_dump($message);};echo $example();//输出为null,匿名函数无法继承外部变量 $message,因为没有 "use"
使用 use 传递父作用域 $message 变量
// 继承 $message$message = 'hello';$example = function () use ($message) { var_dump($message);};echo $example();//输出 string 'hello' (length=5),使用关键词 use 后,//匿名函数可以获取到外部变量 $message.
引用继承,原理是匿名函数在定义时(不是在调用时)会获取其所继承的变量的值
$message = 'hello';// 引用继承,注意 &message$example = function () use (&message) { var_dump($message);};$message = 'world';echo $example();//输出 string 'world' (length=5),

二、Closure 闭包类

前面说过,闭包是 Closure 类的一个实例
Closure 类定义了三种方法:
  • Closure::__construct — 构造函数,定义了 Closure 类禁止实例化。
  • Closure::bindTo — 复制当前闭包对象内容,并给这个闭包绑定一个新的对象和类作用域
  • Closure::bind — 复制一个闭包对象内容,并给这个闭包绑定一个新的对象和类作用域

2.1 Closure::bindTo

复制当前闭包对象(创建并返回一个新的闭包),并给这个闭包绑定一个新的对象(newthis),并且可以为这个闭包指定类作用域(newscope)。
// newthis 绑定给匿名函数的一个对象,或者 NULL 来取消绑定。// newscope 为这个新闭包指定类作用域,或者 'static' 保持当前状态。// 返回新创建的 Closure 对象 或者在失败时返回 FALSEpublic Closure Closure::bindTo ( object $newthis [, mixed $newscope = 'static' ] )
如果你只是想要复制一个闭包,不需要绑定新到对象,可以用 cloning 代替。
bindTo 示例:
class A { function __construct($val) { $this->val = $val; } function getClosure() { //returns closure bound to this object and scope return function() { return $this->val; }; } } $ob1 = new A(1);$ob2 = new A(2);// 返回一个闭包对象,注意这里的 $this 指向的是 $ob1 对象$cl = $ob1->getClosure();echo $cl(), "\n"; //结果是 1// 复制上面 $cl 这个闭包对象内容,将 $this 绑定到了 $ob2 对象$cl = $cl->bindTo($ob2);echo $cl(), "\n"; //结果是 2
参考链接: - [Closure::bindTo][5]

2.2 Closure::bind

这个方法是 Closure::bindTo() 的静态版本。
语法结构:
public static Closure Closure::bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] )
  • closure 需要绑定的匿名函数
  • newthis 需要绑定到匿名函数的对象,或者 NULL 创建未绑定的闭包
  • newscope 想要绑定给闭包的类作用域,或者 ‘static’ 表示不改变
bind 示例:
class A { private static $sfoo = 1; private $ifoo = 2;} // 注意 $cl1 是 static 静态版闭包$cl1 = static function() { return A::$sfoo;};// $ifoo 是非静态属性,使用 $this->ifoo 访问$cl2 = function() { return $this->ifoo;};// 复制 $cl1 闭包,不绑定新对象,作用域 class A$bcl1 = Closure::bind($cl1, null, 'A');// 复制 $cl2 闭包,绑定到 new A() 对象,作用域 class A// 这里必须要为 $this 绑定到一个对象,而上面的 $cl1 是静态的,可以直接 A::$sfoo 访问,不需要绑定对象。$bcl2 = Closure::bind($cl2, new A(), 'A');echo $bcl1(), "\n"; // 1echo $bcl2(), "\n"; // 2
参考链接: - [Closure::bind][6]

三、闭包的作用

3.1 读取局部变量

通过闭包可以读取其他函数的内部变量
function f1() { $n = 2; // 通过闭包函数 $f2 使用 use 关键字获取外部变量 $n $f2 = function () use($n) { return $n; }; return $f2;} var_dump($n);// null, 因为 $n 是函数内的局部变量$f = f1();var_dump($f());//int 2

3.2 使用 bindTo() 或者 Closure::bind

bindTo 或者 Closure::bind 方法可以把闭包的 $this 绑定到其他对象上,这样闭包就可以访问那个对象中受保护和私有的成员变量。
class App { protected $routes = []; protected $responseStatus = '200 OK'; protected $responseContentType = 'text/html'; protected $responseBody = 'Hello world'; public function addRoute($routePath, $routeCallback){ $this->routes[$routePath] = Closure::bind($routeCallback,$this, __CLASS__); //$this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__); } public function dispatch($currentPath){ foreach($this->routes as $routePath => $callback){ if ($routePath === $currentPath) { // 闭包中的 __invoke() 方法会被自动调用 // $callback(5); 闭包可以加参数 $callback(); } } // $this->responseBody 原来值是 Hello world // 闭包绑定到 App 后,默认值修改为 {"name": "nesfo"} header('HTTP/1.1' . $this->responseStatus); header('Content-type: ' . $this->responseContentType); header('Content-length' . mb_strlen($this->responseBody)); echo $this->responseBody; } } $app = new App();$app->addRoute('/user/nesfo', function ($test) { $this->responseContentType = 'application/json; charset=utf8'; $this->responseBody = '{"name": "nesfo"}'; $this->test = $test;});$app->dispatch('/user/nesfo');
参考文档: - 学习PHP闭包 - 《Modern PHP》
[1]:[http://php.net/manual/zh/functions.anonymous.php] [2]:[http://php.net/manual/zh/language.oop5.magic.php#object.invok] [3]:[http://php.net/manual/zh/class.closure.php] [4]:[http://php.net/manual/zh/function.preg-replace-callback.php] [5]:[http://php.net/manual/zh/closure.bindto.php] [6]:[http://php.net/manual/zh/closure.bind.php]
If you have any questions, please contact me.