0x00 前言
閉包是指在建立時封裝周圍狀態的函數。即使閉包所在的環境不存在了,閉包中封裝的狀態依然存在。
在 PHP 裡所有的閉包都是 Clourse 類別所實例化的對象,也就是說閉包與其他 PHP 物件沒有什麼不同。而一個物件必然有其方法和屬性,這篇文章將總結 PHP 中閉包的基礎用法和 Clourse 類別方法的作用。 【推薦:PHP影片教學】
0x01 閉包基本用法
#下面看看最基本的閉包使用方法:
<?php $hello = function ($word) { return 'hello ' . $word; }; echo $hello('world'); // 输出 hello world
嘿,這段程式碼最直觀的感受就是將一個函數賦值給了$hello 變量,然後透過$hello 直接呼叫它。但這個閉包並沒有從父作用域繼承變數(就是封裝周圍狀態),我們可以透過 use 關鍵字從閉包的父作用域繼承變數。範例如下:
<?php $name = 'panda'; $hello = function () use ($name) { return 'hello ' . $name; }; echo $hello(); // 输出 hello panda
PHP 7.1 起,use 不能傳入此類變數: superglobals、 $this 或和參數重名。
此外在使用 use 關鍵字時,父作用域的變數是透過值傳遞進閉包的。也就是說一旦閉包建立完成,外部的變數即使修改也不會影響傳遞進閉包內的值(就是即使閉包所在的環境不存在了,閉包中封裝的狀態依然存在)。範例如下:
<?php $name = 'panda'; $hello = function () use ($name) { return 'hello ' . $name; }; $name = 'cat'; echo $hello(); // 输出 hello panda
傳遞變數的參考可以使閉包修改外部變數的值,範例如下:
<?php $name = 'panda'; $changeName = function () use (&$name) { $name = 'cat'; }; $changeName(); echo $name; // 输出 cat
注意:PHP 中傳遞物件時,預設是以引用傳遞所以在閉包內操作use 傳遞的物件時需要特別注意。範例如下:
<?php class Dog { public $name = 'Wang Cai'; } $dog = new Dog(); $changeName = function () use ($dog) { $dog->name = 'Lai Fu'; }; $changeName(); echo $dog->name; // 输出 Lai Fu
0x02 Clourse 類別
證明閉包只是Clourse 類別物件
<?php $clourse = function () { echo 'hello clourse'; }; if (is_object($clourse)) { echo get_class($clourse); } // 输出 Closure
上面的程式碼將輸出Closure 證明了閉包只是一個普通的Closure 類別物件。
Clourse 類別摘要
我們可以從 PHP 官方手冊 看到閉包類別的相關信息,下面是我在 PhpStorm 的本地文檔查看到 Clourse 類別摘要。
/** * Class used to represent anonymous functions. * <p>Anonymous functions, implemented in PHP 5.3, yield objects of this type. * This fact used to be considered an implementation detail, but it can now be relied upon. * Starting with PHP 5.4, this class has methods that allow further control of the anonymous function after it has been created. * <p>Besides the methods listed here, this class also has an __invoke method. * This is for consistency with other classes that implement calling magic, as this method is not used for calling the function. * @link http://www.php.net/manual/en/class.closure.php */ final class Closure { /** * This method exists only to disallow instantiation of the Closure class. * Objects of this class are created in the fashion described on the anonymous functions page. * @link http://www.php.net/manual/en/closure.construct.php */ private function __construct() { } /** * This is for consistency with other classes that implement calling magic, * as this method is not used for calling the function. * @param mixed $_ [optional] * @return mixed * @link http://www.php.net/manual/en/class.closure.php */ public function __invoke(...$_) { } /** * Duplicates the closure with a new bound object and class scope * @link http://www.php.net/manual/en/closure.bindto.php * @param object $newthis The object to which the given anonymous function should be bound, or NULL for the closure to be unbound. * @param mixed $newscope The class scope to which associate the closure is to be associated, or 'static' to keep the current one. * If an object is given, the type of the object will be used instead. * This determines the visibility of protected and private methods of the bound object. * @return Closure Returns the newly created Closure object or FALSE on failure */ function bindTo($newthis, $newscope = 'static') { } /** * This method is a static version of Closure::bindTo(). * See the documentation of that method for more information. * @static * @link http://www.php.net/manual/en/closure.bind.php * @param Closure $closure The anonymous functions to bind. * @param object $newthis The object to which the given anonymous function should be bound, or NULL for the closure to be unbound. * @param mixed $newscope The class scope to which associate the closure is to be associated, or 'static' to keep the current one. * If an object is given, the type of the object will be used instead. * This determines the visibility of protected and private methods of the bound object. * @return Closure Returns the newly created Closure object or FALSE on failure */ static function bind(Closure $closure, $newthis, $newscope = 'static') { } /** * Temporarily binds the closure to newthis, and calls it with any given parameters. * @link http://php.net/manual/en/closure.call.php * @param object $newThis The object to bind the closure to for the duration of the call. * @param mixed $parameters [optional] Zero or more parameters, which will be given as parameters to the closure. * @return mixed * @since 7.0 */ function call ($newThis, ...$parameters) {} /** * @param callable $callable * @return Closure * @since 7.1 */ public static function fromCallable (callable $callable) {} }
首先Clourse 類別為final 類,也就是說它將無法被繼承,其次它的建構子__construct 被設為private 即無法透過new 關鍵字實例化閉包對象,這兩點保證了閉包只能透過function (...) use(...) {...} 這種語法實例化。
為什麼閉包可以當作函數執行?
從上面的類別摘要中我們看出Clourse 類別實作了__invoke 方法,在PHP 官方手冊中對該方法解釋如下:
當嘗試以呼叫函數的方式呼叫一個物件時,__invoke() 方法會被自動呼叫。
這就是閉包可以被當作函數執行的原因。
綁定指定的$this物件和類別作用域
在允許使用閉包路由的框架中(如:Slim),我們可以看見如下寫法:
$app->get('/test', function () { echo $this->request->getMethod(); });
在一個閉包居然能中使用$this?這個 $this 指向哪個物件?
透過bindTo 和bind 方法都能夠實現綁定$this 和類別作用域的功能,範例如下:
<?php class Pandas { public $num = 1; } $pandas = new Pandas(); $add = function () { echo ++$this->num . PHP_EOL; }; $newAdd1 = $add->bindTo($pandas); $newAdd1(); // 输出 2 $newAdd2 = Closure::bind($add, $pandas); $newAdd2(); // 输出 3
上面的這段例子將指定物件綁定為閉包的$ this,但是我們並沒有指定類別作用域。所以如果將 Pandas 類別的 $num 屬性改寫為 protected 或 private 則會拋出一個致命錯誤!
Fatal error: Uncaught Error: Cannot access protected property Pandas::$num
在需要存取綁定物件的非公開屬性或方法時,我們需要指定類別作用域,範例如下:
<?php class Pandas { protected $num = 1; } $pandas = new Pandas(); $add = function () { echo ++$this->num . PHP_EOL; }; $newAdd1 = $add->bindTo($pandas, $pandas); $newAdd1(); // 输出 2 $newAdd2 = Closure::bind($add, $pandas, 'Pandas'); $newAdd2(); // 输出 3
這裡我們看見bindTo 和bind 方法都指定了$newscope參數,$newscope 參數預設為static 即不改變類別作用域。 $newscope 參數接受類別名稱或對象,並將閉包的類別作用域改為指定的類別作用域,此時 Pandas 類別的 $num 屬性便能夠被閉包存取。
一次綁定$this 物件和類別作用域並執行(PHP7)
bindTo 和bind 方法每次指定新的物件和類別作用域時都要將原閉包進行複製然後返回新的閉包,在需要多次修改綁定對象的情景下便顯得繁瑣,所以PHP7 提供了一個新的方法call 它能將閉包臨時的綁定到一個對象中(類別作用域同時被修改為該物件所屬的類別)並執行。範例如下:
<?php class Pandas { protected $num = 1; } $pandas = new Pandas(); $add = function ($num) { $this->num += $num; echo $this->num . PHP_EOL; }; $add->call($pandas, 5); // 输出 6
Callable 轉為閉包(PHP7.1)
在PHP7.1 中Closure 類別存在fromCallable 方法能夠將callable 類型的值轉為閉包,範例如下:
<?php class Foo { protected $num = 1; public static function hello(string $bar) { echo 'hello ' . $bar; } } $hello = Closure::fromCallable(['Foo', 'hello']); $hello('world');
這種寫法還是挺爽的畢竟透過閉包呼叫總比用call_user_func 函式呼叫爽的多^_^。
0x03 總結
##更多相關內容請看Closure 類別和匿名函數,因為PHP 官方手冊中文版的Closure 類別沒有更新,所以沒有call 和fromCallable 方法的內容,推薦大家看英文版(ㄒoㄒ)。以上是解析PHP閉包及Clourse類別方法的作用的詳細內容。更多資訊請關注PHP中文網其他相關文章!