クラスは、メソッドの使い方を約束(契約)するものです:
<?php
class A {}
class B extends A {}
function foo(A $a) {}
function bar(B $b) {
foo($b);
}
?>
上のコードは型安全といえます。なぜなら、 B は A との契約を守っており、かつ共変性/反変性のマジックによって、 メソッドへのあらゆる期待が満たされるからです。例外は別です。
一方で、列挙型は case について契約するものです。メソッドではありません:
<?php
enum ErrorCode {
case SOMETHING_BROKE;
}
function quux(ErrorCode $errorCode)
{
// 以下のようなコードを書くと、全ての case をカバーします
match ($errorCode) {
ErrorCode::SOMETHING_BROKE => true,
}
}
?>
quux
関数内の match 式は、
ErrorCode の全ての case をカバーしているかを静的に解析できます。
ここで以下のように、列挙型が継承可能だとしましょう:
<?php
// 列挙型が final でなかったと仮定した、思考実験のコード
// このコードは実際には動作しないので注意
enum MoreErrorCode extends ErrorCode {
case PEBKAC;
}
function fot(MoreErrorCode $errorCode) {
quux($errorCode);
}
fot(MoreErrorCode::PEBKAC);
?>
通常の継承のルールでは、あるクラスの子クラスは親クラスの型チェックを通過します。
上のコードの問題点は、
quux()
関数の match 式が全ての case をカバーしてはいないということです。
なぜなら、MoreErrorCode::PEBKAC
がカバーされていないので、match 式が例外をスローするからです。
こうした理由から、列挙型は final 扱いであり、継承できなくなっています。