PHP7 realpath函數一個長期存在的bug 原 薦

本文最後結論:PHP的realpath函數不支持phar文件。經過網友指出,是我腦袋發熱,自以爲是的 let_it_work函數,以爲生效,其實裏面存在非常致命的邏輯錯誤,我記住教訓了

爲了留下的記錄,文章內容一字不改,只在 let_it_work 函數那裏註釋了一下。

這事情教育我,還是得要單元測試,這種想當然錯誤太明顯了。

實在是忍不住要吐槽一下,從7.0.0到7.0.4的時候,我一直在看這個bug,而且也發去php issues了,已經說修復了,但是顯然並沒有修復。後來忙,就沒管這個問題了,可是到今天,都7.0.14了,7.1.0都發布了,還是沒修復啊!

phar包

假定我有一個phar包,包內結構如下:

$paths = [
	'phar://phar_test.phar/hello',
	'phar://phar_test.phar/hello/a.php',
];

上述的這個路徑,是存在的。

第一次測試

執行測試:

foreach ($paths as $path) {
	var_dump(file_exists($path)); // return true
	var_dump(realpath($path)); // return false
}

輸出結果如下:

如果用file_exists檢查,那麼他是返回true的,文件是存在的。

用realpath檢查,他返回了false,就是返回真實路徑的時候,他無法返回相對應的真實路徑。

所以,一般到這裏,會讓我們得出一個想當然的結論:realpath顯然不支持phar包。

第二次測試

問題並沒有結束,顯然realpath是支持phar的,這次我們加一個函數:

function let_it_work(string $path)
{
	$realPath = realpath($path);
	if ($realPath !== false) {
		$path = $realPath;
	}
    // 這裏我想當然了
    // 應該改爲 return false
	return $path;
}

這個函數其實沒啥特別,但是他卻能讓我們得到想要的結果。

執行下列測試程序:

foreach ($paths as $path) {
	var_dump(file_exists($path)); // return true
	var_dump(realpath($path)); // return false
	var_dump(let_it_work($path)); // 輸出 "phar://phar_test.phar/hello" 和 "phar://phar_test.phar/hello/a.php"
}

我們會得到如下的結果:

顯然realpath函數還是生效了。不然是不會得到真實路徑的。

第三次測試

那麼是不是給realpath包一層函數,就能得到我們想要的結果呢?那我再加一個函數:

function realpath2(string $path)
{
	return realpath($path);
}

執行以下測試:

foreach ($paths as $path) {
	var_dump(file_exists($path)); // return true
	var_dump(realpath($path)); // return false
	var_dump(let_it_work($path)); // 輸出 "phar://phar_test.phar/hello" 和 "phar://phar_test.phar/hello/a.php"
	var_dump(realpath2($path)); // 和realpath返回結果一樣
}

得到如下的結果:

顯然包一層函數是不能解決問題的。

第四次測試

這個問題,並不止於realpath函數,所有獲取realpath的函數,都存在這個問題。比如目錄迭代器 DirectoryIterator,我們再寫一個函數:

function entry(DirectoryIterator $dir)
{
	foreach ($dir as $item) {
		$path = $item->getPathname();
		var_dump(file_exists($path)); // true
		var_dump($item->getRealPath()); // false
		var_dump(let_it_work($path)); // 這裏返回的結果,是正確的
	}
}

entry(new DirectoryIterator('phar://phar_test.phar'));

這個測試很簡單,就是傳入一個目錄迭代器,然後遍歷目錄下的內容,然後調用 getRealPath 方法以進行測試。

執行結果如下:

不出所料,getRealPath返回還是返回無效的結果。

結論

其實這個問題看上去,並不是一個很嚴重的問題,而且也有解決方案了,所以也沒什麼可抱怨的。

但是冷靜分析一下let_it_work的函數,問題的關鍵在於:

if ($realPath !== false) {
	$path = $realPath;
}

$path變量是函數的參數傳入的,但是到這裏,我把他和false做了一次比較,然後又把他寫入了另一個變量的結果,這樣就改變了結果。這裏其實是一個很嚴重的問題,就是變量的內存地址問題。也就是說,通過一些操作,就讓一個變量的內存地址發生了變化,取回了正確的值。怎麼想都覺得非常詭異,莫名其妙。

這個問題從7.0.0發佈的時候我就發現了,因爲這個問題,這一年來我一直在認真考慮轉Java還是C#的問題。

當然,也許我可以通過一些內存跟蹤的手段去明確這個問題的根本,但我實在懶得折騰了。

其實PHP7還有一些讓我不太滿意的問題,比如ArrayObject的問題,比如:ArrayObject->item += 1,是無法觸發offsetSet和offsetGet接口的。這個可能不算bug,也許到php7,關閉了這個特性。

無論如何,我對PHP的態度是,我只是這個語言的使用者,如果你讓我折騰C,我不如去寫Go、Java、C#等等,多了去的選擇。所以如果這個語言本身不可靠,那我真的應該考慮換一個語言了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章