歡迎您光臨本站 註冊首頁

PHP V5.3 中的新特性,第 2 部分: 閉包及 lambda 函數

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
繼續學習 “PHP V5.3 中的新特性” 系列文章,了解 2008 年底即將發布的 PHP 5.3 版中的新特性及優秀功能。本系列的 第 1 部分 介紹了 PHP 5.3 中對面向對象編程及對象處理所做的更改。在第 2 部分中,我們將介紹閉包函數及 lambda 函數。這些函數專門用於簡化編程,讓您輕鬆地定義可以在許多不同環境中使用的拋棄型(throwaway)函數。

閉包函數和 lambda 函數絕對不是新出現的概念;它們均來自函數編程領域。函數編程 是一種編程風格,它將關注點從執行命令轉移到表達式計算。這些表達式是使用函數構成的,結合這些函數可以得到我們要查找的結果。這種編程風格最常用於學術目的,但是也可以在人工智慧與數學領域中見到,並且可以在用 Erlang、Haskell 及 Scheme 等語言編寫的商業應用程序中找到。

閉包 最初是在 20 世紀 60 年代作為 Scheme 的一部分開發的,Scheme 是最著名的函數編程語言之一。Lambda 函數和閉包通常出現在允許將函數處理為第一類值(First-class value)的語言中,這意味著函數可以動態創建並作為參數傳遞給其他語言。

從那時起,閉包及 lambda 函數已經找到了走出函數編程世界並進入 JavaScript、Python 和 Ruby 等語言的方法。JavaScript 是支持閉包和 lambda 函數的最常見語言之一。JavaScript 實際使用這些函數作為支持面向對象的編程方法,把函數嵌套到其他函數中以用作私有成員。清單 1 提供了 JavaScript 如何使用閉包的示例。


清單 1. 使用閉包構建 JavaScript 對象
				  var Example = function()  {       this.public = function()       {           return "This is a public method";       };         var private = function()       {           return "This is a private method";       };  };    Example.public()  // returns "This is a public method"   Example.private() // error - doesn't work  

如清單 1 中所示,Example 對象的成員函數被定義為閉包。由於私有方法作用於局部變數(與綁定到使用此關鍵字的 Example 對象的公共方法相反),因此從外部看不到它。

現在我們已經了解了這些概念的歷史,讓我們查看 PHP 中的 lambda 函數。lambda 函數的概念是閉包的基礎,並且提供了一種比 PHP 中已有的 create_function() 函數改進了很多的動態創建函數的方法。

Lambda 函數

Lambda 函數(或者通常所謂的 “匿名函數”)是可以隨時定義的簡單拋棄型函數,並且通常都與變數綁定。函數本身僅存在於定義函數的變數範圍內,因此當該變數超出範圍時,函數也超出範圍。lambda 函數的理念源於 20 世紀 30 年代的數學研究。它被稱為 lambda 演算,用於研究函數定義與應用程序以及遞歸概念。lambda 演算用於開發函數編程語言,例如 Lisp 和 Scheme。

對於大多數實例來說,尤其對於接受回調函數的許多 PHP 函數來說,Lambda 函數非常方便。array_map() 就是這樣一種函數,它允許我們遍曆數組並將回調函數應用到數組的每個元素上。在早期版本的 PHP 中,這些函數的最大問題是沒有一種清晰的方式定義回調函數;我們堅持使用以下三種解決方法的其中一種:

  1. 我們可以在代碼中的其他位置定義回調函數,因此我們知道它可用。這有些麻煩,因為它把調用的實現部分移到了其他位置,這樣做對於可讀性與可維護性極為不便,尤其是不打算在其他位置使用此函數時。
  2. 我們可以在同一個代碼塊中定義回調函數,但是使用一個名稱。雖然這樣做有助於把內容放在一起,但是需要在定義周圍添加 if 塊以避免名稱空間衝突。清單 2 展示了這種方法。


    清單 2. 在同一個代碼塊中定義指定的回調
    						  function quoteWords()  {       if (!function_exists ('quoteWordsHelper')) {           function quoteWordsHelper($string) {               return preg_replace('/(\w)/','"$1"',$string);           }        }        return array_map('quoteWordsHelper', $text);  }

  3. 我們可以使用 create_function()(從 V4 開始就是 PHP 的一部分)在運行時創建函數。雖然在功能上此函數執行了所需操作,但是它有一些缺點。一個主要缺點是,它在運行時而非編譯時編譯,不允許操作碼緩存來緩存函數。它的語法智能(syntax-wise)也非常糟糕,並且大多數 IDE 中的字元串高亮顯示功能完全不起作用。

雖然接受回調函數的函數功能十分強大,但是沒有一種好方法可以執行一次性回調函數,而無需執行一些非常笨拙的工作。使用 PHP V5.3,我們可以使用 lambda 函數以更規則的方法重新執行以上示例。


清單 3. 使用 lambda 函數用於回調的 quoteWords()
				  function quoteWords()  {       return array_map('quoteWordsHelper',              function ($string) {                  return preg_replace('/(\w)/','"$1"',$string);              });  }  

我們看到了定義這些函數的更規則的語法,這可以通過操作碼緩存來優化性能。我們也已經得到了改進的可讀性以及與字元串高亮顯示功能的兼容性。讓我們在此基礎上了解如何在 PHP 中使用閉包。





閉包

Lambda 函數本身並沒有添加以前不能執行的功能。正如我們所見,我們可以使用 create_function() 執行所有這項操作,儘管後果是使用更糟糕的語法並且性能更加不理想。但是它們仍然是拋棄型函數並且不維護任何類型的狀態,這限制了我們可以用它們執行的操作。因此出現了閉包並使 lambda 函數得到增強。

閉包是在它自己的環境中執行計算的函數,它有一個或多個綁定變數可以在調用函數時訪問。它們來自函數編程世界,其中涉及大量概念。閉包類似於 lambda 函數,但是在與定義閉包的外部環境中的變數進行交互方面更加智能。

讓我們看一看如何在 PHP 中定義閉包。清單 4 顯示了從外部環境導入變數並將其簡單地輸出到屏幕上的閉包示例。


清單 4. 簡單閉包示例
				  $string = "Hello World!";  $closure = function() use ($string) { echo $string; };    $closure();    Output:  Hello World!  

從外部環境中導入的變數是在閉包函數定義的 use 子句中指定的。默認情況下,它們是由值傳遞的,意味著如果要更新傳遞到閉包函數定義內的值,則不更新外部值。但是,我們可以通過在變數前放置 & 運算符來完成此操作,這種方法在函數定義中用於表示按引用傳遞。清單 5 顯示了這種方法的示例。


清單 5. 通過引用傳遞變數的閉包
				  $x = 1  $closure = function() use (&$x) { ++$x; }    echo $x . "\n";  $closure();  echo $x . "\n";  $closure();  echo $x . "\n";    Output:  1  2  3  

可以看到,閉包使用外部變數 $x 並在每次調用閉包時遞增該變數。我們可以將按值和按引用傳遞的變數輕鬆地混合到 use 子句中,並且可以順利地處理這些變數。我們也可以擁有直接返回閉包的函數,如清單 6 所示。在本例中,閉包的生命周期實際上比定義閉包的方法長。


清單 6. 函數返回的閉包
				  function getAppender($baseString)  {        return function($appendString) use ($baseString) { return $baseString .   $appendString; };  }  





閉包與對象

閉包不但是過程式編程的有用工具,而且是面向對象編程的有用工具。在這種情況下使用閉包與在類外部使用閉包實現的目的相同:包含在小範圍內綁定的特定函數。在對象外部與在對象內部使用一樣簡單。

在對象內定義時,非常方便的一點是閉包通過 $this 變數擁有對對象的完全訪問權,而無需顯式導入。清單 7 演示了該示例。


清單 7. 對象內的閉包
				  class Dog  {      private 
繼續學習 “PHP V5.3 中的新特性” 系列文章,了解 2008 年底即將發布的 PHP 5.3 版中的新特性及優秀功能。本系列的 第 1 部分 介紹了 PHP 5.3 中對面向對象編程及對象處理所做的更改。在第 2 部分中,我們將介紹閉包函數及 lambda 函數。這些函數專門用於簡化編程,讓您輕鬆地定義可以在許多不同環境中使用的拋棄型(throwaway)函數。

閉包函數和 lambda 函數絕對不是新出現的概念;它們均來自函數編程領域。函數編程 是一種編程風格,它將關注點從執行命令轉移到表達式計算。這些表達式是使用函數構成的,結合這些函數可以得到我們要查找的結果。這種編程風格最常用於學術目的,但是也可以在人工智慧與數學領域中見到,並且可以在用 Erlang、Haskell 及 Scheme 等語言編寫的商業應用程序中找到。

閉包 最初是在 20 世紀 60 年代作為 Scheme 的一部分開發的,Scheme 是最著名的函數編程語言之一。Lambda 函數和閉包通常出現在允許將函數處理為第一類值(First-class value)的語言中,這意味著函數可以動態創建並作為參數傳遞給其他語言。

從那時起,閉包及 lambda 函數已經找到了走出函數編程世界並進入 JavaScript、Python 和 Ruby 等語言的方法。JavaScript 是支持閉包和 lambda 函數的最常見語言之一。JavaScript 實際使用這些函數作為支持面向對象的編程方法,把函數嵌套到其他函數中以用作私有成員。清單 1 提供了 JavaScript 如何使用閉包的示例。


清單 1. 使用閉包構建 JavaScript 對象
				  var Example = function()  {       this.public = function()       {           return "This is a public method";       };         var private = function()       {           return "This is a private method";       };  };    Example.public()  // returns "This is a public method"   Example.private() // error - doesn't work  

如清單 1 中所示,Example 對象的成員函數被定義為閉包。由於私有方法作用於局部變數(與綁定到使用此關鍵字的 Example 對象的公共方法相反),因此從外部看不到它。

現在我們已經了解了這些概念的歷史,讓我們查看 PHP 中的 lambda 函數。lambda 函數的概念是閉包的基礎,並且提供了一種比 PHP 中已有的 create_function() 函數改進了很多的動態創建函數的方法。

Lambda 函數

Lambda 函數(或者通常所謂的 “匿名函數”)是可以隨時定義的簡單拋棄型函數,並且通常都與變數綁定。函數本身僅存在於定義函數的變數範圍內,因此當該變數超出範圍時,函數也超出範圍。lambda 函數的理念源於 20 世紀 30 年代的數學研究。它被稱為 lambda 演算,用於研究函數定義與應用程序以及遞歸概念。lambda 演算用於開發函數編程語言,例如 Lisp 和 Scheme。

對於大多數實例來說,尤其對於接受回調函數的許多 PHP 函數來說,Lambda 函數非常方便。array_map() 就是這樣一種函數,它允許我們遍曆數組並將回調函數應用到數組的每個元素上。在早期版本的 PHP 中,這些函數的最大問題是沒有一種清晰的方式定義回調函數;我們堅持使用以下三種解決方法的其中一種:

  1. 我們可以在代碼中的其他位置定義回調函數,因此我們知道它可用。這有些麻煩,因為它把調用的實現部分移到了其他位置,這樣做對於可讀性與可維護性極為不便,尤其是不打算在其他位置使用此函數時。
  2. 我們可以在同一個代碼塊中定義回調函數,但是使用一個名稱。雖然這樣做有助於把內容放在一起,但是需要在定義周圍添加 if 塊以避免名稱空間衝突。清單 2 展示了這種方法。


    清單 2. 在同一個代碼塊中定義指定的回調
    						  function quoteWords()  {       if (!function_exists ('quoteWordsHelper')) {           function quoteWordsHelper($string) {               return preg_replace('/(\w)/','"$1"',$string);           }        }        return array_map('quoteWordsHelper', $text);  }

  3. 我們可以使用 create_function()(從 V4 開始就是 PHP 的一部分)在運行時創建函數。雖然在功能上此函數執行了所需操作,但是它有一些缺點。一個主要缺點是,它在運行時而非編譯時編譯,不允許操作碼緩存來緩存函數。它的語法智能(syntax-wise)也非常糟糕,並且大多數 IDE 中的字元串高亮顯示功能完全不起作用。

雖然接受回調函數的函數功能十分強大,但是沒有一種好方法可以執行一次性回調函數,而無需執行一些非常笨拙的工作。使用 PHP V5.3,我們可以使用 lambda 函數以更規則的方法重新執行以上示例。


清單 3. 使用 lambda 函數用於回調的 quoteWords()
				  function quoteWords()  {       return array_map('quoteWordsHelper',              function ($string) {                  return preg_replace('/(\w)/','"$1"',$string);              });  }  

我們看到了定義這些函數的更規則的語法,這可以通過操作碼緩存來優化性能。我們也已經得到了改進的可讀性以及與字元串高亮顯示功能的兼容性。讓我們在此基礎上了解如何在 PHP 中使用閉包。





閉包

Lambda 函數本身並沒有添加以前不能執行的功能。正如我們所見,我們可以使用 create_function() 執行所有這項操作,儘管後果是使用更糟糕的語法並且性能更加不理想。但是它們仍然是拋棄型函數並且不維護任何類型的狀態,這限制了我們可以用它們執行的操作。因此出現了閉包並使 lambda 函數得到增強。

閉包是在它自己的環境中執行計算的函數,它有一個或多個綁定變數可以在調用函數時訪問。它們來自函數編程世界,其中涉及大量概念。閉包類似於 lambda 函數,但是在與定義閉包的外部環境中的變數進行交互方面更加智能。

讓我們看一看如何在 PHP 中定義閉包。清單 4 顯示了從外部環境導入變數並將其簡單地輸出到屏幕上的閉包示例。


清單 4. 簡單閉包示例
				  $string = "Hello World!";  $closure = function() use ($string) { echo $string; };    $closure();    Output:  Hello World!  

從外部環境中導入的變數是在閉包函數定義的 use 子句中指定的。默認情況下,它們是由值傳遞的,意味著如果要更新傳遞到閉包函數定義內的值,則不更新外部值。但是,我們可以通過在變數前放置 & 運算符來完成此操作,這種方法在函數定義中用於表示按引用傳遞。清單 5 顯示了這種方法的示例。


清單 5. 通過引用傳遞變數的閉包
				  $x = 1  $closure = function() use (&$x) { ++$x; }    echo $x . "\n";  $closure();  echo $x . "\n";  $closure();  echo $x . "\n";    Output:  1  2  3  

可以看到,閉包使用外部變數 $x 並在每次調用閉包時遞增該變數。我們可以將按值和按引用傳遞的變數輕鬆地混合到 use 子句中,並且可以順利地處理這些變數。我們也可以擁有直接返回閉包的函數,如清單 6 所示。在本例中,閉包的生命周期實際上比定義閉包的方法長。


清單 6. 函數返回的閉包
				  function getAppender($baseString)  {        return function($appendString) use ($baseString) { return $baseString .   $appendString; };  }  





閉包與對象

閉包不但是過程式編程的有用工具,而且是面向對象編程的有用工具。在這種情況下使用閉包與在類外部使用閉包實現的目的相同:包含在小範圍內綁定的特定函數。在對象外部與在對象內部使用一樣簡單。

在對象內定義時,非常方便的一點是閉包通過 $this 變數擁有對對象的完全訪問權,而無需顯式導入。清單 7 演示了該示例。


清單 7. 對象內的閉包
___FCKpd___6

在這裡,我們在閉包內顯式使用提供給 greet() 方法的歡迎詞,閉包在該方法內定義。我們還在閉包內獲取狗的顏色和名字,傳遞到構造函數中並存儲到對象中。

在類中定義的閉包基本上與在對象外部定義的閉包相同。惟一的不同之處在於通過 $this 變數自動導入對象。我們可以通過將閉包定義為靜態閉包禁用此行為。


清單 8. 靜態閉包
				  class House  {       public function paint($color)       {           return static function() use ($color) { echo "Painting the   house $color...."; };       }  }    $house = new House();  $house->paint('red');    Output:  Painting the house red....  

此示例類似於清單 5 中定義的 Dog 類。最大的差別是在閉包內不使用對象的任何屬性,因為它被定義為靜態類。

在對象內使用靜態閉包與使用非靜態閉包相比的最大優點是節省內存。由於無需將對象導入閉包中,因此可以節省大量內存,尤其是在擁有許多不需要此功能的閉包時。

針對對象的另一個優點是添加名為 __invoke() 的魔術方法,此方法允許對象本身被調用為閉包。如果定義了此方法,則在該上下文中調用對象時將使用此方法。清單 9 演示了示例。


清單 9. 使用 __invoke() 方法
				    class Dog  {      public function __invoke()      {           echo "I am a dog!";      }  }    $dog = new Dog();  $dog();  

將清單 9 中所示的對象引用調用為變數將自動調用 __invoke() 魔術方法,使類本身用作閉包。

閉包可以很好地與面向對象的代碼以及面向過程的代碼整合。讓我們看一看閉包如何與 PHP 的強大 Reflection API 交互。





閉包與反射

PHP 有一個有用的反射 API,它允許我們對類、介面、函數和方法執行反向工程。按照設計,閉包是匿名函數,這意味著它們不顯示在反射 API 中。

但是,新 getClosure() 方法已經添加到 PHP 中的 ReflectionMethod 和 ReflectionFunction 類中,可以從指定的函數或方法動態創建閉包。它在此上下文中用作宏,通過閉包調用函數方法將在定義它的上下文中執行函數調用。清單 10 顯示了此方法如何運行。


清單 10. 使用 getClosure() 方法
				  class Counter  {        private $x;          public function __construct()        {             $this->x = 0;        }          public function increment()        {             $this->x++;        }          public function currentValue()        {             echo $this->x . "\n";        }  }  $class = new ReflectionClass('Counter');  $method = $class->getMethod('currentValue');  $closure = $method->getClosure()  $closure();  $class->increment();  $closure();  Output:  0  1  

這種方法的一個有趣的副作用是允許通過閉包訪問類的私有成員和受保護成員,這有利於對類執行單元測試。清單 11 展示了對類的私有方法的訪問。


清單 11. 訪問類的私有方法
				  class Example   {        ....       private static function secret()        {             echo "I'm an method that's hiding!";        }        ...  }     $class = new ReflectionClass('Example');  $method = $class->getMethod('secret');  $closure = $method->getClosure()  $closure();  Output:  I'm an method that's hiding!  

此外,可以使用反射 API 來內省(introspect)閉包本身,如清單 12 所示。只需將對閉包的變數引用傳遞到 ReflectionMethod 類的構造函數中。


清單 12. 使用反射 API 內省閉包
				  $closure = function ($x, $y = 1) {};   $m = new ReflectionMethod($closure);   Reflection::export ($m);  Output:  Method [ <internal> public method __invoke ] {    - Parameters [2] {      Parameter #0 [ <required> $x ]      Parameter #1 [ <optional> $y ]    }  }  

關於向後兼容性值得注意的一點是,PHP 引擎現在保留類名 Closure 並用於存儲閉包,因此使用該名稱的所有類都需要重命名。

正如我們所見,反射 API 能夠通過現有函數和方法動態創建閉包,從而為閉包提供強大的支持。它們還可以像普通函數一樣內省到閉包中。





為什麼使用閉包?

如在 lambda 函數的示例中所示,閉包的最明顯用法之一是少數 PHP 函數接受回調函數作為參數。但是,在需要把邏輯封裝到自己的範圍內的情況下,閉包會十分有用。重構舊代碼以進行簡化並提高可讀性就是這樣一個例子。查看以下示例,該示例顯示了在運行一些 SQL 查詢時使用的記錄程序。


清單 13. 記錄 SQL 查詢的代碼
				  $db = mysqli_connect("server","user","pass");   Logger::log('debug','database','Connected to database');   $db->query('insert into parts (part, description) values ('Hammer','Pounds nails');   Logger::log('debug','database','Insert Hammer into to parts table');   $db->query('insert into parts (part, description) values         ('Drill','Puts holes in wood');  Logger::log('debug','database','Insert Drill into to parts table');   $db->query('insert into parts (part, description) values ('Saw','Cuts wood');   Logger::log('debug','database','Insert Saw into to parts table');   

從清單 13 中可以看出執行操作的重複程度。對 Logger::log() 執行的每次調用都有相同的前兩個實參。為了解決此問題,我們可以把該方法調用放入閉包並轉而針對該閉包執行調用。得到的代碼如下所示:


清單 14. 記錄 SQL 查詢的重構代碼
				  $logdb = function ($string) { Logger::log('debug','database',$string); };  $db = mysqli_connect("server","user","pass");   $logdb('Connected to database');   $db->query('insert into parts (part, description) values ('Hammer','Pounds nails');   $logdb('Insert Hammer into to parts table');   $db->query('insert into parts (part, description) values          ('Drill','Puts holes in wood');  $logdb('Insert Drill into to parts table');   $db->query('insert into parts (part, description) values ('Saw','Cuts wood');   $logdb('Insert Saw into to parts table');   

代碼不但在外觀上更加規則,而且更易於更改 SQL 查詢日誌的日誌級別,因為現在只需要在一個位置進行更改。





結束語

本文演示了閉包在 PHP V5.3 代碼中作為函數編程構造時多麼有用。我們討論了 lambda 函數及閉包與這些函數相比的優點。對象與閉包可以很好地結合使用,比如我們在面向對象的代碼內對閉包的特殊處理。我們看到了如何使用反射 API 創建動態閉包,以及如何內省現有的閉包。(責任編輯:A6)

name; protected
繼續學習 “PHP V5.3 中的新特性” 系列文章,了解 2008 年底即將發布的 PHP 5.3 版中的新特性及優秀功能。本系列的 第 1 部分 介紹了 PHP 5.3 中對面向對象編程及對象處理所做的更改。在第 2 部分中,我們將介紹閉包函數及 lambda 函數。這些函數專門用於簡化編程,讓您輕鬆地定義可以在許多不同環境中使用的拋棄型(throwaway)函數。

閉包函數和 lambda 函數絕對不是新出現的概念;它們均來自函數編程領域。函數編程 是一種編程風格,它將關注點從執行命令轉移到表達式計算。這些表達式是使用函數構成的,結合這些函數可以得到我們要查找的結果。這種編程風格最常用於學術目的,但是也可以在人工智慧與數學領域中見到,並且可以在用 Erlang、Haskell 及 Scheme 等語言編寫的商業應用程序中找到。

閉包 最初是在 20 世紀 60 年代作為 Scheme 的一部分開發的,Scheme 是最著名的函數編程語言之一。Lambda 函數和閉包通常出現在允許將函數處理為第一類值(First-class value)的語言中,這意味著函數可以動態創建並作為參數傳遞給其他語言。

從那時起,閉包及 lambda 函數已經找到了走出函數編程世界並進入 JavaScript、Python 和 Ruby 等語言的方法。JavaScript 是支持閉包和 lambda 函數的最常見語言之一。JavaScript 實際使用這些函數作為支持面向對象的編程方法,把函數嵌套到其他函數中以用作私有成員。清單 1 提供了 JavaScript 如何使用閉包的示例。


清單 1. 使用閉包構建 JavaScript 對象
				  var Example = function()  {       this.public = function()       {           return "This is a public method";       };         var private = function()       {           return "This is a private method";       };  };    Example.public()  // returns "This is a public method"   Example.private() // error - doesn't work  

如清單 1 中所示,Example 對象的成員函數被定義為閉包。由於私有方法作用於局部變數(與綁定到使用此關鍵字的 Example 對象的公共方法相反),因此從外部看不到它。

現在我們已經了解了這些概念的歷史,讓我們查看 PHP 中的 lambda 函數。lambda 函數的概念是閉包的基礎,並且提供了一種比 PHP 中已有的 create_function() 函數改進了很多的動態創建函數的方法。

Lambda 函數

Lambda 函數(或者通常所謂的 “匿名函數”)是可以隨時定義的簡單拋棄型函數,並且通常都與變數綁定。函數本身僅存在於定義函數的變數範圍內,因此當該變數超出範圍時,函數也超出範圍。lambda 函數的理念源於 20 世紀 30 年代的數學研究。它被稱為 lambda 演算,用於研究函數定義與應用程序以及遞歸概念。lambda 演算用於開發函數編程語言,例如 Lisp 和 Scheme。

對於大多數實例來說,尤其對於接受回調函數的許多 PHP 函數來說,Lambda 函數非常方便。array_map() 就是這樣一種函數,它允許我們遍曆數組並將回調函數應用到數組的每個元素上。在早期版本的 PHP 中,這些函數的最大問題是沒有一種清晰的方式定義回調函數;我們堅持使用以下三種解決方法的其中一種:

  1. 我們可以在代碼中的其他位置定義回調函數,因此我們知道它可用。這有些麻煩,因為它把調用的實現部分移到了其他位置,這樣做對於可讀性與可維護性極為不便,尤其是不打算在其他位置使用此函數時。
  2. 我們可以在同一個代碼塊中定義回調函數,但是使用一個名稱。雖然這樣做有助於把內容放在一起,但是需要在定義周圍添加 if 塊以避免名稱空間衝突。清單 2 展示了這種方法。


    清單 2. 在同一個代碼塊中定義指定的回調
    						  function quoteWords()  {       if (!function_exists ('quoteWordsHelper')) {           function quoteWordsHelper($string) {               return preg_replace('/(\w)/','"$1"',$string);           }        }        return array_map('quoteWordsHelper', $text);  }

  3. 我們可以使用 create_function()(從 V4 開始就是 PHP 的一部分)在運行時創建函數。雖然在功能上此函數執行了所需操作,但是它有一些缺點。一個主要缺點是,它在運行時而非編譯時編譯,不允許操作碼緩存來緩存函數。它的語法智能(syntax-wise)也非常糟糕,並且大多數 IDE 中的字元串高亮顯示功能完全不起作用。

雖然接受回調函數的函數功能十分強大,但是沒有一種好方法可以執行一次性回調函數,而無需執行一些非常笨拙的工作。使用 PHP V5.3,我們可以使用 lambda 函數以更規則的方法重新執行以上示例。


清單 3. 使用 lambda 函數用於回調的 quoteWords()
				  function quoteWords()  {       return array_map('quoteWordsHelper',              function ($string) {                  return preg_replace('/(\w)/','"$1"',$string);              });  }  

我們看到了定義這些函數的更規則的語法,這可以通過操作碼緩存來優化性能。我們也已經得到了改進的可讀性以及與字元串高亮顯示功能的兼容性。讓我們在此基礎上了解如何在 PHP 中使用閉包。





閉包

Lambda 函數本身並沒有添加以前不能執行的功能。正如我們所見,我們可以使用 create_function() 執行所有這項操作,儘管後果是使用更糟糕的語法並且性能更加不理想。但是它們仍然是拋棄型函數並且不維護任何類型的狀態,這限制了我們可以用它們執行的操作。因此出現了閉包並使 lambda 函數得到增強。

閉包是在它自己的環境中執行計算的函數,它有一個或多個綁定變數可以在調用函數時訪問。它們來自函數編程世界,其中涉及大量概念。閉包類似於 lambda 函數,但是在與定義閉包的外部環境中的變數進行交互方面更加智能。

讓我們看一看如何在 PHP 中定義閉包。清單 4 顯示了從外部環境導入變數並將其簡單地輸出到屏幕上的閉包示例。


清單 4. 簡單閉包示例
				  $string = "Hello World!";  $closure = function() use ($string) { echo $string; };    $closure();    Output:  Hello World!  

從外部環境中導入的變數是在閉包函數定義的 use 子句中指定的。默認情況下,它們是由值傳遞的,意味著如果要更新傳遞到閉包函數定義內的值,則不更新外部值。但是,我們可以通過在變數前放置 & 運算符來完成此操作,這種方法在函數定義中用於表示按引用傳遞。清單 5 顯示了這種方法的示例。


清單 5. 通過引用傳遞變數的閉包
				  $x = 1  $closure = function() use (&$x) { ++$x; }    echo $x . "\n";  $closure();  echo $x . "\n";  $closure();  echo $x . "\n";    Output:  1  2  3  

可以看到,閉包使用外部變數 $x 並在每次調用閉包時遞增該變數。我們可以將按值和按引用傳遞的變數輕鬆地混合到 use 子句中,並且可以順利地處理這些變數。我們也可以擁有直接返回閉包的函數,如清單 6 所示。在本例中,閉包的生命周期實際上比定義閉包的方法長。


清單 6. 函數返回的閉包
				  function getAppender($baseString)  {        return function($appendString) use ($baseString) { return $baseString .   $appendString; };  }  





閉包與對象

閉包不但是過程式編程的有用工具,而且是面向對象編程的有用工具。在這種情況下使用閉包與在類外部使用閉包實現的目的相同:包含在小範圍內綁定的特定函數。在對象外部與在對象內部使用一樣簡單。

在對象內定義時,非常方便的一點是閉包通過 $this 變數擁有對對象的完全訪問權,而無需顯式導入。清單 7 演示了該示例。


清單 7. 對象內的閉包
___FCKpd___6

在這裡,我們在閉包內顯式使用提供給 greet() 方法的歡迎詞,閉包在該方法內定義。我們還在閉包內獲取狗的顏色和名字,傳遞到構造函數中並存儲到對象中。

在類中定義的閉包基本上與在對象外部定義的閉包相同。惟一的不同之處在於通過 $this 變數自動導入對象。我們可以通過將閉包定義為靜態閉包禁用此行為。


清單 8. 靜態閉包
___FCKpd___7

此示例類似於清單 5 中定義的 Dog 類。最大的差別是在閉包內不使用對象的任何屬性,因為它被定義為靜態類。

在對象內使用靜態閉包與使用非靜態閉包相比的最大優點是節省內存。由於無需將對象導入閉包中,因此可以節省大量內存,尤其是在擁有許多不需要此功能的閉包時。

針對對象的另一個優點是添加名為 __invoke() 的魔術方法,此方法允許對象本身被調用為閉包。如果定義了此方法,則在該上下文中調用對象時將使用此方法。清單 9 演示了示例。


清單 9. 使用 __invoke() 方法
___FCKpd___8

將清單 9 中所示的對象引用調用為變數將自動調用 __invoke() 魔術方法,使類本身用作閉包。

閉包可以很好地與面向對象的代碼以及面向過程的代碼整合。讓我們看一看閉包如何與 PHP 的強大 Reflection API 交互。





閉包與反射

PHP 有一個有用的反射 API,它允許我們對類、介面、函數和方法執行反向工程。按照設計,閉包是匿名函數,這意味著它們不顯示在反射 API 中。

但是,新 getClosure() 方法已經添加到 PHP 中的 ReflectionMethod 和 ReflectionFunction 類中,可以從指定的函數或方法動態創建閉包。它在此上下文中用作宏,通過閉包調用函數方法將在定義它的上下文中執行函數調用。清單 10 顯示了此方法如何運行。


清單 10. 使用 getClosure() 方法
___FCKpd___9

這種方法的一個有趣的副作用是允許通過閉包訪問類的私有成員和受保護成員,這有利於對類執行單元測試。清單 11 展示了對類的私有方法的訪問。


清單 11. 訪問類的私有方法
___FCKpd___10

此外,可以使用反射 API 來內省(introspect)閉包本身,如清單 12 所示。只需將對閉包的變數引用傳遞到 ReflectionMethod 類的構造函數中。


清單 12. 使用反射 API 內省閉包
___FCKpd___11

關於向後兼容性值得注意的一點是,PHP 引擎現在保留類名 Closure 並用於存儲閉包,因此使用該名稱的所有類都需要重命名。

正如我們所見,反射 API 能夠通過現有函數和方法動態創建閉包,從而為閉包提供強大的支持。它們還可以像普通函數一樣內省到閉包中。





為什麼使用閉包?

如在 lambda 函數的示例中所示,閉包的最明顯用法之一是少數 PHP 函數接受回調函數作為參數。但是,在需要把邏輯封裝到自己的範圍內的情況下,閉包會十分有用。重構舊代碼以進行簡化並提高可讀性就是這樣一個例子。查看以下示例,該示例顯示了在運行一些 SQL 查詢時使用的記錄程序。


清單 13. 記錄 SQL 查詢的代碼
___FCKpd___12

從清單 13 中可以看出執行操作的重複程度。對 Logger::log() 執行的每次調用都有相同的前兩個實參。為了解決此問題,我們可以把該方法調用放入閉包並轉而針對該閉包執行調用。得到的代碼如下所示:


清單 14. 記錄 SQL 查詢的重構代碼
___FCKpd___13

代碼不但在外觀上更加規則,而且更易於更改 SQL 查詢日誌的日誌級別,因為現在只需要在一個位置進行更改。





結束語

本文演示了閉包在 PHP V5.3 代碼中作為函數編程構造時多麼有用。我們討論了 lambda 函數及閉包與這些函數相比的優點。對象與閉包可以很好地結合使用,比如我們在面向對象的代碼內對閉包的特殊處理。我們看到了如何使用反射 API 創建動態閉包,以及如何內省現有的閉包。(責任編輯:A6)

color; public function __construct($name, $color) { $this->_name = $name; $this->_color = $color; } public function greet($greeting) { return function() use ($greeting) { echo "$greeting, I am a {$this->_color} dog named {$this->_name}."; }; } } $dog = new Dog("Rover","red"); $dog->greet("Hello"); Output: Hello, I am a red dog named Rover.

在這裡,我們在閉包內顯式使用提供給 greet() 方法的歡迎詞,閉包在該方法內定義。我們還在閉包內獲取狗的顏色和名字,傳遞到構造函數中並存儲到對象中。

在類中定義的閉包基本上與在對象外部定義的閉包相同。惟一的不同之處在於通過 $this 變數自動導入對象。我們可以通過將閉包定義為靜態閉包禁用此行為。


清單 8. 靜態閉包
___FCKpd___7

此示例類似於清單 5 中定義的 Dog 類。最大的差別是在閉包內不使用對象的任何屬性,因為它被定義為靜態類。

在對象內使用靜態閉包與使用非靜態閉包相比的最大優點是節省內存。由於無需將對象導入閉包中,因此可以節省大量內存,尤其是在擁有許多不需要此功能的閉包時。

針對對象的另一個優點是添加名為 __invoke() 的魔術方法,此方法允許對象本身被調用為閉包。如果定義了此方法,則在該上下文中調用對象時將使用此方法。清單 9 演示了示例。


清單 9. 使用 __invoke() 方法
___FCKpd___8

將清單 9 中所示的對象引用調用為變數將自動調用 __invoke() 魔術方法,使類本身用作閉包。

閉包可以很好地與面向對象的代碼以及面向過程的代碼整合。讓我們看一看閉包如何與 PHP 的強大 Reflection API 交互。





閉包與反射

PHP 有一個有用的反射 API,它允許我們對類、介面、函數和方法執行反向工程。按照設計,閉包是匿名函數,這意味著它們不顯示在反射 API 中。

但是,新 getClosure() 方法已經添加到 PHP 中的 ReflectionMethod 和 ReflectionFunction 類中,可以從指定的函數或方法動態創建閉包。它在此上下文中用作宏,通過閉包調用函數方法將在定義它的上下文中執行函數調用。清單 10 顯示了此方法如何運行。


清單 10. 使用 getClosure() 方法
___FCKpd___9

這種方法的一個有趣的副作用是允許通過閉包訪問類的私有成員和受保護成員,這有利於對類執行單元測試。清單 11 展示了對類的私有方法的訪問。


清單 11. 訪問類的私有方法
___FCKpd___10

此外,可以使用反射 API 來內省(introspect)閉包本身,如清單 12 所示。只需將對閉包的變數引用傳遞到 ReflectionMethod 類的構造函數中。


清單 12. 使用反射 API 內省閉包
___FCKpd___11

關於向後兼容性值得注意的一點是,PHP 引擎現在保留類名 Closure 並用於存儲閉包,因此使用該名稱的所有類都需要重命名。

正如我們所見,反射 API 能夠通過現有函數和方法動態創建閉包,從而為閉包提供強大的支持。它們還可以像普通函數一樣內省到閉包中。





為什麼使用閉包?

如在 lambda 函數的示例中所示,閉包的最明顯用法之一是少數 PHP 函數接受回調函數作為參數。但是,在需要把邏輯封裝到自己的範圍內的情況下,閉包會十分有用。重構舊代碼以進行簡化並提高可讀性就是這樣一個例子。查看以下示例,該示例顯示了在運行一些 SQL 查詢時使用的記錄程序。


清單 13. 記錄 SQL 查詢的代碼
___FCKpd___12

從清單 13 中可以看出執行操作的重複程度。對 Logger::log() 執行的每次調用都有相同的前兩個實參。為了解決此問題,我們可以把該方法調用放入閉包並轉而針對該閉包執行調用。得到的代碼如下所示:


清單 14. 記錄 SQL 查詢的重構代碼
___FCKpd___13

代碼不但在外觀上更加規則,而且更易於更改 SQL 查詢日誌的日誌級別,因為現在只需要在一個位置進行更改。





結束語

本文演示了閉包在 PHP V5.3 代碼中作為函數編程構造時多麼有用。我們討論了 lambda 函數及閉包與這些函數相比的優點。對象與閉包可以很好地結合使用,比如我們在面向對象的代碼內對閉包的特殊處理。我們看到了如何使用反射 API 創建動態閉包,以及如何內省現有的閉包。(責任編輯:A6)



[火星人 ] PHP V5.3 中的新特性,第 2 部分: 閉包及 lambda 函數已經有1109次圍觀

http://coctec.com/docs/linux/show-post-68988.html