繼續學習 “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 如何使用閉包的示例。
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 中,這些函數的最大問題是沒有一種清晰的方式定義回調函數;我們堅持使用以下三種解決方法的其中一種:
function quoteWords() { if (!function_exists ('quoteWordsHelper')) { function quoteWordsHelper($string) { return preg_replace('/(\w)/','"$1"',$string); } } return array_map('quoteWordsHelper', $text); } |
雖然接受回調函數的函數功能十分強大,但是沒有一種好方法可以執行一次性回調函數,而無需執行一些非常笨拙的工作。使用 PHP V5.3,我們可以使用 lambda 函數以更規則的方法重新執行以上示例。
function quoteWords() { return array_map('quoteWordsHelper', function ($string) { return preg_replace('/(\w)/','"$1"',$string); }); } |
我們看到了定義這些函數的更規則的語法,這可以通過操作碼緩存來優化性能。我們也已經得到了改進的可讀性以及與字元串高亮顯示功能的兼容性。讓我們在此基礎上了解如何在 PHP 中使用閉包。
![]() ![]() |
閉包
Lambda 函數本身並沒有添加以前不能執行的功能。正如我們所見,我們可以使用 create_function() 執行所有這項操作,儘管後果是使用更糟糕的語法並且性能更加不理想。但是它們仍然是拋棄型函數並且不維護任何類型的狀態,這限制了我們可以用它們執行的操作。因此出現了閉包並使 lambda 函數得到增強。
閉包是在它自己的環境中執行計算的函數,它有一個或多個綁定變數可以在調用函數時訪問。它們來自函數編程世界,其中涉及大量概念。閉包類似於 lambda 函數,但是在與定義閉包的外部環境中的變數進行交互方面更加智能。
讓我們看一看如何在 PHP 中定義閉包。清單 4 顯示了從外部環境導入變數並將其簡單地輸出到屏幕上的閉包示例。
$string = "Hello World!"; $closure = function() use ($string) { echo $string; }; $closure(); Output: Hello World! |
從外部環境中導入的變數是在閉包函數定義的 use 子句中指定的。默認情況下,它們是由值傳遞的,意味著如果要更新傳遞到閉包函數定義內的值,則不更新外部值。但是,我們可以通過在變數前放置 & 運算符來完成此操作,這種方法在函數定義中用於表示按引用傳遞。清單 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 所示。在本例中,閉包的生命周期實際上比定義閉包的方法長。
function getAppender($baseString) { return function($appendString) use ($baseString) { return $baseString . $appendString; }; } |
![]() ![]() |
閉包與對象
閉包不但是過程式編程的有用工具,而且是面向對象編程的有用工具。在這種情況下使用閉包與在類外部使用閉包實現的目的相同:包含在小範圍內綁定的特定函數。在對象外部與在對象內部使用一樣簡單。
在對象內定義時,非常方便的一點是閉包通過 $this 變數擁有對對象的完全訪問權,而無需顯式導入。清單 7 演示了該示例。
class Dog { private繼續學習 “PHP V5.3 中的新特性” 系列文章,了解 2008 年底即將發布的 PHP 5.3 版中的新特性及優秀功能。本系列的 第 1 部分 介紹了 PHP 5.3 中對面向對象編程及對象處理所做的更改。在第 2 部分中,我們將介紹閉包函數及 lambda 函數。這些函數專門用於簡化編程,讓您輕鬆地定義可以在許多不同環境中使用的拋棄型(throwaway)函數。
如清單 1 中所示,Example 對象的成員函數被定義為閉包。由於私有方法作用於局部變數(與綁定到使用此關鍵字的 Example 對象的公共方法相反),因此從外部看不到它。 現在我們已經了解了這些概念的歷史,讓我們查看 PHP 中的 lambda 函數。lambda 函數的概念是閉包的基礎,並且提供了一種比 PHP 中已有的 create_function() 函數改進了很多的動態創建函數的方法。 Lambda 函數 Lambda 函數(或者通常所謂的 “匿名函數”)是可以隨時定義的簡單拋棄型函數,並且通常都與變數綁定。函數本身僅存在於定義函數的變數範圍內,因此當該變數超出範圍時,函數也超出範圍。lambda 函數的理念源於 20 世紀 30 年代的數學研究。它被稱為 lambda 演算,用於研究函數定義與應用程序以及遞歸概念。lambda 演算用於開發函數編程語言,例如 Lisp 和 Scheme。 對於大多數實例來說,尤其對於接受回調函數的許多 PHP 函數來說,Lambda 函數非常方便。array_map() 就是這樣一種函數,它允許我們遍曆數組並將回調函數應用到數組的每個元素上。在早期版本的 PHP 中,這些函數的最大問題是沒有一種清晰的方式定義回調函數;我們堅持使用以下三種解決方法的其中一種:
雖然接受回調函數的函數功能十分強大,但是沒有一種好方法可以執行一次性回調函數,而無需執行一些非常笨拙的工作。使用 PHP V5.3,我們可以使用 lambda 函數以更規則的方法重新執行以上示例。 清單 3. 使用 lambda 函數用於回調的 quoteWords()
我們看到了定義這些函數的更規則的語法,這可以通過操作碼緩存來優化性能。我們也已經得到了改進的可讀性以及與字元串高亮顯示功能的兼容性。讓我們在此基礎上了解如何在 PHP 中使用閉包。
閉包 Lambda 函數本身並沒有添加以前不能執行的功能。正如我們所見,我們可以使用 create_function() 執行所有這項操作,儘管後果是使用更糟糕的語法並且性能更加不理想。但是它們仍然是拋棄型函數並且不維護任何類型的狀態,這限制了我們可以用它們執行的操作。因此出現了閉包並使 lambda 函數得到增強。 閉包是在它自己的環境中執行計算的函數,它有一個或多個綁定變數可以在調用函數時訪問。它們來自函數編程世界,其中涉及大量概念。閉包類似於 lambda 函數,但是在與定義閉包的外部環境中的變數進行交互方面更加智能。 讓我們看一看如何在 PHP 中定義閉包。清單 4 顯示了從外部環境導入變數並將其簡單地輸出到屏幕上的閉包示例。 清單 4. 簡單閉包示例
從外部環境中導入的變數是在閉包函數定義的 use 子句中指定的。默認情況下,它們是由值傳遞的,意味著如果要更新傳遞到閉包函數定義內的值,則不更新外部值。但是,我們可以通過在變數前放置 & 運算符來完成此操作,這種方法在函數定義中用於表示按引用傳遞。清單 5 顯示了這種方法的示例。 清單 5. 通過引用傳遞變數的閉包
可以看到,閉包使用外部變數 $x 並在每次調用閉包時遞增該變數。我們可以將按值和按引用傳遞的變數輕鬆地混合到 use 子句中,並且可以順利地處理這些變數。我們也可以擁有直接返回閉包的函數,如清單 6 所示。在本例中,閉包的生命周期實際上比定義閉包的方法長。 清單 6. 函數返回的閉包
閉包與對象 閉包不但是過程式編程的有用工具,而且是面向對象編程的有用工具。在這種情況下使用閉包與在類外部使用閉包實現的目的相同:包含在小範圍內綁定的特定函數。在對象外部與在對象內部使用一樣簡單。 在對象內定義時,非常方便的一點是閉包通過 $this 變數擁有對對象的完全訪問權,而無需顯式導入。清單 7 演示了該示例。 清單 7. 對象內的閉包
在這裡,我們在閉包內顯式使用提供給 greet() 方法的歡迎詞,閉包在該方法內定義。我們還在閉包內獲取狗的顏色和名字,傳遞到構造函數中並存儲到對象中。 在類中定義的閉包基本上與在對象外部定義的閉包相同。惟一的不同之處在於通過 $this 變數自動導入對象。我們可以通過將閉包定義為靜態閉包禁用此行為。 清單 8. 靜態閉包
此示例類似於清單 5 中定義的 Dog 類。最大的差別是在閉包內不使用對象的任何屬性,因為它被定義為靜態類。 在對象內使用靜態閉包與使用非靜態閉包相比的最大優點是節省內存。由於無需將對象導入閉包中,因此可以節省大量內存,尤其是在擁有許多不需要此功能的閉包時。 針對對象的另一個優點是添加名為 __invoke() 的魔術方法,此方法允許對象本身被調用為閉包。如果定義了此方法,則在該上下文中調用對象時將使用此方法。清單 9 演示了示例。 清單 9. 使用 __invoke() 方法
將清單 9 中所示的對象引用調用為變數將自動調用 __invoke() 魔術方法,使類本身用作閉包。 閉包可以很好地與面向對象的代碼以及面向過程的代碼整合。讓我們看一看閉包如何與 PHP 的強大 Reflection API 交互。
閉包與反射 PHP 有一個有用的反射 API,它允許我們對類、介面、函數和方法執行反向工程。按照設計,閉包是匿名函數,這意味著它們不顯示在反射 API 中。 但是,新 getClosure() 方法已經添加到 PHP 中的 ReflectionMethod 和 ReflectionFunction 類中,可以從指定的函數或方法動態創建閉包。它在此上下文中用作宏,通過閉包調用函數方法將在定義它的上下文中執行函數調用。清單 10 顯示了此方法如何運行。 清單 10. 使用 getClosure() 方法
這種方法的一個有趣的副作用是允許通過閉包訪問類的私有成員和受保護成員,這有利於對類執行單元測試。清單 11 展示了對類的私有方法的訪問。 清單 11. 訪問類的私有方法
此外,可以使用反射 API 來內省(introspect)閉包本身,如清單 12 所示。只需將對閉包的變數引用傳遞到 ReflectionMethod 類的構造函數中。 清單 12. 使用反射 API 內省閉包
關於向後兼容性值得注意的一點是,PHP 引擎現在保留類名 Closure 並用於存儲閉包,因此使用該名稱的所有類都需要重命名。 正如我們所見,反射 API 能夠通過現有函數和方法動態創建閉包,從而為閉包提供強大的支持。它們還可以像普通函數一樣內省到閉包中。
為什麼使用閉包? 如在 lambda 函數的示例中所示,閉包的最明顯用法之一是少數 PHP 函數接受回調函數作為參數。但是,在需要把邏輯封裝到自己的範圍內的情況下,閉包會十分有用。重構舊代碼以進行簡化並提高可讀性就是這樣一個例子。查看以下示例,該示例顯示了在運行一些 SQL 查詢時使用的記錄程序。 清單 13. 記錄 SQL 查詢的代碼
從清單 13 中可以看出執行操作的重複程度。對 Logger::log() 執行的每次調用都有相同的前兩個實參。為了解決此問題,我們可以把該方法調用放入閉包並轉而針對該閉包執行調用。得到的代碼如下所示: 清單 14. 記錄 SQL 查詢的重構代碼
代碼不但在外觀上更加規則,而且更易於更改 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 對象
如清單 1 中所示,Example 對象的成員函數被定義為閉包。由於私有方法作用於局部變數(與綁定到使用此關鍵字的 Example 對象的公共方法相反),因此從外部看不到它。 現在我們已經了解了這些概念的歷史,讓我們查看 PHP 中的 lambda 函數。lambda 函數的概念是閉包的基礎,並且提供了一種比 PHP 中已有的 create_function() 函數改進了很多的動態創建函數的方法。 Lambda 函數 Lambda 函數(或者通常所謂的 “匿名函數”)是可以隨時定義的簡單拋棄型函數,並且通常都與變數綁定。函數本身僅存在於定義函數的變數範圍內,因此當該變數超出範圍時,函數也超出範圍。lambda 函數的理念源於 20 世紀 30 年代的數學研究。它被稱為 lambda 演算,用於研究函數定義與應用程序以及遞歸概念。lambda 演算用於開發函數編程語言,例如 Lisp 和 Scheme。 對於大多數實例來說,尤其對於接受回調函數的許多 PHP 函數來說,Lambda 函數非常方便。array_map() 就是這樣一種函數,它允許我們遍曆數組並將回調函數應用到數組的每個元素上。在早期版本的 PHP 中,這些函數的最大問題是沒有一種清晰的方式定義回調函數;我們堅持使用以下三種解決方法的其中一種:
雖然接受回調函數的函數功能十分強大,但是沒有一種好方法可以執行一次性回調函數,而無需執行一些非常笨拙的工作。使用 PHP V5.3,我們可以使用 lambda 函數以更規則的方法重新執行以上示例。 清單 3. 使用 lambda 函數用於回調的 quoteWords()
我們看到了定義這些函數的更規則的語法,這可以通過操作碼緩存來優化性能。我們也已經得到了改進的可讀性以及與字元串高亮顯示功能的兼容性。讓我們在此基礎上了解如何在 PHP 中使用閉包。
閉包 Lambda 函數本身並沒有添加以前不能執行的功能。正如我們所見,我們可以使用 create_function() 執行所有這項操作,儘管後果是使用更糟糕的語法並且性能更加不理想。但是它們仍然是拋棄型函數並且不維護任何類型的狀態,這限制了我們可以用它們執行的操作。因此出現了閉包並使 lambda 函數得到增強。 閉包是在它自己的環境中執行計算的函數,它有一個或多個綁定變數可以在調用函數時訪問。它們來自函數編程世界,其中涉及大量概念。閉包類似於 lambda 函數,但是在與定義閉包的外部環境中的變數進行交互方面更加智能。 讓我們看一看如何在 PHP 中定義閉包。清單 4 顯示了從外部環境導入變數並將其簡單地輸出到屏幕上的閉包示例。 清單 4. 簡單閉包示例
從外部環境中導入的變數是在閉包函數定義的 use 子句中指定的。默認情況下,它們是由值傳遞的,意味著如果要更新傳遞到閉包函數定義內的值,則不更新外部值。但是,我們可以通過在變數前放置 & 運算符來完成此操作,這種方法在函數定義中用於表示按引用傳遞。清單 5 顯示了這種方法的示例。 清單 5. 通過引用傳遞變數的閉包
可以看到,閉包使用外部變數 $x 並在每次調用閉包時遞增該變數。我們可以將按值和按引用傳遞的變數輕鬆地混合到 use 子句中,並且可以順利地處理這些變數。我們也可以擁有直接返回閉包的函數,如清單 6 所示。在本例中,閉包的生命周期實際上比定義閉包的方法長。 清單 6. 函數返回的閉包
閉包與對象 閉包不但是過程式編程的有用工具,而且是面向對象編程的有用工具。在這種情況下使用閉包與在類外部使用閉包實現的目的相同:包含在小範圍內綁定的特定函數。在對象外部與在對象內部使用一樣簡單。 在對象內定義時,非常方便的一點是閉包通過 $this 變數擁有對對象的完全訪問權,而無需顯式導入。清單 7 演示了該示例。 清單 7. 對象內的閉包
在這裡,我們在閉包內顯式使用提供給 greet() 方法的歡迎詞,閉包在該方法內定義。我們還在閉包內獲取狗的顏色和名字,傳遞到構造函數中並存儲到對象中。 在類中定義的閉包基本上與在對象外部定義的閉包相同。惟一的不同之處在於通過 $this 變數自動導入對象。我們可以通過將閉包定義為靜態閉包禁用此行為。 清單 8. 靜態閉包
此示例類似於清單 5 中定義的 Dog 類。最大的差別是在閉包內不使用對象的任何屬性,因為它被定義為靜態類。 在對象內使用靜態閉包與使用非靜態閉包相比的最大優點是節省內存。由於無需將對象導入閉包中,因此可以節省大量內存,尤其是在擁有許多不需要此功能的閉包時。 針對對象的另一個優點是添加名為 __invoke() 的魔術方法,此方法允許對象本身被調用為閉包。如果定義了此方法,則在該上下文中調用對象時將使用此方法。清單 9 演示了示例。 清單 9. 使用 __invoke() 方法
將清單 9 中所示的對象引用調用為變數將自動調用 __invoke() 魔術方法,使類本身用作閉包。 閉包可以很好地與面向對象的代碼以及面向過程的代碼整合。讓我們看一看閉包如何與 PHP 的強大 Reflection API 交互。
閉包與反射 PHP 有一個有用的反射 API,它允許我們對類、介面、函數和方法執行反向工程。按照設計,閉包是匿名函數,這意味著它們不顯示在反射 API 中。 但是,新 getClosure() 方法已經添加到 PHP 中的 ReflectionMethod 和 ReflectionFunction 類中,可以從指定的函數或方法動態創建閉包。它在此上下文中用作宏,通過閉包調用函數方法將在定義它的上下文中執行函數調用。清單 10 顯示了此方法如何運行。 清單 10. 使用 getClosure() 方法
這種方法的一個有趣的副作用是允許通過閉包訪問類的私有成員和受保護成員,這有利於對類執行單元測試。清單 11 展示了對類的私有方法的訪問。 清單 11. 訪問類的私有方法
此外,可以使用反射 API 來內省(introspect)閉包本身,如清單 12 所示。只需將對閉包的變數引用傳遞到 ReflectionMethod 類的構造函數中。 清單 12. 使用反射 API 內省閉包
關於向後兼容性值得注意的一點是,PHP 引擎現在保留類名 Closure 並用於存儲閉包,因此使用該名稱的所有類都需要重命名。 正如我們所見,反射 API 能夠通過現有函數和方法動態創建閉包,從而為閉包提供強大的支持。它們還可以像普通函數一樣內省到閉包中。
為什麼使用閉包? 如在 lambda 函數的示例中所示,閉包的最明顯用法之一是少數 PHP 函數接受回調函數作為參數。但是,在需要把邏輯封裝到自己的範圍內的情況下,閉包會十分有用。重構舊代碼以進行簡化並提高可讀性就是這樣一個例子。查看以下示例,該示例顯示了在運行一些 SQL 查詢時使用的記錄程序。 清單 13. 記錄 SQL 查詢的代碼
從清單 13 中可以看出執行操作的重複程度。對 Logger::log() 執行的每次調用都有相同的前兩個實參。為了解決此問題,我們可以把該方法調用放入閉包並轉而針對該閉包執行調用。得到的代碼如下所示: 清單 14. 記錄 SQL 查詢的重構代碼
代碼不但在外觀上更加規則,而且更易於更改 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 變數自動導入對象。我們可以通過將閉包定義為靜態閉包禁用此行為。
___FCKpd___7 |
此示例類似於清單 5 中定義的 Dog 類。最大的差別是在閉包內不使用對象的任何屬性,因為它被定義為靜態類。
在對象內使用靜態閉包與使用非靜態閉包相比的最大優點是節省內存。由於無需將對象導入閉包中,因此可以節省大量內存,尤其是在擁有許多不需要此功能的閉包時。
針對對象的另一個優點是添加名為 __invoke() 的魔術方法,此方法允許對象本身被調用為閉包。如果定義了此方法,則在該上下文中調用對象時將使用此方法。清單 9 演示了示例。
___FCKpd___8 |
將清單 9 中所示的對象引用調用為變數將自動調用 __invoke() 魔術方法,使類本身用作閉包。
閉包可以很好地與面向對象的代碼以及面向過程的代碼整合。讓我們看一看閉包如何與 PHP 的強大 Reflection API 交互。
![]() ![]() |
閉包與反射
PHP 有一個有用的反射 API,它允許我們對類、介面、函數和方法執行反向工程。按照設計,閉包是匿名函數,這意味著它們不顯示在反射 API 中。
但是,新 getClosure() 方法已經添加到 PHP 中的 ReflectionMethod 和 ReflectionFunction 類中,可以從指定的函數或方法動態創建閉包。它在此上下文中用作宏,通過閉包調用函數方法將在定義它的上下文中執行函數調用。清單 10 顯示了此方法如何運行。
___FCKpd___9 |
這種方法的一個有趣的副作用是允許通過閉包訪問類的私有成員和受保護成員,這有利於對類執行單元測試。清單 11 展示了對類的私有方法的訪問。
___FCKpd___10 |
此外,可以使用反射 API 來內省(introspect)閉包本身,如清單 12 所示。只需將對閉包的變數引用傳遞到 ReflectionMethod 類的構造函數中。
___FCKpd___11 |
關於向後兼容性值得注意的一點是,PHP 引擎現在保留類名 Closure 並用於存儲閉包,因此使用該名稱的所有類都需要重命名。
正如我們所見,反射 API 能夠通過現有函數和方法動態創建閉包,從而為閉包提供強大的支持。它們還可以像普通函數一樣內省到閉包中。
![]() ![]() |
為什麼使用閉包?
如在 lambda 函數的示例中所示,閉包的最明顯用法之一是少數 PHP 函數接受回調函數作為參數。但是,在需要把邏輯封裝到自己的範圍內的情況下,閉包會十分有用。重構舊代碼以進行簡化並提高可讀性就是這樣一個例子。查看以下示例,該示例顯示了在運行一些 SQL 查詢時使用的記錄程序。
___FCKpd___12 |
從清單 13 中可以看出執行操作的重複程度。對 Logger::log() 執行的每次調用都有相同的前兩個實參。為了解決此問題,我們可以把該方法調用放入閉包並轉而針對該閉包執行調用。得到的代碼如下所示:
___FCKpd___13 |
代碼不但在外觀上更加規則,而且更易於更改 SQL 查詢日誌的日誌級別,因為現在只需要在一個位置進行更改。
![]() ![]() |
結束語
本文演示了閉包在 PHP V5.3 代碼中作為函數編程構造時多麼有用。我們討論了 lambda 函數及閉包與這些函數相比的優點。對象與閉包可以很好地結合使用,比如我們在面向對象的代碼內對閉包的特殊處理。我們看到了如何使用反射 API 創建動態閉包,以及如何內省現有的閉包。(責任編輯:A6)
[火星人 ] PHP V5.3 中的新特性,第 2 部分: 閉包及 lambda 函數已經有997次圍觀