這是 require
的標(biāo)準(zhǔn)行為,與 include
# 的文檔,這兩者之間的行為是相同的:
如您所見,當(dāng)回傳值未被覆寫時(shí),快樂路徑上會(huì)傳回整數(shù) (1)。
這對您的範(fàn)例來說是有意義的,到目前為止,檔案存在(因此沒有致命錯(cuò)誤),但由於檔案剛剛創(chuàng)建,因此它可能只是被截?cái)?,也就是說,它是空的。 p>
因此傳回值不會(huì)被覆寫,您可以看到 int(1)。
另一種解釋自然是您已經(jīng)用整數(shù)覆蓋,這也是可能的,因?yàn)槎鄠€(gè)進(jìn)程可以寫入同一個(gè)文件,但對於您編寫示例的方式來說,這種可能性較小。我只是提到它,因?yàn)檫@是另一個(gè)有效的解釋。
範(fàn)例如何在您尋找 $result
時(shí)懸浮競爭條件,而不是(僅)在檔案存在時(shí):
if (($result = @include($cachePath)) && is_array($result) ) { # $result is array, which is required # ... }
背後的想法是,我們只進(jìn)行很少的錯(cuò)誤處理,例如檢查文件是否存在,否則無法包含該文件(include() 只會(huì)發(fā)出警告並以$result = false 傳遞),然後如果$result載入確實(shí)適用於is_array() 測試。
這就是我們?yōu)殄e(cuò)誤而設(shè)計(jì)的,但我們知道我們在尋找什麼,即 $result 是一個(gè)陣列。
這通常稱為事務(wù)或事務(wù)操作。
在這個(gè)新範(fàn)例中,當(dāng) $result 陣列為空時(shí),我們甚至不會(huì)輸入 if-body,例如不包含任何資料。
在程式處理層級上,這可能是我們感興趣的,檔案存在或不存在、為空或不為空、甚至寫錯(cuò)都是錯(cuò)誤情況,它需要「吃掉」並使 $result 無效。
定義錯(cuò)誤不存在。
自 PHP 7.0 起,我們可以使用 include(),如果不幸的是返回的包含檔案已寫入一半,我們將看到 PHP 解析錯(cuò)誤,該錯(cuò)誤可以捕獲:
# start the transaction $result = null; assert( is_string($cachePath) && # pathnames are strings, '' !== $cachePath && # never empty, false === strpos($cachePath, "rrreee") # and must not contain null-bytes ); try { if (file_exists($cachePath)) { $result = include($cachePath); } # invalidate $result in case include() did technically work. if (!$result || !is_array($result) { $result = null; } } catch (Throwable $t) { # catch all errors and exceptions, # the fall-through is intended to invalidate $result. $result = null; } finally { # $result is not null, but a non-empty array if it worked. # $result is null, if it could not be acquired. }
請參考 PHP try-catch-finally 了解如何拋出異常/異常處理工作詳細(xì),assert()用於記錄範(fàn)例中輸入?yún)?shù)$cachePath的含義。
第二個(gè)範(fàn)例不使用抑制操作“@”,原因是如果像前面的範(fàn)例一樣使用它,並且要包含的檔案將包含真正的致命錯(cuò)誤,則該致命錯(cuò)誤將被靜音。如今,在現(xiàn)代PHP 中,這不再是一個(gè)大問題,但是使用file_exists() include() – 雖然由於檢查時(shí)間與使用時(shí)間而存在競爭條件– 對於不存在的文件是安全的(僅警告)並且致命錯(cuò)誤不會(huì)被隱藏。
正如您可能已經(jīng)看到的,您了解的細(xì)節(jié)越多,就越難編寫盡可能具有前瞻性的程式碼。我們絕不能迷失在錯(cuò)誤處理本身的錯(cuò)誤處理中,而應(yīng)該專注於結(jié)果並定義這些錯(cuò)誤不存在。
也就是說,include() 仍然導(dǎo)致將資料載入到記憶體中,file_exists() 僅用於「抑制」警告,我們知道,儘管如此,include() 可能會(huì)發(fā)出警告並可能傳回一個(gè)整數(shù),而不是一個(gè)陣列。
現(xiàn)在,由於編程很困難:然後您可能會(huì)將其包裝在一個(gè)循環(huán)中,例如重試三次。為什麼不使用 for 迴圈 來計(jì)數(shù)並保護(hù)數(shù)字重試次數(shù)?
如果腳本始終只有一個(gè)執(zhí)行者,則此問題無法重現(xiàn)。
如果您正在談?wù)搧K行運(yùn)行此腳本,那麼問題在於以獨(dú)佔(zhàn)模式寫入檔案並不能保護(hù)您稍後在寫入過程中讀取檔案。
進(jìn)程可能正在寫入檔案(並擁有鎖),但 require
不遵守該鎖(檔案系統(tǒng)鎖是建議性的,而不是強(qiáng)制執(zhí)行的)。
所以正確的解決方案是:
<?php $f = function() use($a){ $cachePath = '/tmp/t.php'; /* Open the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). The file pointer is positioned on the beginning of the file. This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file, as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can be used after the lock is requested). */ $fp = fopen($cachePath, "c"); $code = '<?php'; $code .= "\n\n"; $code .= 'return ' . var_export([], true) . ';'; // acquire exclusive lock, waits until lock is acquired flock($fp, LOCK_EX); // clear the file ftruncate($fp, 0); // write the contents fwrite($fp, $code); //wrong (see my answer as to why) //file_put_contents($cachePath, $code, LOCK_EX); //not needed //if (file_exists($cachePath)) { // Lock is held during require $result = require($cachePath); if (!is_array($result)) { var_dump($result, $cachePath, file_get_contents($cachePath)); exit("ok"); } var_dump($result); //} // closing the file implicitly releases the lock fclose($fp); }; for($i=0;$i<1000000;$i++) { $f(); }
請注意,寫入後不會(huì)釋放並重新取得鎖定,因?yàn)榱硪粋€(gè)進(jìn)程可能正在等待覆寫該檔案。
不發(fā)布它是為了確保編寫的同一段程式碼也是 require
d。
然而,這整件事從一開始就值得懷疑。
為什麼需要寫入一個(gè)檔案以便稍後require
將其傳回?