自已曾經碰過,今天被問到的問題,覺得解釋的不太好,所以事後把它寫下來。
問題是這樣子:
某個 class 裡面的 method 呼叫一段在其他檔案定義的 function,回傳值卻不如預期。
實際把問題簡化成 sample code 如下:
假設 app.php
與 lib.php
都在同一層目錄
lib.php
<?php
$exchange_rates = array(
'TW' => 30,
);
function currency_convert($currency, $to)
{
global $exchange_rates;
return $currency * $exchange_rates[$to];
}
app.php
<?php
require __DIR__ . '/lib.php';
echo currency_convert(10, 'TW') . PHP_EOL;
這邊為了簡單呈現問題,不做 sanity check,上面這一段輸出預期會是 300
,但是在特地的條件下 currency_convert()
的回傳的會是讓你驚訝的 0
,我們把 app.php
修改為下面的內容:
<?php
call_user_func(function() {
require __DIR__ . '/lib.php';
});
echo currency_convert(10) . PHP_EOL;
這邊簡單用 anonymous function 來 require lib.php
來重製問題,故意讓 currency_convert()
壞掉,結果輸出就會是 0
,知道問題了嗎?
與 C 一樣,PHP 在 function 裡的變數都是區域變數,唯一不同的地方在全域變數在 PHP function 裡必需要用 global
宣告才能看的到。
所以在上面的例子中, $exchange_rates 因為 anonymous function 的關係,他從全域變數變成了區域變域,只在 anonymous function 裡才存取的到。
千萬別認為神經病才用 anonymous function 來 require library!市面上所見到 autoload 機制的 class loader 1,大多都是在 method (或 function) 裡來 require 檔案,然後就引爆地雷了。
workaround 的方法就是在看的到變數的地方做手腳,所以有幾種方法:
- 在 require 前宣告 global $var
- 用 superglobal 變數 $GLOBALS
- 你有權限 touch 的到
lib.php
的話,在外面的 $exchange_rate 也可以宣告成 global,global 並沒有限制一定要在 function 裡使用,在 function 外也可以使用,因為檔案在 function 裡被引用的話,此時的global
就發揮用處了
後話
有一定年紀的 PHP Application,如果持續維護至今的話,應該都會面臨 programming paradigm 從 precedural 轉換至 object-oriented 的過程,在轉換過程之中就很容易遇到這類的問題。
這個問題是義大利麵與定食共享的結果。