php 中的closure用法實例詳解

closure,匿名函數,又稱為anonymous functions,是php5.3的時候引入的。匿名函數就是沒有定義名字的函數,也就是閉包(closure。這點牢牢記住就能理解匿名函數的定義了。比如以下代碼(文件名是do.php)

<?php function A() {    return 100;  };  function B(Closure $callback)  {    return $callback();  }  $a = B(A());  print_r($a);//輸出:Fatal error: Uncaught TypeError: Argument 1 passed to B() must be an instance of Closure, integer given, called in D:webtestdo.php on line 11 and defined in D:webtestdo.php:6 Stack trace: #0 D:webtestdo.php(11): B(100) #1 {main} thrown in D:webtestdo.php on line 6  ?>

這里的A()永遠沒有辦法用來作為B的參數,因為A它并不是“匿名”函數。

所以應該改成這樣:

<?php $f = function () {    return 100;  };  function B(Closure $callback)  {    return $callback();  }  $a = B($f);  print_r($a);//輸出100  <?  $func = function( $param ) {    echo $param;  };  $func( &#39;hello word&#39; );  //輸出:hello word

實現閉包

將匿名函數在普通函數中當做參數傳入,也可以被返回。這就實現了一個簡單的閉包。

立即學習PHP免費學習筆記(深入)”;

下邊我舉三個例子:

<?php //例一  //在函數里定義一個匿名函數,并且調用它  function printStr() {    $func = function( $str ) {      echo $str;    };    $func( &#39; hello my girlfriend ! &#39; );  }  printStr();//輸出 hello my girlfriend !  //例二  //在函數中把匿名函數返回,并且調用它  function getPrintStrFunc() {    $func = function( $str ) {      echo $str;    };    return $func;  }  $printStrFunc = getPrintStrFunc();  $printStrFunc( &#39; do you love me ? &#39; );//輸出 do you love me ?  //例三  //把匿名函數當做參數傳遞,并且調用它  function callFunc( $func ) {    $func( &#39; no!i hate you &#39; );  }  $printStrFunc = function( $str ) {    echo $str.&#39;<br>';  };  callFunc(?$printStrFunc?);  //也可以直接將匿名函數進行傳遞。如果你了解js,這種寫法可能會很熟悉  callFunc(?function(?$str?)?{  ??echo?$str;?//輸出no!i?hate?you  }?);

連接閉包和外界變量的關鍵字:USE

閉包可以保存所在代碼塊上下文的一些變量和值。PHP在默認情況下,匿名函數不能調用所在代碼塊的上下文變量,而需要通過使用use關鍵字。

換一個例子看看(好吧,我缺錢,我很俗):

<?php function getMoney() {    $rmb = 1;    $dollar = 8;    $func = function() use ( $rmb ) {      echo $rmb;      echo $dollar;    };    $func();  }  getMoney();  //輸出:1

可以看到,dollar沒有在use關鍵字中聲明,在這個匿名函數里也就不能獲取到它,所以開發中要注意這個問題。

有人可能會想到,是否可以在匿名函數中改變上下文的變量,但我發現好像是不可以的:

<?php function getMoney() {    $rmb = 1;    $func = function() use ( $rmb ) {      echo $rmb.&#39;<br>';  ????//把$rmb的值加1  ????$rmb++;  ??};  ??$func();  ??echo?$rmb;  }  getMoney();  //輸出:  //1  //1

額,原來use所引用的也只不過是變量的一個副本clone而已。但是我想要完全引用變量,而不是復制呢?要達到這種效果,其實在變量前加一個 & 符號就可以了:

<?php function getMoney() {    $rmb = 1;    $func = function() use ( &$rmb ) {      echo $rmb.&#39;<br>';  ????//把$rmb的值加1  ????$rmb++;  ??};  ??$func();  ??echo?$rmb;  }  getMoney();  //輸出:  //1  //2

好,這樣匿名函數就可以引用上下文的變量了。如果將匿名函數返回給外界,匿名函數會保存use所引用的變量,而外界則不能得到這些變量,這樣形成‘閉包’這個概念可能會更清晰一些。

根據描述我們再改變一下上面的例子:

<?php function getMoneyFunc() {    $rmb = 1;    $func = function() use ( &$rmb ) {      echo $rmb.&#39;<br>';  ????//把$rmb的值加1  ????$rmb++;  ??};  ??return?$func;  }  $getMoney?=?getMoneyFunc();  $getMoney();  $getMoney();  $getMoney();  //輸出:  //1  //2  //3

好吧,扯了這么多,那么如果我們要調用一個類里面的匿名函數呢?直接上demo

<?php class A {    public static function testA() {      return function($i) { //返回匿名函數        return $i+100;      };    }  }  function B(Closure $callback)  {    return $callback(200);  }  $a = B(A::testA());  print_r($a);//輸出 300

其中的A::testA()返回的就是一個無名funciton。

綁定的概念

上面的例子的Closure只是全局的的匿名函數,好了,那我們現在想指定一個類有一個匿名函數。也可以理解說,這個匿名函數的訪問范圍不再是全局的了,而是一個類的訪問范圍。

那么我們就需要將“一個匿名函數綁定到一個類中”。

<?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

上面的例子中,f這個匿名函數中莫名奇妙的有個this,這個this關鍵詞就是說明這個匿名函數是需要綁定在類中的。

綁定之后,就好像A中有這么個函數一樣,但是這個函數是public還是private,bind的最后一個參數就說明了這個函數的可調用范圍。

上面大家看到了bindTo,我們來看官網的介紹

(PHP?5?&gt;=?5.4.0,?PHP?7)

Closure::bind — 復制一個閉包,綁定指定的$this對象和類作用域。

說明

public static Closure Closure::bind ( Closure $closure , object $newthis [, mixed $newscope = ‘static’ ] )
這個方法是 匿名函數() 的靜態版本。查看它的文檔獲取更多信息。

參數

closure

需要綁定的匿名函數。

newthis

需要綁定到匿名函數的對象,或者 NULL 創建未綁定的閉包。

newscope

想要綁定給閉包的類作用域,或者 ‘static’ 表示不改變。如果傳入一個對象,則使用這個對象的類型名。 類作用域用來決定在閉包中 $this 對象的 私有、保護方法 的可見性。(備注:可以傳入類名或類的實例,默認值是 ‘static’, 表示不改變。)

返回值:

返回一個新的 Closure 對象 或者在失敗時返回 FALSE

<?php class A {    private static $sfoo = 1;    private $ifoo = 2;  }  $cl1 = static function() {    return A::$sfoo;  };  $cl2 = function() {    return $this->ifoo;  };  $bcl1?=?Closure::bind($cl1,?null,?'A');  $bcl2?=?Closure::bind($cl2,?new?A(),?'A');  echo?$bcl1(),?"n";//輸出?1  echo?$bcl2(),?"n";//輸出?2

我們再來看個例子加深下理解:

<?php class A {    public $base = 100;  }  class B {    private $base = 1000;  }  class C {    private static $base = 10000;  }  $f = function () {    return $this->base?+?3;  };  $sf?=?static?function()?{  ??return?self::$base?+?3;  };  $a?=?Closure::bind($f,?new?A);  print_r($a());//這里輸出103,綁定到A類  echo?PHP_EOL;  $b?=?Closure::bind($f,?new?B?,?'B');  print_r($b());//這里輸出1003,綁定到B類  echo?PHP_EOL;  $c?=?$sf-&gt;bindTo(null,?'C');?//注意這里:使用變量#sf綁定到C類,默認第一個參數為null  print_r($c());//這里輸出10003

我們再看一個demo:

<?php /**   * 復制一個閉包,綁定指定的$this對象和類作用域。   *   * @author fantasy   */  class Animal {    private static $cat = "加菲貓";    private $dog = "汪汪隊";    public $pig = "豬豬俠";  }  /*   * 獲取Animal類靜態私有成員屬性   */  $cat = static function() {    return Animal::$cat;  };  /*   * 獲取Animal實例私有成員屬性   */  $dog = function() {    return $this->dog;  };  /*  ?*?獲取Animal實例公有成員屬性  ?*/  $pig?=?function()?{  ??return?$this-&gt;pig;  };  $bindCat?=?Closure::bind($cat,?null,?new?Animal());//?給閉包綁定了Animal實例的作用域,但未給閉包綁定$this對象  $bindDog?=?Closure::bind($dog,?new?Animal(),?'Animal');//?給閉包綁定了Animal類的作用域,同時將Animal實例對象作為$this對象綁定給閉包  $bindPig?=?Closure::bind($pig,?new?Animal());//?將Animal實例對象作為$this對象綁定給閉包,保留閉包原有作用域  echo?$bindCat(),'<br>';//?輸出:加菲貓,根據綁定規則,允許閉包通過作用域限定操作符獲取Animal類靜態私有成員屬性  echo?$bindDog(),'<br>';//?輸出:汪汪隊,?根據綁定規則,允許閉包通過綁定的$this對象(Animal實例對象)獲取Animal實例私有成員屬性  echo?$bindPig(),'<br>';//?輸出:豬豬俠,?根據綁定規則,允許閉包通過綁定的$this對象獲取Animal實例公有成員屬性

通過上面的幾個例子,其實匿名綁定的理解就不難了….我們在看一個擴展的demo(引入trait特性)

<?php /**   * 給類動態添加新方法   *   * @author fantasy   */  trait DynamicTrait {    /**     * 自動調用類中存在的方法     */    public function call($name, $args) {      if(is_callable($this->$name)){  ??????return?call_user_func($this-&gt;$name,?$args);  ????}else{  ??????throw?new?RuntimeException("Method?{$name}?does?not?exist");  ????}  ??}  ??/**  ???*?添加方法  ???*/  ??public?function?set($name,?$value)?{  ????$this-&gt;$name?=?is_callable($value)?  ??????$value-&gt;bindTo($this,?$this):  ??????$value;  ??}  }  /**  ?*?只帶屬性不帶方法動物類  ?*  ?*?@author?fantasy  ?*/  class?Animal?{  ??use?DynamicTrait;  ??private?$dog?=?'汪汪隊';  }  $animal?=?new?Animal;  //?往動物類實例中添加一個方法獲取實例的私有屬性$dog  $animal-&gt;getdog?=?function()?{  ??return?$this-&gt;dog;  };  echo?$animal-&gt;getdog();//輸出?汪汪隊

比如現在我們用現在購物環境

<?php /**   * 一個基本的購物車,包括一些已經添加的商品和每種商品的數量   *   * @author fantasy   */  class Cart {    // 定義商品價格    const PRICE_BUTTER = 10.00;    const PRICE_MILK  = 30.33;    const PRICE_EGGS  = 80.88;     protected  $products = array();    /**     * 添加商品和數量     *     * @access public     * @param string 商品名稱     * @param string 商品數量     */    public function add($item, $quantity) {      $this->products[$item]?=?$quantity;  ??}  ??/**  ???*?獲取單項商品數量  ???*  ???*?@access?public  ???*?@param?string?商品名稱  ???*/  ??public?function?getQuantity($item)?{  ????return?isset($this-&gt;products[$item])???$this-&gt;products[$item]?:?FALSE;  ??}  ??/**  ???*?獲取總價  ???*  ???*?@access?public  ???*?@param?string?稅率  ???*/  ??public?function?getTotal($tax)?{  ????$total?=?0.00;  ????$callback?=?function?($quantity,?$item)?use?($tax,?&amp;$total)?{  ??????$pricePerItem?=?constant(CLASS?.?"::PRICE_"?.?strtoupper($item));?//調用以上對應的常量  ??????$total?+=?($pricePerItem?*?$quantity)?*?($tax?+?1.0);  ????};  ????array_walk($this-&gt;products,?$callback);  ????return?round($total,?2);  ??}  }  $my_cart?=?new?Cart;  //?往購物車里添加商品及對應數量  $my_cart-&gt;add('butter',?10);  $my_cart-&gt;add('milk',?3);  $my_cart-&gt;add('eggs',?12);  //?打出出總價格,其中有?3%?的銷售稅.  echo?$my_cart-&gt;getTotal(0.03);//輸出?1196.4

補充說明:閉包可以使用USE關鍵連接外部變量。

總結:PHP閉包的特性其實用CLASS就可以實現類似甚至強大得多的功能,更不能和js的閉包相提并論了吧,只能期待PHP以后對匿名函數的改進。不過匿名函數還是挺有用的,比如在使用preg_replace_callback等之類的函數可以不用在外部聲明匿名函數了。合理使用閉包能使代碼更加簡潔和精煉。

? 版權聲明
THE END
喜歡就支持一下吧
點贊5 分享