PHP8の組み込み例外まとめ
きっかけ
PHP7時代から徐々に、PHP8が使われ始めて個人的にも知りたかったので、まとめてみました。
細かい用途を詳しく知りたかったため、それ以外はおまけです。大まかな分類を知りたい方はmpywさんのこの記事を参照したほうが良いと思います。
まずは、Throwableなクラスがどのような継承関係にあるのか以下にまとめてみました。
※インデントは継承関係
※(新)マークは7.2と比べて新しく追加されたもの。
cf.)https://www.php.net/manual/ja/class.error
Error
ArithmeticError
DivisionByZeroError
AssertionError
CompileError(新)
ParseError
FiberError(新)
TypeError
ArgumentCountError
UnhandledMatchError(新)
ValueError(新)
Exception
ClosedGeneratorException
DOMException
ErrorException
IntlException
JsonException(新)
LogicException
BadFunctionCallException
BadMethodCallException
DomainException
InvalidArgumentException
LengthException
OutOfRangeException
PharException
ReflectionException
RuntimeException
OutOfBoundsException
OverflowException
PDOException
RangeException
UnderflowException
UnexpectedValueException
SodiumException
LogicExceptionとRuntimeExceptionの違い
例外を扱う際に最大の問題が、このLogicExceptionとRuntimeExceptionの違いだと思います。
まず、PHPのドキュメントにおける定義に関して見ていきましょう。
- LogicException
プログラムのロジック内でのエラーを表す例外です。 この類の例外が出た場合は、自分が書いたコードを修正すべきです。
(英原文:Exception that represents error in the program logic. This kind of exception should lead directly to a fix in your code.)
- RuntimeException
実行時にだけ発生するようなエラーの際にスローされます。
(英原文:Exception thrown if an error which can only be found on runtime occurs.)
この文から読み取れることとして
LogicExceptionはプログラムを組む際に起きる例外として、定義されます。
つまり、アプリケーションとして体をなさないような内部処理を行うだけのライブラリ(DBなどの接続を取り持つようなものは除くという意味)では、LogicExceptionをthrowするのが推奨されています。
それに対して、RuntimeExceptionではエンドユーザから送られてくるものが異常だったり、外部システム側の通信障害など、開発側では対応することのできない例外が発生したときに、throwするものです。
特にPDOExceptionがRuntimeExceptionに所属していることが、わかりやすいでしょう。
イメージとして、LogicExceptionはクリーンアーキテクチャの文脈において、より内側の方で発生する例外、RuntimeExceptionはより外側で発生する例外。となります。
つまり、LogicExceptionを捕捉して、RuntimeExceptionとして投げ直すことはあっても、
RuntimeExceptionを捕捉して、LogicExceptionを投げ直すことはありえません。
これを念頭においた上で、各例外の詳細を見てみましょう。
各例外の詳細
Throwable
全ての例外はこのInterfaceを実装しています。
また、このInterfaceを継承していないオブジェクトをthrowすることはできません。
Exception
全てのユーザランドコードから投げられる例外はこれを継承したものをthrowするのを推奨しています。
Exceptionを直接継承している例外
- ClosedGeneratorException
- DOMException
- JsonException
- IntlException
- PharException
- ReflectionException
- SodiumException
これらの例外は各ライブラリからthrowされる例外で、基本的にユーザがこれらをthrowすることはないです。(多分)
ErrorException
特殊な例外です。
最初にご紹介させていただいたmpywさんの記事を参照してください。
LogicException
BadFunctionCallException
LogicExceptionを親に持ちます。
不正な関数実行をした時に、throwする例外です。
類似の例外はRuntimeException側にはありません。
BadMethodCallException
BadfunctionCallExceptionを親に持ちます。
不正な関数実行が行われたうち、特に対象がクラスのメソッドだった場合にthrowする例外です。
同様に類似の例外はRuntimeException側にありまあせん。
DomainException
LogicExceptionを親に持ちます。
そのうち、定義したデータドメインに値が従わないときthrowされます。
RuntimeException側の類似の例外として、RangeExceptionが挙げられます。
ユーザ入力などによって、引き起こされる場合RangeExeptionを、値を引き回している途中で起こる場合、こちらを使用することになります。
InvalidArgumentException
LogicExceptionを親に持ちます。
引数の型が一致しなかった場合に、throwする例外です。
RuntimeException側の類似の例外として、UnexpectedExceptionが挙げられます。
LengthException
OutOfRangeException
LogicExceptionを親に持ちます。
配列(連想配列を含む)のキーが見つからない場合にthrowされるれ以外です。
RuntimeException側の類似の例外として、OutOfBoundsExceptionが挙げられます。
RuntimeException
RangeException
DomainExceptionのRuntimeException版です。
UnexpectedValueException
InvalidArgumentExceptionのRuntimeException版です。
OutOfBoundsException
OutofRangeExceptionのRuntimeException版です。
OverflowException
予期しない数値のオーバフローが起こった際に起こすべき例外です。
これは、PHP上での実装問題としてではなく、コンピュータ自体のアーキテクチャの要請としての例外のため、RuntimeExceptionを継承しているようです。
UnderflowException
上記のアンダーフロー版です。
PDOException
データベース接続用オブジェクトPDOの起こす例外です。
これは外部システムによって起こる、例外のため、RuntimeExceptionになっています。
(正確には、SQLの間違いなどにおいても起こりますが、基本的に外部要因なので、RuntimeExceptionになっている、と考えれば良いと思います。)
Error
全てのPHP内部エラーを表すクラスです。
これ自体をユーザ側で実装することはありませんが、catchする際は意識する必要があります。
ArithmeticError
関数などの処理で数学的にありえない引数の渡し方、呼び出し方をした際に発生するErrorです。
DivisionByZeroError
その名の通り、0での除算を行った際に発生するErrorです。
親がArithmeticError。
AssertionError
assert関数に失敗したときに発生するError。
CompileError
新設のエラーです。
もともと、ParseErrorとErrorが直接継承関係にありましたが、これが挟まるようになりました。
新設された背景としては、JITやOpCache周りの拡充などがあると考えられます。(勝手な予想)
ParseError
親がErrorからCompileErrorに変更されました。
requireやinclude、evalなどの、コード内でコードを評価するような関数を使用したときに、パースが失敗すると発生します。
FiberError
新設のErrorです。
同様にPHP8で追加された非同期処理用クラスFiberの取り扱いを間違った際に発生します。
TypeError
型が間違った場合に発生します。
発生する条件は下記
- クラスのプロパティに設定されている値が、プロパティで宣言されている型と一致しない場合。
- 関数に渡された引数の型が、関数の宣言時に指定された型と一致しない場合。
- 関数の戻り値の型が、関数の宣言時に指定された型と一致しない場合。
cf.) https://www.php.net/manual/ja/class.typeerror
ArgumentCountError
関数あるいはメソッドに渡された引数が少なすぎる場合にスローされます。
注) 多い場合は何も起こりません。
UnhandledMatchError
新設のErrorです。
switchと比較してmatch式は必ずいずれかの分岐に入る想定で実装されています。
どの分岐にも一致しなかった場合、このErrorが発生します。
ValueError
PHP8に移行するときの最大の敵です
今までPHPの組み込み関数では、その処理に失敗した際、falseを返すものが多くありました。
しかし、これからはこのErrorが返るようになります。
ValueErrorはユーザランドで新しくthrowすることも可能ですが、基本的にはPHPの組み込み関数からthrowされるべきです。
Discussion