我對 @ 符號的實際作用感到困惑,經過幾次實驗後,得出以下結論
* 無論錯誤回報設定為何,或是陳述式前面是否帶有 @,所設定的錯誤處理器都會被呼叫
* 由錯誤處理器來賦予不同錯誤等級一些意義。您可以使自訂錯誤處理器回顯所有錯誤,即使錯誤回報設定為 NONE。
* 那麼 @ 運算子有什麼作用?它會暫時將該行的錯誤回報等級設定為 0。如果該行觸發錯誤,錯誤處理器仍然會被呼叫,但它會以錯誤等級 0 被呼叫
希望這對某人有幫助
PHP 支援一個錯誤控制運算子:at 符號(@
)。當附加在 PHP 中的表達式之前時,該表達式可能產生的任何診斷錯誤都會被抑制。
如果使用 set_error_handler() 設定了自訂錯誤處理函式,即使診斷已被抑制,它仍然會被呼叫。
在 PHP 8.0.0 之前,如果錯誤被 @
運算子抑制,在自訂錯誤處理函式中呼叫的 error_reporting() 總是回傳 0
。自 PHP 8.0.0 起,它會回傳此(位元)表達式的值:E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE
。
表達式產生的任何錯誤訊息都可以在 error_get_last() 回傳的陣列中的 "message"
元素中找到。該函式的結果會在每次錯誤發生時改變,因此需要盡早檢查。
<?php
/* 故意檔案錯誤 */
$my_file = @file ('non_existent_file') or
die ("開啟檔案失敗:錯誤為 '" . error_get_last()['message'] . "'");
// 這適用於任何表達式,而不僅僅是函式:
$value = @$cache[$key];
// 如果索引 $key 不存在,則不會發出通知。
?>
注意:
@
運算子僅適用於表達式。一個簡單的經驗法則是:如果可以取得某個事物的數值,那麼就可以在其前面加上@
運算子。例如,它可以加在變數、函式呼叫、某些語言結構呼叫(例如 include)等等的前面。它不能加在函式或類別定義,或是條件結構(例如if
和 foreach)等等的前面。
在 PHP 8.0.0 之前,@
運算子可能會停用將終止腳本執行的嚴重錯誤。例如,在不存在(由於不可用或拼寫錯誤)的函式呼叫前面加上 @
會導致腳本終止,而沒有任何原因指示。
我對 @ 符號的實際作用感到困惑,經過幾次實驗後,得出以下結論
* 無論錯誤回報設定為何,或是陳述式前面是否帶有 @,所設定的錯誤處理器都會被呼叫
* 由錯誤處理器來賦予不同錯誤等級一些意義。您可以使自訂錯誤處理器回顯所有錯誤,即使錯誤回報設定為 NONE。
* 那麼 @ 運算子有什麼作用?它會暫時將該行的錯誤回報等級設定為 0。如果該行觸發錯誤,錯誤處理器仍然會被呼叫,但它會以錯誤等級 0 被呼叫
希望這對某人有幫助
請注意在 include() 之前的陳述式中使用錯誤控制運算子,如下所示
<?PHP
(@include("file.php"))
OR die("找不到 file.php!");
?>
這會導致包含檔案的錯誤回報等級也設定為零。因此,如果包含的檔案中存在一些錯誤,它們將不會顯示。
如果您想知道使用 @ 運算子的效能影響,請考慮以下範例。在此,第二個腳本(使用 @ 運算子)的執行時間是第一個腳本的 1.75 倍...幾乎是第一個腳本的兩倍。
因此,雖然有一些額外負擔,但每次迭代,我們看到 @ 運算子每次呼叫只增加了 0.005 毫秒。恕我直言,這不足以避免使用 @ 運算子。
<?php
function x() { }
for ($i = 0; $i < 1000000; $i++) { x(); }
?>
real 0m7.617s
user 0m6.788s
sys 0m0.792s
vs
<?php
function x() { }
for ($i = 0; $i < 1000000; $i++) { @x(); }
?>
real 0m13.333s
user 0m12.437s
sys 0m0.836s
如果可能,應該避免抑制錯誤,因為它不僅會抑制您試圖阻止的錯誤,還會抑制您沒有預料到會發生的錯誤。這會使除錯成為一場惡夢。
最好在繼續執行程式碼之前,測試您知道會導致錯誤的條件。這樣一來,只會抑制您知道的錯誤,而不會抑制與該段程式碼相關的所有未來錯誤。
使用我建議的方法,而非直接抑制錯誤,可能會有充分的理由。然而,在我編寫網頁應用程式的多年經驗中,我從未遇到過這是個好解決方案的情況。本手冊頁面給出的範例,絕對不是應該使用錯誤控制運算子(@)的情況。
不應該因為「可能會被濫用」而不使用某個東西。你也可以說「unlink 很邪惡,你可以用它刪除檔案,所以永遠不要使用 unlink」。
@ 運算子會隱藏所有錯誤,這是一個有效的觀點 - 所以我的經驗法則是:只有在你清楚你的表達式可能會拋出的所有錯誤,並且你認為所有這些錯誤都不重要時,才使用它。
一個簡單的例子是
<?php
$x = @$a["name"];
?>
這裡只有兩個可能的問題:變數遺失或索引遺失。如果你確定這兩種情況都可以接受,那就沒問題。再次強調:抑制錯誤並不是犯罪。不清楚何時可以安全地抑制錯誤,肯定更糟糕。
在 PHP8 的錯誤處理程序中,仍然可以偵測到何時使用了 @ 運算子。呼叫 error_reporting() 不會再如文件所述傳回 0,但當你呼叫 error_reporting() 時,使用 @ 運算子仍然會改變傳回值。
我的 PHP 錯誤設定設定為使用 E_ALL,當我從未被抑制的錯誤的錯誤處理程序中呼叫 error_reporting() 時,它會如預期傳回 E_ALL。
但是,當我嘗試使用 @ 運算子抑制錯誤的表達式發生錯誤時,它會傳回:E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR (或數字 4437)。
我不希望在我的程式碼中使用 4437,以防它在不同的設定或未來版本的 PHP 中發生變化,所以我現在使用
<?php
function my_error_handler($err_no, $err_msg, $filename, $linenum) {
if (error_reporting() != E_ALL) {
return false; // 已靜音
}
// ...
}
?>
如果程式碼需要在所有 PHP 版本中使用,你可以檢查 error_reporting() 是否不等於 E_ALL 或 0。
當然,如果你的 PHP 錯誤報告設定不是 E_ALL,你必須將其更改為你使用的任何設定。
快速除錯方法
@print($a);
等同於
if isset($a) echo $a ;
@a++;
等同於
if isset($a) $a++ ;
else $a = 1;
請注意,此運算子的行為從 php5 變更為 php7。
以下程式碼無論如何都會引發嚴重錯誤,你無法抑制它
<?php
function query()
{
$myrs = null;
$tmp = @$myrs->free_result();
return $tmp;
}
var_dump(query());
echo "THIS IS NOT PRINT";
?>
更多資訊請見:https://bugs.php.net/bug.php?id=78532&thanks=3
在花了一些時間研究為什麼我仍然收到應該用 @ 抑制的錯誤後,我發現了以下內容。
1. 如果你設定了自己的預設錯誤處理程序,那麼無論是否有 @ 符號,錯誤仍然會傳送到錯誤處理程序。
2. 如下所述,@ 抑制只會針對該呼叫變更錯誤層級。這並不是說在你的錯誤處理程序中,你可以檢查給定的 $errno 是否為 0,因為 $errno 仍然會參照錯誤的 TYPE(而非錯誤層級),例如 E_WARNING 或 E_ERROR 等。
3. @ 只會將該次呼叫的執行階段錯誤報告層級變更為 0。這表示在你的自訂錯誤處理程序中,你可以使用 error_reporting() 檢查目前的執行階段 error_reporting 層級(請注意,如果你想取得目前值,則不得將任何參數傳遞給此函式),如果它是零,則表示它已被抑制。
<?php
// 自訂錯誤處理程序
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
if ( 0 == error_reporting () ) {
// 錯誤報告目前已關閉或使用 @ 抑制
return;
}
// 在這裡執行你的正常自訂錯誤報告
}
?>
如需設定自訂錯誤處理程序的更多資訊,請參閱:https://php.dev.org.tw/manual/en/function.set-error-handler.php
如需 error_reporting 的更多資訊,請參閱:https://php.dev.org.tw/manual/en/function.error-reporting.php
如果你使用 ErrorException 例外來進行統一的錯誤管理,我建議你測試錯誤處理程序中的 error_reporting,而不是例外處理程序,因為你可能會遇到一些問題,例如空白頁面,因為 error_reporting 可能不會傳輸到例外處理程序。
所以,不要使用
<?php
function exception_error_handler($errno, $errstr, $errfile, $errline )
{
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");
function catchException($e)
{
if (error_reporting() === 0)
{
return;
}
// 執行一些操作
}
set_exception_handler('catchException');
?>
最好改為執行
<?php
function exception_error_handler($errno, $errstr, $errfile, $errline )
{
if (error_reporting() === 0)
{
return;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");
function catchException($e)
{
// 執行一些操作
}
set_exception_handler('catchException');
?>
最好使用函式 trigger_error() (http://de.php.net/manual/en/function.trigger-error.php)
來顯示已定義的通知、警告和錯誤,而不是自己檢查錯誤層級。如果 php.ini 中有定義,這可讓你將訊息寫入日誌檔,並根據 error_reporting() 層級輸出
訊息,並使用 @ 符號抑制輸出。
雖然你絕對不應該太過自由地使用 @ 運算子,但我也不贊成那些聲稱它是終極罪惡的人。
例如,一個非常合理的用法是抑制 parse_ini_file() 在你已知 .ini 檔案可能遺失時產生的通知層級錯誤。
在我的情況下,取得 FALSE 傳回值足以處理該情況,但我不想讓我的 API 輸出通知錯誤。
TL;DR:使用它,但前提是你清楚自己在抑制什麼以及為什麼要抑制。
當表達式遇到錯誤時,如果該表達式被錯誤控制運算符保護,則將該表達式的返回值賦值給變數時,PHP 的行為是什麼?
根據以下程式碼,結果是 NULL (但如果能確認在所有情況下都為真,那就太好了)。
<?php
$var = 3;
$arr = array();
$var = @$arr['x']; // 在此賦值之後,$var 的值是什麼?
// 它的值會像從未進行賦值一樣,保持先前的的值 (3) 嗎?
// 它是 FALSE 還是 NULL?
// 它會是某種例外、錯誤訊息或錯誤編號嗎?
var_dump($var); // 列印 "NULL"
?>
* 如何讓已棄用的超全域變數 `$php_errormsg` 工作
>1. 修改 php.ini
>track_errors = On
>error_reporting = E_ALL & ~E_NOTICE
>2. 請注意,如果您已經在使用自定義的錯誤處理程序,它會提示「未定義變數」
>請在執行程式碼前插入程式碼`set_error_handler(null);`,例如
>```php
>set_error_handler(null);
>$my_file = @file ('phpinfo.phpx') or
>die ("<br>開啟檔案失敗:<br>\t$php_errormsg");
>```
>(c) Kenny Fang