iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 22
2
Modern Web

成為 Modern PHPer系列 第 22

Day 22:Closure 匿名函式

  • 分享至 

  • xImage
  •  

前言

PHP 中,Closure 又稱為 Anonymous Function,中文譯為匿名函式:從字面上的意思上推敲,就是「沒有名字的函式」。

$inc = function (int $n): int {
    return $n + 1;
}

$inc(10); // 11

實務運用

通常 Closure 用於「具有變化可能性」的函式參數,舉例來說在各 Routing 的實作中,時常可以看到以下用法

<?php

Route::get('/', function () { return 'index'; });
Route::get('/{name}', function ($name) { return "Hello, $name"; };

此處借鑑了 Laravel 的 Routing 寫法:第一個參數為 Path,第二個參數為這個 Path 的處理方法。

變數傳遞

如果要將外部變數傳給 Closure,需要以 use 明確指定,且 Closure 中的變更不會影響外部變數:


$number = 10;

$c = function () use ($number) {
    return ++$n;
};

$c(); // 11
$number; // 10

如果希望 Closure 中的變更影響外部變數,可以利用 & 將匿名函式中的變更影響原本的外部變數

$number = 10;

$c = function () use (&$number) {
    return ++$n;
};

$c(); // 11
$number; // 11

語言層實作

PHP 在處理 Closure 時,是產生一個 Closure Class,這個 Class 有一些特質

  • __consturct 為私有,故此 Class 無法被 new 出來
  • 具有 bindbindTo 兩個 methods,兩個功能類似但 bind 是 static
  • 存在 __invoke method,但僅是為了與 Callable Object 保持一致性,與 Closure 的執行無關

bindbindTo

bindTo

bindTo 用於複製一個 Closure,並且使用原本的 $this 及其可見性。

假設存在 Class A:

<?php

class A
{
    protected $val;

    public function __construct($val) {
        $this->val = $val;
    }
    
    public function getClosure(): Closure {
        return function () {
            return $this->val;
        };
    }
}

我們可以用以下方式利用 getClosure()

<?php

$firstClass = new A(10);
$secondClass = new A(20);

$aClosure = $firstClass->getClosure();
$aClosure(); // 10
$bClosure = $aClosure->bindTo($secondClass);
$bClosure(); // 20

因為 $aClosure 是從 $firstClass 中出來的,所以回傳 10;因為 $bClosure 是將 $aClosure 這個 Closure 重新綁定到 $secondClass,所以會回傳 20

順帶一提,bindTo 可以改變 method 的可見性,舉例來說

<?php

class A 
{
    protected $val = 10;
    
    public function getClosure() { 
        return function () { 
            return $this->val;
        };
    }
}

class B
{
    protected $val = 20;
}

$a = new A();
$b = new B();

$aClosure = $a->getClosure();
$aClosure(); // 10

// 在這邊將 $bClosure 的可見性設為 B Class
// 如此一來,$bClosure 就可以存取 B 的 protected variable
$bClosure = $aClosure->bindTo($b, B::class);
$bClosure(); // 20

bind

bindbindTo 的靜態版本,其用法基本上與 bindTo 類似,差別僅在於 Closure 可以在 bind 的第一個參數中指定

class A
{
    protected $val = 10;
}

Closure::bind(function () {
    return $this->val;
}, new A(), A::class);

複製 Closure

bindbindTo 不應是用來「複製」 Closure 使用,如果單純只是要複製一個 Closure 的話應該使用 clone

$c = function (int $n): int { return $n + 1; }
$cCopy = clone $c;

箭頭函式

在 PHP 7.4 中,Arrow Functino 將簡化 Closure 的寫法。

$c = fn($x) => ++$x;
// $c = function ($x) { return $++x; } 

另外,Arrow Function 會自動捕獲外部變數,所以不再需要使用 use

$y = 10;
$c = fn($x) => $x + $y;

$c(100); // 110

後記

今天的內容針對 Closure 做了比較詳細的解釋。

事實上,在部份的 Framework 中已經大量使用 Closure 實現類似 Functional Programming 的 Lambda,雖然在目前(PHP 7.3)中還是有所不便,但這樣的狀況在 Arrow Function 出現後應該會有所改善。


上一篇
Day 21:Callable 類型
下一篇
Day 23:Class 的 Magic Method
系列文
成為 Modern PHPer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言