2009年11月26日 00:34

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 的內容。

  1. PHP 5.3 新增了一個內建類別,叫做 Closure 。 Closure 是真實存在的類別,不像 callback 只是文件上存在的類別(別問我為何 callback 不是內建類別,我也不知道)。
    所有採用新式匿名函數語法建立的匿名函數,都是 Closure 的實例。所以宣告 public function each(Closure $callback) {...} 不會發生錯誤,這是很正常的。
  2. 宣告 public function each(Closure $callback) {...} ,並不需要配合 use Closure;
    事實上,你直接這樣寫的話,PHP 還會發出一個警告訊息,告訴你 use Closure; 沒有任何作用。因為你沒有定義任何一個叫做 Closure 的名稱空間。
  3. Chaosrx 看到的 sources ,一定在某處定義了名稱叫 Closure 的名稱空間,所以它才會寫 use Closure;
    注意,「use Closure」與「宣告函數的參數型態為 Closure」是不相干的。
  4. 當你明確地宣告參數型態為 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 的實例,才是正途。


  • shirock 發表於樂多回應(5)引用(1)PHP編輯本文
    樂多分類:學術/學習 │昨日人次:2 │累計人次:2039 │標籤:PHP5.3,closure,anonymous function,const,nowdoc
    Ads by Roodo! 

    引用URL

    http://cgi.blog.roodo.com/trackback/10833753
    引用列表:
    日前 OpenJDK 發表了第一版的 Java Lambda 語法 (First Version of Java Lambda Syntax Sparks Debate),語法好壞,爭論不斷。本文分別列出了以 JavaScript, PHP, Ruby, C# 語言模仿 Java Lambda 範例的程式碼。 在數學中,lambda 有一個嚴謹的定義。但在是程式語言中,lambda 有另一個更廣泛的理解,即「匿名函數」(anonymous function)。在大多數程式語言中,根本沒有區分
    透過 JavaScript,Ruby,PHP,C# 語言,理解 Java 的 Lambda 語法【石頭閒語】 at 2010年06月23日 16:10
    回應文章

    callback 是個假型別喔,是給文件用的,實際上是不存在的... (請參考: http://www.php.net/manual/en/language.pseudo-types.php)
    callback 在 5.3 以前其實是指以下型別: string, array
    | 檢舉 | Posted by jaceju at 2009年11月26日 10:08
    在動態語言的環境中,型別宣告本來就是參考文件一般的存在。

    問題是,為什麼加上這個假型別後,反而妨礙了原本可運作的程式。

    Manual 的同一頁,最後一句說「As of PHP 5.3.0 it is possible to also pass a closure to a callback parameter. 」。所有 PHP 函數中,關於 $callback 參數的型別宣告都是 callback。比照 PHP 的官方示範,我們自己設計的函數參數宣告 callback 型別後應該也一樣能用,這種預期是很合理也很正常的。但實際上是「You can not pass」。
    對我而言,這就是 bug 。

    另外,我特地查了我本機中 CHM 版本的 PHP manual ,在 PHP5.3之前,$callback參數的型別就一直是寫 callback.例如

    mixed preg_replace_callback ( mixed $pattern , callback $callback , mixed $subject [, int $limit= -1 [, int &$count ]] )

    CHM版本是 2009-4-17, PHP 5.3還沒 release.
    | 檢舉 | Posted by 遊手好閒的石頭成 at 2009年11月26日 19:25

    所以才會說它只是給文件用的...這點官方一直說得很清楚耶...就像 mixed 也不是真的型態一樣的道理...
    | 檢舉 | Posted by jaceju at 2009年11月30日 17:13

    我在一個 Open Source 的程式碼內看到
    關於
    public function each(callback $callback)
    宣告錯誤的地方

    該程式是在程式檔開頭先加入
    use Closure;

    然後宣告成
    public function each(Closure $callback) {...}
    | 檢舉 | Posted by Chaosrx at 2009年12月27日 06:07
    關於 Chaosrx 說的事,我回覆的內容有點多。

    還牽涉到 create_function() 有趣的潛規則。

    我補到正文的後面去了。
    | 檢舉 | Posted by 遊手好閒的石頭成 at 2009年12月27日 06:15