• PHP
【php8上級/準上級試験】模擬問題解説 問題8. error_reporting set_error_handler() 例外捕捉

「第1回 PHP 8 上級 模擬試験」を解説していきます

本記事ではひたすら下記記事の模擬試験の解説をしていきたいと思います!

第1回 PHP 8 上級 模擬試験
https://study.prime-strategy.co.jp/study/ph8ex1/

解説記事一覧

模擬問題 8

エラーに関する説明の中で、誤っているものを1つ選びなさい。
なお「\」はバックスラッシュに読み替えること。
また、すべてのコードの先頭には下記のコードが書かれているものとする。

declare(strict_types=1);
error_reporting(-1);
下記はマニュアルから一部引用した内容である。

Exception implements Throwable { }
Error implements Throwable { }

PHP で「何をエラーとして出力し、何をエラーとして出力しないか」の制御は、php.ini の error_reporting で行う。
プログラム実行時に ini_set() 関数を使っても設定できるが、error_reporting() という専用の関数も存在する。
ここには定数 (E_ERROR や E_NOTICE など) を、複数指定する場合はビット和演算子等をつかって指定する事が多い。値は int のため直接数値を入れてもよいが、定数を使う事が推奨されている。
定数には色々あるが、数値で 0 を指定すると「全てのエラー出力をオフにする」事ができる。
error_reporting() 関数で -1 を渡す事があるが、これは「将来のバージョンの PHP で新しいレベルと定数が追加されたとしてもすべてのエラーを表示するようになる (2の補数で、-1は「全てのbitが立つ」値になる)」という意味で使われる。
そのため、以下のコード

error_reporting(0);
$i++;
var_dump($i);

error_reporting(-1);
$j++;
var_dump($j);

を実行すると、結果は次のとおりとなる。

int(1)
Warning: Undefined variable $j in …
int(1)

error_reporting

PHP ではどのエラーを出力するかを、php.ini の error_reporting で設定します。

また表示方法については下記があります

  • 数値で指定
  • E_NOTICE や E_WARNING などの定数をビット演算子で組み合わせて「これは表示」「これは非表示」と個別に設定
定数数値主な意味
E_ERROR1致命的なランタイムエラー(処理停止)
E_WARNING2警告(処理は継続可能)
E_PARSE4コンパイル時のパースエラー
E_NOTICE8通知(気をつけたいが致命的ではない)
E_DEPRECATED8192非推奨のコードを使用(将来削除予定)
E_ALL32767ほぼすべてのエラーを表示
00すべてのエラー非表示
-1-1現行および将来のすべてのエラー表示

そこで選択肢の内容をみてみると

error_reporting(定数や数値)

  • PHPがどのレベルのエラーや警告を画面に出すかを指定する設定です。
  • error_reporting(0) は「全部のエラーを出さない」設定
  • error_reporting(-1) は「将来追加されるものも含めて全てのエラーを出す」設定(-1 は全ビットが立っている値)

なぜ $i の未定義警告は出ない?

  • 先に error_reporting(0) を呼び出しているため、すべてのエラーが非表示になっています。
  • その状態で $i++ をしても、エラーや警告は出ません。
  • 未定義だけど、自動的に 0 として1加算されて 1 になる → var_dump($i);でint(1)となる

なぜ $j の未定義警告は出る?

  • $j++ の直前に error_reporting(-1) を呼び出して、あらゆるエラーを表示する設定に戻しているからです。
  • そのため $j が未定義だと警告が表示されます。

上記より選択肢の内容は⭕です

set_error_handler() を使うと、ユーザー定義のエラーハンドラ関数を設定する事ができる。
これを使うと「致命的なエラーの際になんらかの後処理が必要な場合」などにその処理を記述できる。
また、書き方によっては「PHP の標準関数のエラー時に、例外を投げる事」や「E_NOTICE であっても例外を投げる」事も可能である。
そのため、以下のコード

set_error_handler(
    function ($errno, $errstr, $errfile, $errline) {
        if (0 !== $errno & error_reporting()) {
            throw new ErrorException( $errstr, 0, $errno, $errfile, $errline);
        }
    }
);

try {
    $i++;
} catch(\Throwable $e) {
    echo 'catch Exception', PHP_EOL;
    echo $e->getMessage(), PHP_EOL;
}

を実行すると、結果は次のとおりとなる。

catch Exception
Undefined variable $i

set_error_handler()

set_error_handler()は、PHPの標準エラー処理を、自分で作ったエラー処理に置き換えることができます。

エラー発生 set_error_handler による処理 独自の処理(ログ記録・例外変換など)

この関数は以下のパラメータを受け取ります:

set_error_handler の引数 function($errno, $errstr, $errfile, $errline) $errno エラーの種類: E_WARNING(2), E_NOTICE(8), E_ERROR(1)など $errstr エラーメッセージ: “Undefined variable”, “Division by zero”など $errfile エラーが発生したファイル名: “/var/www/html/index.php” $errline エラーが発生した行番号: 123

上記より選択肢の内容は⭕です

PHP のエラーのうち、E_ERRORは「重大な実行時エラー」、E_PARSE は「コンパイル時のパースエラー」となり、いずれもスクリプトの実行は中断される。
一方で、E_WARNING「実行時の警告 (致命的なエラーではない)」とE_NOTICE「実行時の警告」は、スクリプトの実行は中断されない。
そのため、以下のコード

$i++;
var_dump($i);
echo 'fin.';

を実行すると、結果は次のとおりとなる。

Warning: Undefined variable $i in …
int(1)
fin.

致命的なエラー (E_ERROR, E_PARSE)と警告 (E_WARNING, E_NOTICE)

PHP には、実行中に発生するエラーがいくつかあり、そのうち「致命的なエラー」と「警告」の2種類に大別されます。

PHPエラーの動作 PHPエラー発生 致命的エラー? E_ERROR / E_PARSE 実行中断 E_WARNING / E_NOTICE スクリプト継続 例: $i++; var_dump($i); echo ‘fin.’; (Warning発生 → 継続実行)

E_WARNING や E_NOTICE は警告として表示されるだけでプログラムの実行は止まらないのに対し、E_ERROR や E_PARSE はエラーが発生するとスクリプト全体が停止してしまう、という違いがあります

よって選択肢の内容はWarningで警告でスクリプトは停止せず処理は継続してされており、内容は正解⭕です

PHP 5 において、Exception クラスは全ての例外の基底クラスであった。PHP 7 において、Exception クラスは「Throwable インタフェース」を基底クラスとしている。
また、PHP 7 以降において Error クラスという「PHP のすべての内部エラーの基底クラス」ができた。
そのため、以下のコード

try {
    throw new \Exception('Exception');
} catch(\Throwable $e) {
    echo 'catch ' , $e->getMessage();
}

を実行すると、結果は catch Exception となる。
同様に、以下のコード

try {
    throw new \Error('Error');
} catch(\Exception $e) {
    echo 'catch ' , $e->getMessage();
}

を実行すると、結果は catch Error となる。

PHP 7 以降はエラーと例外で補足方法が分かれてます!

PHP 7以降では、エラー(\Error)と例外(\Exception)が区別されるようになりました。

PHP 5投げる例外オブジェクトがすべてException オブジェクト
PHP 7 以降throwで投げるオブジェクトは必ずしもすべてが \Exception のインスタンスというわけではありません
代わりに共通のインターフェースである \Throwable を実装していればよくなりました。

問題文では、\Error を catch(\Exception $e) で捕まえようとしていますが、これは間違い❌です。

なぜなら、PHP 7 以降では、\Error は \Exception の子ではなく、独立したクラスになっているため、\Error を捕捉するには catch(\Error $e) と書く必要があります。

もし、\Error と \Exception の両方をまとめて捕捉したい場合は、どちらも「Throwable」という共通のインターフェースを実装しているので、catch(\Throwable $e) と書けばOKです。

PHP 7: エラーと例外の捕捉方法 【誤った捕捉例】 <?php try { throw new \Error(‘Error’); } catch(\Exception $e) { … } // \Error は捕捉されない 【正しい捕捉例】 <?php try { throw new \Error(‘Error’); } catch(\Error $e) { … } // または catch(\Throwable $e) でも可 共通インターフェース: Throwable \Error と \Exception は、どちらも Throwable インターフェースを実装している → 両方をまとめて捕捉する場合は catch(\Throwable $e) を使います

正解一覧

以下は、各問題番号とその正解の選択肢をまとめた表です。

問題番号正解の選択肢
1PHPの「最新以外の(古い)コード」は、公式サイトでの提供は全くしていない。そのため、古いバージョンのコードが必要な場合、別途「非公式の外部サイト」からソースコードを入手する必要がある。
2論理型 (boolean) は「真偽値」とも呼ばれ、値は true か false か null のいずれかになる。なお、true、false、null の文字は、大文字でも小文字でもよい。
3抽象クラスから継承する際、親クラスで abstract としてマークされた全てのメソッドは子クラスで定義する必要があり、可視性は同等(またはより緩い制約)で、必須引数の数は同じであれば型宣言が異なってもかまわない。
4PHP において、デストラクタは __destruct() メソッドで実装される。親クラスのデストラクタは暗黙的に呼ばれ、呼び出し順序は「子クラスのデストラクタ → 親クラスのデストラクタ」となる。
5__get() はアクセス不能(protected または private)または存在しないプロパティからデータを読み込む際に使用される。なお、__getStatic() は存在せず、オブジェクトや静的コンテキストで動作しない。
6PHP のリファレンス渡しを使用すると、関数内で引数を修正できる。(※※正解テキストでは「呼び出す側で変数に & を付ける必要がある」と記載されています。)
7名前空間は namespace キーワードで宣言する。通常はファイルの先頭に記述する必要があり、名前空間宣言前に書かれたクラスはその名前空間に含まれない。
8PHP 5 では Exception クラスが全例外の基底クラスだったが、PHP 7 以降は Exception クラスは Throwable インタフェースを基底とし、Error クラスが内部エラーの基底クラスとして導入された。
9ArrayAccess インターフェイスは、オブジェクトを配列としてアクセスするための機能を提供する。
10SplFileInfo クラスは、ファイルの情報取得や操作を行うためのクラスである。
11$_COOKIE は、HTTP クッキーから渡された値が連想配列として格納され、また設定も可能である。
12PHP 7.1.x では、文字列操作関数で負のオフセット指定が可能となり、[] や {} による文字単位のアクセスも文字列の末尾からのオフセットとして解釈される。
13可変変数は、スーパーグローバル変数にも使用できる。
14PHP 7.4.x では、波括弧を使った配列や文字列のオフセットアクセスの文法は非推奨となった。
15PHP 8 では、private メソッドの継承に関するルールが変更され、親クラスの同名メソッドの可視性に関係なく子クラスでオーバーライド可能となった。
16break は、現在実行中の for, foreach, while, do-while, switch 構造の実行を終了し、オプションでネストしたループの何段分を抜けるか指定できる。
17XSS 対策として、htmlentities() を適切に使用することで、文字列や配列の入力に対して安全な出力が可能となる。
18アップロードされたファイルの元のファイル名は、$_FILES[‘{formのnameの値}’][‘name’] に格納され、move_uploaded_file() の第二引数として利用できる。
19uniqid() と mt_rand() を組み合わせることで、暗号論的にランダムな(推測困難な)トークンを生成できる。
20PHP のセッションでは、セッション ID がクッキーに保存され、session_set_cookie_params() によりそのクッキーのパラメータを設定できる。
21PHP の変数の参照カウントは、xdebug_debug_zval() で確認でき、オブジェクトを clone した場合は内部的に参照が使われ、一時的に参照カウントが増加する。
22DirectoryIterator クラスは、ディレクトリ内のファイルやサブディレクトリの情報を取得するシンプルなインターフェイスを提供する。
23mail() 関数は、メールを送信するための関数で、第四引数で追加のヘッダー情報を指定できる。
24escapeshellarg() と escapeshellcmd() は、外部入力をシェルコマンドの引数として使用する際に必要なエスケープ処理を行い、どちらを使用しても意味合いは同じである。
25stream_wrapper_register() 関数を使用すると、新しいストリームラッパー(プロトコルハンドラ)を登録できるが、既に存在する場合は失敗する。
26strpos() 関数は、文字列内で指定した部分文字列の最初の出現位置を返し、見つからなければ false を返す。ただし、先頭位置の場合は 0 が返るため注意が必要。
27Phar のスタブには __HALT_COMPILER() が使用され、これ以降のコードはコンパイルされない。
28function_exists() 関数は、指定された関数が定義されているかどうかをチェックし、存在すれば true を返す。
29openssl_decrypt() 関数は、openssl_encrypt() で暗号化されたデータを正しく復号し、元のデータを取り戻す。
30strtotime() 関数は、英語形式の日付文字列を Unix タイムスタンプに変換し、無効な日付の場合は false を返す。