當 PHP 中不可能時 require 方法回傳 int
P粉883223328
P粉883223328 2023-09-02 09:55:14
0
2
579
<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>
P粉883223328
P粉883223328

全部回覆(2)
P粉351138462

這是 require 的標準行為,與 include# 的文檔,這兩者之間的行為是相同的:

如您所見,當回傳值未被覆寫時,快樂路徑上會傳回整數 (1)。

這對您的範例來說是有意義的,到目前為止,檔案存在(因此沒有致命錯誤),但由於檔案剛剛創建,因此它可能只是被截斷,也就是說,它是空的。 p>

因此傳回值不會被覆寫,您可以看到 int(1)。

另一種解釋自然是您已經用整數覆蓋,這也是可能的,因為多個進程可以寫入同一個文件,但對於您編寫示例的方式來說,這種可能性較小。我只是提到它,因為這是另一個有效的解釋。

如果存在則包含

範例如何在您尋找 $result 時懸浮競爭條件,而不是(僅)在檔案存在時:

if (($result = @include($cachePath)) &&
    is_array($result)    
) {
   # $result is array, which is required
   # ...
}

背後的想法是,我們只進行很少的錯誤處理,例如檢查文件是否存在,否則無法包含該文件(include() 只會發出警告並以$result = false 傳遞),然後如果$result載入確實適用於is_array() 測試。

這就是我們為錯誤而設計的,但我們知道我們在尋找什麼,即 $result 是一個陣列。

這通常稱為事務或事務操作。

在這個新範例中,當 $result 陣列為空時,我們甚至不會輸入 if-body,例如不包含任何資料。

在程式處理層級上,這可能是我們感興趣的,檔案存在或不存在、為空或不為空、甚至寫錯都是錯誤情況,它需要「吃掉」並使 $result 無效。

定義錯誤不存在。

處理解析錯誤(對於 Include-If-Exists)

自 PHP 7.0 起,我們可以使用 include(),如果不幸的是返回的包含檔案已寫入一半,我們將看到 PHP 解析錯誤,該錯誤可以捕獲

# 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 了解如何拋出異常/異常處理工作詳細,assert()用於記錄範例中輸入參數$cachePath的含義。

第二個範例不使用抑制操作“@”,原因是如果像前面的範例一樣使用它,並且要包含的檔案將包含真正的致命錯誤,則該致命錯誤將被靜音。如今,在現代PHP 中,這不再是一個大問題,但是使用file_exists() include() – 雖然由於檢查時間與使用時間而存在競爭條件– 對於不存在的文件是安全的(僅警告)並且致命錯誤不會被隱藏。

正如您可能已經看到的,您了解的細節越多,就越難編寫盡可能具有前瞻性的程式碼。我們絕不能迷失在錯誤處理本身的錯誤處理中,而應該專注於結果並定義這些錯誤不存在。

也就是說,include() 仍然導致將資料載入到記憶體中,file_exists() 僅用於「抑制」警告,我們知道,儘管如此,include() 可能會發出警告並可能傳回一個整數,而不是一個陣列。


現在,由於編程很困難:然後您可能會將其包裝在一個循環中,例如重試三次。為什麼不使用 for 迴圈 來計數並保護數字重試次數?

P粉550323338

如果腳本始終只有一個執行者,則此問題無法重現。

如果您正在談論並行運行此腳本,那麼問題在於以獨佔模式寫入檔案並不能保護您稍後在寫入過程中讀取檔案。

進程可能正在寫入檔案(並擁有鎖),但 require 不遵守該鎖(檔案系統鎖是建議性的,而不是強制執行的)。

所以正確的解決方案是:

<?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();
}

請注意,寫入後不會釋放並重新取得鎖定,因為另一個進程可能正在等待覆寫該檔案。

不發布它是為了確保編寫的同一段程式碼也是 required。

然而,這整件事從一開始就值得懷疑。

為什麼需要寫入一個檔案以便稍後require將其傳回?

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板