PHP 5.3/6 新增功能 - Closures, const, and others
PHP 5.3 新增特性列表與本部落格的系列文章:
- 名稱空間 (Namespace)
- 延遲靜態繫結 (Late Static Bindings)
- 新的魔術方法, __callStatic and __invoke.
- 標記跳躍, Support for jump labels (limited goto) has been added.
就是 goto ,忘了它吧。 - HTTP 串流轉接器(HTTP stream wrapper) 現在將狀態碼 200 到 399 視為成功執行。我不曉得為什麼這會列在新功能中。這看來是為了改善 RESTful service 的支援。
- 支援巢狀的例外處理。
- 加入一個垃圾收集器,預設開啟。嗯... PHP 沒有垃圾收集功能嗎?Ok, 原本的垃圾回收機制清潔力不夠。 目前大多數 PHP 程式架構是處於一次性消耗的無狀態環境下,基本上我們都假設程式跑完後行程就自動結束,而行程中配置的資源也會被作業系統回收。 在這種情形下設計出來的 PHP 程式碼,直接搬到 application container 架構執行時,會出現資源佔用不放的問題。 所以這功能應該是為了將來發展 PHP 的 application container 而強化的機制。
- 閉包、匿名函數(native Closures)(Lambda/Anonymous functions)。
- 新的即席文件語法(Nowdoc syntax)。
- 關鍵字 const 現在可用於類別定義之外。
- 三元運算子(?:)有縮寫形式。
關於 Closures (匿名函數), Const, Nowdoc 等新功能,將於本文中說明。
Const
Const 同樣是定義常數的語法,但以往只能用在類別定義中。PHP 5.3 起讓它也能用在類別定義之外。這讓我們可以有一致化的常數表達方式,以後我們可以不用 define()
的寫法。但是 define()
仍然有一件事是 const
做不到的,就是代入變數作為常數名稱。
Nowdoc, 無跳脫處理的 Heredoc
Heredoc,一般譯為「即席文件」,是一種表達跨行且預先縮排過的字串語法。它對字串中的跳脫字元與變數處理方式,等同雙引號(")字串。而 Nowdoc 雖然有一個新名稱,但說穿了就是單引號(')字串版的 Heredoc 。 Nowdoc 不會特別處理跳脫字元與變數,就只是將之視為一般字元。
Nowdoc 的語法和 Heredoc 相似,差別在於起始的識別字要用單引號(')括起。為了強化表達力, PHP5.3 也建議大家使用 Heredoc 時,用雙引號(")括起起始的識別字。
注意輸出結果, Nowdoc 會原原本本地顯示 $text\n。
---- NOWDOC ---- Hello $text\n ---- HEREDOC ---- Hello world ---- END ----
Shorthand Ternary
縮寫版的 ?:
運算子。這是針對一個還算常見的程式碼形式所提供的縮寫法,它可以讓我們省略一個重複的表達式。我們三不五時會用到「當 a 為真時回傳 a ,否則回傳 b 」的式子,即 a ? a : b
。這個縮寫法可以省略「回傳a」的表達式,自動回傳第一個式子的值。
Closures
Closures ,數學常用譯名為「閉包」或「閉鎖」,我戲稱為「封絕」。不過我個人更常使用「匿名函數(Anonymous function)」這一詞彙。各位可以看看我先前發布的匿名函數相關文章。
以往 PHP 可以使用 create_function() 建立一個匿名函數,但是寫法非常不方便,基本上我們不會想用以前的方式來寫 PHP 的匿名函數,就算寫了也不一定有我們預期的結果。
PHP 5.3 終於提供了一個更好的匿名函數語法,這個語法與 JavaScript 的語法一致,我想各位應該不會感到學習困難。只有一點差異, JavaScript 的匿名函數將共享上層活動領域(scope)的變數;但是 PHP 的匿名函數則不會共享上層活動領域的變數,你必須使用 use
關鍵字將外層變數導入匿名函數內。
先來看一段 JavaScript 的寫法。
PHP 5.3 的寫法則是:
Notice: Undefined variable: i in anonymous_function.php on line 6 2 3
接下來示範的是一個可以讓使用者傳入匿名函數的程式。看過PHP的中介編程與反射能力示範的人,應該會對下列程式碼感到似曾相識。
基本上, PHP 提供的函數中,只要是寫明 $callback 的,都可以用匿名函數作為參數。
嚴格來說是 callback
型別的參數就可以用匿名函數。但目前為止(本文撰寫時最新版本為PHP 5.3.1)似乎有一個 bug ,如果我將參數列的 $callback 宣告為 callback
型別,PHP 反而會擲出一個錯誤,抱怨我傳給它的是一個 Closure 實體而不是 callback 實體: Catchable fatal error: Argument 1 passed to Data::each() must be an instance of callback, instance of Closure given.
。這是 PHP 從 Java 學到的缺點,型別紊亂。所幸 PHP 可以忽略參數的型別宣告,故而我們不必像 Java 程序員那樣精通如何把方形塞進圓洞的技巧。省略參數的型別宣告,會變得較美好。
create_function 的潛規則
回覆 Chaosrx 的內容。
-
PHP 5.3 新增了一個內建類別,叫做 Closure 。 Closure 是真實存在的類別,不像 callback 只是文件上存在的類別(別問我為何 callback 不是內建類別,我也不知道)。
所有採用新式匿名函數語法建立的匿名函數,都是 Closure 的實例。所以宣告public function each(Closure $callback) {...}
不會發生錯誤,這是很正常的。 -
宣告
public function each(Closure $callback) {...}
,並不需要配合use Closure;
。
事實上,你直接這樣寫的話,PHP 還會發出一個警告訊息,告訴你use Closure;
沒有任何作用。因為你沒有定義任何一個叫做 Closure 的名稱空間。 -
Chaosrx 看到的 sources ,一定在某處定義了名稱叫 Closure 的名稱空間,所以它才會寫
use Closure;
。
注意,「use Closure」與「宣告函數的參數型態為 Closure」是不相干的。 -
當你明確地宣告參數型態為 Closure 時,你只能傳遞新式語法建立的匿名函數,而不能傳舊式的
create_function()
建立的匿名函數。
上述答覆的最後一點,牽涉到 PHP 處理 create_function()
的潛規則。
首先,我們來看看新、舊兩種語法產生的匿名函數碰上 Closure 型態宣告時會發生什麼事?
瞧,PHP 擲出錯誤了。它說我們用 create_function()
(舊式匿名函數語法) 建立的東西並不是一個 Closure 的實例。
那麼 create_function() 建立的是什麼東西?
根據 PHP Manual 所說,是 string 。見鬼。我寫了十年PHP,從沒看過 'echo "hello";'() 可以執行。
很顯然, create_function()
回傳的東西絕對不是單純的字串,它回傳的字串其實是匿名函數的名稱。事實上, PHP 建立了一個特殊的潛規則去處理 create_function()
回傳的字串。那個潛規則就是,所有 create_function()
建立的匿名函數,內部的函數名稱都是以 "\0lambda_"
為首,再接上編號;例如 "\0lambda_1"
。create_function()
回傳的字串就是這個名稱。
當 PHP 碰到 $s();
且 $s 為字串的敘述時,它會根據字串內容去查函數表。以 "\0lambda_"
為首的,自然就是 lambda 函數。
當我們知道這個潛規則時,我們就可以玩一個非常有趣的把戲。如下所示範:
只要我們能夠控制 create_function()
執行的順序,我們還可以玩隨機呼叫不同的 lambda 。
這個把戲看來有趣,其實用途不大。既然 PHP 5.3 正式提供了 Closure 類別,那麼 create_function()
的潛規則便沒有必要繼續存在。 PHP 應該早點將 create_function()
回傳的內容改成 Closure 的實例,才是正途。
樂多舊回應