PHP のコードで、@file_get_contents(...) や @$_GET['id'] のように、関数や変数の前に @ が付いているのを見かけることがあります。これはエラー制御演算子と呼ばれるもので、その式で出るエラーメッセージを表示しないようにします。手軽に警告を消せますが、多用すると、不具合の原因を探すのがとても難しくなります。

@ がすること

<?php
// ファイルが無いと「Failed to open stream」という警告が出る
$text = file_get_contents('/path/to/missing.txt');

// @ を付けると、警告は出ない (失敗して false が返るのは同じ)
$text = @file_get_contents('/path/to/missing.txt');
var_dump($text); // bool(false)

@ は、関数の呼び出しだけでなく、変数や配列の要素、include など、値を返す式の前に付けられます。if や foreach の前には付けられません。

大事なのは、@ が消すのはメッセージだけ、という点です。ファイルが読めなかったという事実は変わらず、関数は false を返します。戻り値を確かめずに先へ進むと、ずっと後の処理で別の不具合として表に出ます。そのときには、もとの警告は画面にもログにも残っていないため、原因にたどり着くのに時間がかかります。

PHP 8 での変更

PHP 8.0 で、@ の扱いが変わりました。

  • 致命的なエラー (スクリプトが止まるもの) は、@ で隠れなくなった。以前は、存在しない関数の呼び出しなどに @ が付いていると、何も表示されないままスクリプトが止まることがあった
  • set_error_handler() で自作のエラーハンドラーを登録していると、@ が付いた式のエラーでもハンドラーが呼ばれる (これは以前から同じ)。その中で error_reporting() を呼ぶと、以前は 0 が返ったが、PHP 8.0 からは致命的なエラーの種類 (E_ERROR など) を合わせた値が返る。error_reporting() === 0 で @ の有無を見分けていたハンドラーは、書き直しが必要

また、PHP 8 では、関数に違う型の引数を渡したときなど、多くの場面で警告ではなく例外 (TypeError や ValueError) が投げられるようになりました。例外は @ では止められません。

代わりの書き方

戻り値を確かめる

失敗すると false を返す関数は、=== で確かめます。== で比べると、空のファイル ('') と失敗 (false) を区別できません。

<?php
$text = file_get_contents($path);
if ($text === false) {
    // 読めなかったときの処理
}

警告そのものを出したくないなら、先に is_file() や is_readable() で確かめます。ただし、確かめてから読むまでの間にファイルが消されることもあるので、戻り値の確認は省きません。

無いかもしれない配列の要素は ?? で読む

@$_GET['page'] のように、無いかもしれない配列の要素を読むために @ を使っている場合は、?? (null 合体演算子) で置き換えられます。左側が無いか null のとき、右側の値になります。

<?php
$page = (int) ($_GET['page'] ?? 1);

エラーを例外に変える

警告を例外に変えておくと、try と catch で失敗を扱えます。多くのフレームワークも、同じような仕組みでエラーを例外として扱っています。

<?php
set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
    if (!(error_reporting() & $severity)) {
        // 報告しない設定のエラー (@ が付いたものを含む) は、PHP の通常の処理に任せる
        return false;
    }
    throw new ErrorException($message, 0, $severity, $file, $line);
});

try {
    $text = file_get_contents($path);
} catch (ErrorException $e) {
    error_log('読み込みに失敗しました: ' . $e->getMessage());
    $text = null;
}

どうしても @ を使うなら、すぐに error_get_last() を見る

ほかに手段が無く @ を使うときは、直後に error_get_last() で、隠したエラーの内容を取り出して記録します。@ で隠したエラーも、この関数で読めます。次のエラーで上書きされるので、すぐに確かめます。

<?php
$text = @file_get_contents($path);
if ($text === false) {
    $error = error_get_last();
    error_log('読み込みに失敗しました: ' . ($error['message'] ?? '不明なエラー'));
}

利用者にエラーを見せたくないとき

@ を使う理由が「利用者にエラーメッセージを見せたくない」なら、php.ini の設定で行うのが正しい方法です。本番では display_errors を Off にして画面に出さず、log_errors を On にしてログで確認します。開発の環境では display_errors を On にして、すぐに気づけるようにします。

; 本番の php.ini の例
display_errors = Off
log_errors = On
error_reporting = E_ALL

エラーメッセージには、ファイルのパスなど、外に見せたくない情報が含まれることがあります。表示を止めるのは、コードの @ ではなく設定の役目です。

まとめ

  • @ は、その式で出るエラーメッセージを表示しない演算子。失敗そのものは無くならない
  • PHP 8.0 からは、致命的なエラーは @ で隠れない。例外も @ では止まらない
  • 代わりに、戻り値を === で確かめる、?? を使う、エラーを例外に変えて try と catch で扱う
  • 使うなら、直後に error_get_last() で内容を記録する
  • 利用者に見せたくないなら、display_errors を Off、log_errors を On にする

参考