PHP 匿名函式初探

alalala發表於2020-04-09

9FYCKx

PHP中的閉包函式

Closure —-> 匿名函式,又稱為 Anonymous functions , 是 PHP5.3 的時候引入的。

匿名函式 就是沒有定義名字的函式。

非匿名函式

<?php
// 非匿名函式
function test()
{
  return 100;
}

// 測試匿名函式的方法
// 傳入的引數是一個匿名函式
function testClosure(Closure $callback)
{
  return $callback();
}

$a = testClosure(test());
print_r($a);// 報錯,因為傳入的函式 test() 並非匿名函式 PHP Fatal error:  Uncaught TypeError: Argument 1 passed to testClosure() must be an instance of Closure, integer given

上面的報錯是指,函式 testClosure() 中傳入的引數應該是一個 匿名函式 的例項,卻傳入一個 int 型別的值。

因為 test() 不是匿名函式,因此 test() 的出現相當於呼叫這個函式,也就是說 $a = testClosure(test());$a = testClosure(10); 效果是等同的;

匿名函式

<?php
// 匿名函式
$f = function()
{
  return 10;
};

// 測試匿名函式的方法
// 傳入的引數是一個匿名函式
function testClosure(Closure $callback) 
{
  return $callback();
}

$a = testClosure($f);
print_r($a); // 10

呼叫一個類中的匿名函式

<?php
class C
{
  public static function testC()
  {
    return function ($i) {
      return $i + 10;
    };
  }
}

function testClosure(Closure $callback)
{
  // 呼叫傳入的閉包函式,並給閉包函式中傳入引數13
  return $callback(13);
}

$a = testClosure(C::testC());
print_r($a);

testClosure(C::testC()); 中的 C::testC() 就相當於 :

function ($i) {
  return $i + 10;
};

C::testC() 返回的是一個 funciton

繫結的概念

上面的例子的 Closure 只是 全域性的的匿名函式,好了,我現在想指定一個類有一個匿名函式。

也可以理解說,這個匿名函式的訪問範圍不再是全域性的了,是一個類的訪問範圍

bind

(PHP 5 >= 5.4.0, PHP 7)

Closure::bind — 複製一個閉包,繫結指定的$this物件和類作用域。

public static Closure::bind ( Closure $closure , object $newthis [, mixed $newscope = ‘static’ ] ) : Closure

bind 一共三個引數:

  1. 需要繫結的匿名函式;
  2. 需要繫結到匿名函式的物件,或者 NULL 建立未繫結的閉包;
  3. 想要繫結給閉包的類作用域,或者 ‘static’ 表示不改變。如果傳入一個物件,則使用這個物件的型別名。 類作用域用來決定在閉包中 $this 物件的 私有、保護方法 的可見性。

注意

Closure::bind() 在使用時,最少得有兩個引數,如果第二個引數不是很有必要的話,可以寫 null 填充

將一個 匿名函式 繫結到一個類中。

<?php
class A
{
  public $base = 100;
}

class B
{
   private $base = 1000;
}

$f = function()
{
  return $this->base + 3;
};

$a = Closure::bind($f, new A);
print_r($a()); // 103

echo PHP_EOL;

$b = Closure::bind($f, new B , 'B');
print_r($b()); // 1003

echo PHP_EOL;

上面的程式碼中,f 這個匿名函式中莫名其妙出現了一個 $this, 就說明這個函式是需要繫結到類中使用的,也可以說是 匿名函式 中的 $this 指向某個類的例項物件上。

就像這樣:

$f = function()
{
  $this = new A() / new B(); // 只是類比,實際上不能這麼用,因為 $this 是關鍵字
  return $this->base + 3;
};

但是我們注意到,這裡兩個 bind 傳入的引數個數是不同的,而且兩個類 AB 中的 $base 變數作用域是不同的,前者是 public ,或者為 private

public、protected、private

  • public公有屬性,在子類中可以通過 self::var呼叫 public方法或屬性,parent::method 呼叫父類方法 。在例項中可以能**過 $obj->var 來呼叫 public 型別的方法或屬性;
  • protected受保護屬性,在子類中可以通過 self::var 呼叫 protected 方法或屬性,parent::method 呼叫父類方法,在例項中不能通過 $obj->var來呼叫 protected 型別的方法或屬性;
  • private私有屬性,該型別的屬性或方法只能在該類中使用,在該類的例項、子類中、子類的例項中都不能呼叫私有型別的屬性和方法。

因此,$b 相對於 $a 而言,在繫結時,多了第三個引數, 就是為了將 class B私有屬性 轉化為 公有屬性 以便於在外部通過 $this->base 進行訪問。

舉個?

<?php
class A {
    private $name = '王力巨集';
    protected $age = '30';
    private static $weight = '70kg';
    public $address = '中國';
    public static $height = '180cm';

}

$fun = function() {
  return $this->address;
};

$fun1 = function() {
  return $this->name;
};

$fun2 = function() {
  return A::height;
};

$fun3 = function() {
  return A::weight;
};

funfun1fun2fun3 繫結到類 A 的情況我們們分別討論:

  • $fun

    $a = Closure::bind($fun, new A());
    print_r($a()); // 中國
    

    因為訪問的是類 A公有屬性,且匿名函式中包含 $this 關鍵詞,所以只需要前兩個引數就可以。

  • $fun1

    $b = Closure::bind($fun1, new A(), 'A');
    print_r($b()); // 王力巨集
    

    因為訪問的是類 A私有屬性,且匿名函式中包含 $this 關鍵詞,所以個引數都需要,將 私有屬性 轉化為 公有屬性 訪問。

  • $fun2

    $c = Closure::bind($fun2, null);
    print_r($c()); // 180cm
    
    // 或者
    $c = Closure::bind($fun2, new A);
    print_r($c());// 180cm
    

    因為訪問的是類 A公有靜態屬性,且匿名函式中包含 類名::靜態屬性 呼叫形式,其中已指明繫結的類名,因此第二個引數可寫明或者用 null 填充。

  • fun3

    $d = Closure::bind($fun3, null, 'A');
    print_r($d()); // 70kg
    
    // 或者
    $d = Closure::bind($fun3, new A, 'A');
    print_r($d());// 70kg
    

    因為訪問的是類 A私有靜態屬性,且匿名函式中包含 類名::靜態屬性 呼叫形式,其中已指明繫結的類名,因此第二個引數可寫明類名或者用 null 填充,且第個引數必須有。

總結

  1. 一般匿名函式中有 $this->$name 類似這樣用 $this 訪問屬性方式時,你在使用 bind 繫結時 ,第二個引數肯定要寫,寫出你繫結那個物件例項,第三個引數要不要呢,要看你訪問的這個屬性,在繫結物件中的許可權屬性,如果是 privateprotected 你要使用第三個引數使其變為 公有屬性, 如果本來就是公有,你可以省略,也可以不省略
  2. 一般匿名函式中是 類名::靜態屬性 類似這樣的訪問方式(比如例子中 A::$weight),你在使用 bind 繫結時,第二個引數可以寫 null,也可以寫出具體的物件例項,一般寫 null 就行(寫了具體物件例項多此一舉),第三個引數寫不寫還是得看你訪問的這個靜態屬性的許可權private 還是 public,如果不是public第三個引數能使其許可權變為公有屬性正常訪問,如果本來就是公有public可以不用寫,可以省略

bindTo

(PHP 5 >= 5.4.0, PHP 7)

Closure::bindTo — 複製當前閉包物件,繫結指定的$this物件和類作用域。

public Closure::bindTo ( object $newthis [, mixed $newscope = ‘static’ ] ) : Closure

建立並返回一個 匿名函式, 它與當前物件的函式體相同、繫結了同樣變數,但可以繫結不同的物件,也可以繫結新的類作用域。

“繫結的物件”決定了函式體中的 $this 的取值,“類作用域”代表一個型別、決定在這個匿名函式中能夠呼叫哪些 私有 和 保護 的方法。 也就是說,此時 $this 可以呼叫的方法,與 newscope 類的成員函式是相同的。

靜態閉包不能有繫結的物件( newthis 引數的值應該設為 NULL)不過仍然可以用 bubdTo 方法來改變它們的類作用域。

引數

  • newthis

    繫結給匿名函式的一個物件,或者 NULL 來取消繫結。

  • newscope

    關聯到匿名函式的類作用域,或者 static 保持當前狀態。如果是一個物件,則使用這個物件的型別為新的類作用域。 這會決定繫結的物件的 保護、私有成員 方法的可見性。

使用

Closure::bindToClosure::bind的動態方法,Closure::bindClosure::bindTo 的靜態方法,差別在於 Closure::bindTo 共有兩個引數,Closure::bind共有三個引數。

舉個?

上面的四個小例子,換成 bindTo 應該是:

// fun
$a = $fun -> bindTo(new A());
print_r($a()); // 中國

// fun1
$b = $fun1 -> bindTo(new A(), 'A');
print_r($b()); // 王力巨集

// fun2
$c = fun2 -> bindTo(null);
print_r($c()); // 180cm
// 或者
$c = Closure::bindTo(new A);
print_r($c());// 180cm

// fun3
$d = $fun3 -> bindTo(null, 'A');
print_r($d()); // 70kg
// 或者
$d = $fun3 -> bindTo(new A, 'A');
print_r($d());// 70kg

轉載?

PHP中閉包Closure::bind詳解

PHP 中的Closure

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章