PHP Clourse(閉包類) 淺析

0x00 前言

閉包是指在建立時封裝周圍狀態的函數。即便閉包所在的環境不存在了,閉包中封裝的狀態依然存在。php

在 PHP 裏全部的閉包都是 Clourse 類所實例化的一個對象,也就是說閉包與其餘 PHP 對象沒有什麼不一樣。而一個對象就必然有其方法和屬性,這篇文章將總結 PHP 中閉包的基礎用法和 Clourse 類方法的做用。html

0x01 閉包基本用法

下面看看最基本的閉包使用方法:閉包

<?php
$hello = function ($word) {
    return 'hello ' . $word;
};

echo $hello('world');
// 輸出 hello world
複製代碼

嘿,這段代碼最直觀的感覺就是將一個函數賦值給了 $hello 變量,而後經過 $hello 直接調用它。可是這個閉包並無從父做用域中繼承變量(就是封裝周圍狀態),咱們能夠經過 use 關鍵字從閉包的父做用域繼承變量。示例以下:app

<?php
$name = 'panda';

$hello = function () use ($name) {
    return 'hello ' . $name;
};

echo $hello();
// 輸出 hello panda
複製代碼

PHP 7.1 起,use 不能傳入此類變量: superglobals、 $this 或者和參數重名。框架

此外在使用 use 關鍵字時,父做用域的變量是經過值傳遞進閉包的。也就是說一旦閉包建立完成,外部的變量即便修改也不會影響傳遞進閉包內的值(就是即便閉包所在的環境不存在了,閉包中封裝的狀態依然存在)。示例以下:ide

<?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 傳遞的對象時須要特別注意。示例以下:oop

<?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 類對象。post

Clourse 類摘要

咱們能夠從 PHP 官方手冊 看到閉包類的相關信息,下面是我在 PhpStorm 的本地文檔查看到 Clourse 類摘要。this

/** * 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 指向哪一個對象?

經過 bindTobind 方法都可以實現綁定 $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 屬性改寫爲 protectedprivate 則會拋出一個致命錯誤!

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
複製代碼

這裏咱們看見 bindTobind 方法都指定了 $newscope 參數,$newscope 參數默認爲 static 即不改變類做用域。$newscope 參數接受類名或對象,並將閉包的類做用域改成指定的類做用域,此時 Pandas 類的 $num 屬性便可以被閉包訪問。

一次性綁定 $this 對象和類做用域並執行(PHP7)

bindTobind 方法每次指定新的對象和類做用域時都要將原閉包進行復制而後返回新的閉包,在須要屢次修改綁定對象的情景下便顯得繁瑣,因此 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 類沒有更新,因此沒有 callfromCallable 方法的內容,推薦你們看英文版(ㄒoㄒ)。

PHP Clourse(閉包類) 淺析 - 個人博客原文

相關文章
相關標籤/搜索