當 PHP 中不可能時 require 方法回傳 int
P粉883223328
2023-09-02 09:55:14
<p>我有以下程式碼,它將一些php程式碼儲存到檔案中,然後載入它,再次運行,有時require方法傳回int,為什麼會發生這種情況? </p>
<h1>示範.php</h1>
<pre class="brush:php;toolbar:false;"><?php
$f = function() use($a){
$cachePath = '/tmp/t.php';
$code = '<?php';
$code .= "\n\n";
$code .= 'return ' . var_export([], true) . ';';
file_put_contents($cachePath, $code, LOCK_EX);
if (file_exists($cachePath)) {
// Sometime the following line returns int,why?
$result = require($cachePath);
if (!is_array($result)) {
var_dump($result, $cachePath, file_get_contents($cachePath));
exit("ok");
}
var_dump($result);
}
};
for($i=0;$i<1000000;$i ) {
$f();
}</pre>
<h1>如何重現? </h1>
<p>使用兩個 php 行程執行上面的程式碼</p>
<pre class="brush:php;toolbar:false;">php demo.php</pre></p>
這是
require
的標準行為,與include
# 的文檔,這兩者之間的行為是相同的:如您所見,當回傳值未被覆寫時,快樂路徑上會傳回整數 (1)。
這對您的範例來說是有意義的,到目前為止,檔案存在(因此沒有致命錯誤),但由於檔案剛剛創建,因此它可能只是被截斷,也就是說,它是空的。 p>
因此傳回值不會被覆寫,您可以看到 int(1)。
另一種解釋自然是您已經用整數覆蓋,這也是可能的,因為多個進程可以寫入同一個文件,但對於您編寫示例的方式來說,這種可能性較小。我只是提到它,因為這是另一個有效的解釋。
如果存在則包含
範例如何在您尋找
$result
時懸浮競爭條件,而不是(僅)在檔案存在時:背後的想法是,我們只進行很少的錯誤處理,例如檢查文件是否存在,否則無法包含該文件(include() 只會發出警告並以$result = false 傳遞),然後如果$result載入確實適用於is_array() 測試。
這就是我們為錯誤而設計的,但我們知道我們在尋找什麼,即 $result 是一個陣列。
這通常稱為事務或事務操作。
在這個新範例中,當 $result 陣列為空時,我們甚至不會輸入 if-body,例如不包含任何資料。
在程式處理層級上,這可能是我們感興趣的,檔案存在或不存在、為空或不為空、甚至寫錯都是錯誤情況,它需要「吃掉」並使 $result 無效。
定義錯誤不存在。
處理解析錯誤(對於 Include-If-Exists)
自 PHP 7.0 起,我們可以使用 include(),如果不幸的是返回的包含檔案已寫入一半,我們將看到 PHP 解析錯誤,該錯誤可以捕獲:
請參考 PHP try-catch-finally 了解如何拋出異常/異常處理工作詳細,assert()用於記錄範例中輸入參數$cachePath的含義。
第二個範例不使用抑制操作“@”,原因是如果像前面的範例一樣使用它,並且要包含的檔案將包含真正的致命錯誤,則該致命錯誤將被靜音。如今,在現代PHP 中,這不再是一個大問題,但是使用file_exists() include() – 雖然由於檢查時間與使用時間而存在競爭條件– 對於不存在的文件是安全的(僅警告)並且致命錯誤不會被隱藏。
正如您可能已經看到的,您了解的細節越多,就越難編寫盡可能具有前瞻性的程式碼。我們絕不能迷失在錯誤處理本身的錯誤處理中,而應該專注於結果並定義這些錯誤不存在。
也就是說,include() 仍然導致將資料載入到記憶體中,file_exists() 僅用於「抑制」警告,我們知道,儘管如此,include() 可能會發出警告並可能傳回一個整數,而不是一個陣列。
現在,由於編程很困難:然後您可能會將其包裝在一個循環中,例如重試三次。為什麼不使用 for 迴圈 來計數並保護數字重試次數?
如果腳本始終只有一個執行者,則此問題無法重現。
如果您正在談論並行運行此腳本,那麼問題在於以獨佔模式寫入檔案並不能保護您稍後在寫入過程中讀取檔案。
進程可能正在寫入檔案(並擁有鎖),但
require
不遵守該鎖(檔案系統鎖是建議性的,而不是強制執行的)。所以正確的解決方案是:
請注意,寫入後不會釋放並重新取得鎖定,因為另一個進程可能正在等待覆寫該檔案。
不發布它是為了確保編寫的同一段程式碼也是
require
d。然而,這整件事從一開始就值得懷疑。
為什麼需要寫入一個檔案以便稍後
require
將其傳回?