PHP の Error/Exception の分類方法やまとめ方を考えてみたときのメモ。

## LogicException と RuntimeException の、子クラスの関連

Logic | Runtime | 意味
:-- | :-- | :--
Domain | Range | 変数が、事前に定義された範囲から逸脱してる。
Out Of Range | Out Of Bounds | 無効なインデックス、キー。
Invalid Argument | Unexpected Value | 関数側か、関数の呼び出し側か。
Length | Overflow / Underflow | すでに長さが変だった / 操作したら長さが変になる。

説明 | Logic | Runtime | 説明 | 共通してそうなもの
:-- | :-- | :-- | :-- | :--
a value does not adhere to a defined valid data domain.  | Domain | Range | indicate range errors during program execution. Normally this means there was an arithmetic error other than under/overflow. | 変数が、事前に定義された範囲から逸脱してる。
when an illegal index was requested.  | Out Of Range | Out Of Bounds | if a value is not a valid key. | 無効なインデックス、キー。
xxx | Invalid Argument | Unexpected Value | xxx | 関数側か、関数の呼び出し側か。
if a length is invalid. | Length | Overflow / Underflow | xxx | すでに長さが変だった / 操作したら長さが変になる。

## Domain と Range

単純に、Logic Exception か Runtime Exception か。

Domain は「事前に定義されたドメインデータ」に含まれない、という例外。
Range は「事前に定義された境界が明確なデータの範囲」から逸脱する、という例外。

[RangeException の説明](https://www.php.net/manual/ja/class.rangeexception.php)に「アンダーフローやオーバーフロー以外の計算エラーが発生した」とあるので、「なにか操作をしようと思ったら変だった（Domain）」「なにか操作をしたら、変になった（Range）」と言い換えることもできる。
（「ドメインデータに含まれない」は、「使おうと思ったらデータがおかしかった」と言い換えることができるので。）

Domain と Range が同じ意味合いで対応する という話は、RangeException の説明に「これ（RangeEaception）は実行時版の DomainException です」とあるので確定。

## OutOfRange と OutOfBounds

単純に、Logic Exception か Runtime Exception か。

TypeScript の場合、「コンパイル時に検出」は型定義でできるので、Out of Range は使用する機会がない。

~~Valid な値が、列挙するのが馬鹿らしいほどの数がある場合に使う例外。
データを列挙できるかできないか？という点で、Domain/Range と OutOfRange/OutOfBounds を使い分ける。（という使い分けでいいのか？）~~

OutOfRange は、配列やタプルに対して、インデックス（ほとんどの場合で自然数）に入っているデータが決まっている場合に、未定義な場所を参照しようとして発生する Null Pointer Exception 的なものだと思う。
例えば `foo: [string, number, string]` というタプルにおいて `foo[5]` とアクセスした場合に OutOfRange になる。
で、PHPではタプルを型定義できないけれど、タプルの不整合はコンパイル時に検出可能なので（例TypeScript の場合は例の通り）、ドキュメントに書かれている通り「コンパイル時に検出できる」。

OutOfBounds は、配列やタプルではなく、連想配列や Map に対して、使おうとしたキー名が Invalid な場合に発生する。

## InvalidArgument と UnexpectedValue

Invalid Argument は呼び出された関数側で throw する。
普通に引数の Validation したときに Invalid なら投げる。

Unexpected Value は関数を呼び出した側で throw する。
関数を呼び出して戻ってきた値が意図した通りになっているかを確認する用途。

例えば https://github.com/sebastianbergmann/diff/blob/master/tests/Utils/UnifiedDiffAssertTrait.php みたいなやつ。

## フォームから送られてきた「セレクトボックスやラジオボタンで選んだ値」の改ざんのときは、 DomainException でもいいんじゃね？

**そもそも Validation の文脈で例外を投げようっていう発想がダメ**

~~**そもそも Domain か OutOfBound で迷ってる時点でおかしいんじゃね？**~~

いまは、フロントエンドが実装をミスってる可能性もあるので、「一応確認してくれ」という意図で、Domain Exception にしておくのでもいい気がしている。

~~まぁ、サーバサイドから見たら「外部から来たデータ」であることに変わりはないので、LogicException は不適切なので、Out Of Bounds にしよう派なので、自分で実装するときは Out Of Bounds にするけど。~~

## JavaScript の Error 3種類と、PHP の Exception の対応について

JavaScript には、PHP の例外のような使い方をする（と思われる） Error が3つある。

- [`TypeError`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/TypeError): 値が期待される型でない場合のエラー
- [`RangeError`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RangeError): 値が配列内に存在しない、または値が許容範囲にない場合のエラー
- [`ReferenceError`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError): 存在しない変数が参照された場合のエラー

これらは、もしかして PHP の Domain/Range OutOfXxx InvalidXxx のそれぞれに対応するんじゃね？と思ったりした。

ただし、 `ReferenceError` は Undefined な **変数** にアクセスしようとした場合の話であって、 オブジェクトの Undefined なキー ではない（はず）なので、PHP で言うところの Error レベルの話で、Exception と対比させるのは間違いかもしれない。

PHP Exception | JavaScript Error | 意味
:-- | :-- | :--
~~`Domain` `Range`~~ | ~~`Reference`~~ | ~~おそらく列挙できるので。ほんとは変数の話だから、 `RangeError` と見るべきかもしれない。~~
`OutOfRange` `OutOfBound` | `Range` | まぁ、普通に範囲エラーだよな。
`InvalidArgument` `UnexpectedValue` | `Type` | 用途的に考えて同じ。

## 処理フロー順で発生しそうな順番を考えてみる

1. 受け取った変数の型がおかしい: `InvalidArgument` :Logic
  1. 変数の Length がおかしい: `Length` :Logic
  1. 必要なインデックスがない: `OutOfRange` :Logic
  1. その他、なにかが明らかにおかしい: `Domain`
1. （なにか処理）
1. 必要なキーがなかった: `OutOfBounds`
1. Length がおかしくなった: `Overflow` `Underflow`
1. その他、なにかが明らかにおかしくなった: `Range`
1. 関数呼び出し側でのみ知りうる情報と照らし合わせて、戻ってきた値がなにかおかしい: `UnexpectedValue`

~~こうやってみると、 Length や Domain になっているものがなぜ InvalidArgument ではないのか？という疑問が出そう。
Length や Domain は InvalidArgument のより具体的な例外 って扱いなのかな？~~

~~Domain/Range は、コンパイル時に検出可能・不可能って話をしているのだから、意味的には同じになるけれど、実装レベルでは全然違うタイミングになる、みたいなアレはあるかも？わからん~~

継承関係を再整理すると、こうなるんじゃね？

![](http://www.plantuml.com/plantuml/proxy?src=https://gist.githubusercontent.com/ndxbn/163e8da50f64d6819f96ea3457a48a68/raw/3534e466b0d3766f83dd70c2bec95d136fc42c07/php_error_exception_structure_rearrengement.puml)

## 付録

### PHP の Error/Exception の継承関係のクラス図

![](http://www.plantuml.com/plantuml/proxy?src=https://gist.githubusercontent.com/ndxbn/163e8da50f64d6819f96ea3457a48a68/raw/56ebd4dffb87624f38689a82de07ed7f91aa033e/php_error_exception_structure.puml)

[生成元の Plant UML のソースはこちら](https://gist.github.com/ndxbn/163e8da50f64d6819f96ea3457a48a68#file-php_error_exception_structure-puml)。