こんにちは。よっしーです(^^)
今日は、PHPの列挙型(Enum)についてご紹介します。
背景
PHP8.2を利用したAPIを開発しているときに列挙型(Enum)を利用したので、そのときの調査内容を備忘としてのこしました。
こちらのサイトを参考にしています。
列挙型(Enum)拡張できない理由
列挙型(Enum)が拡張できない理由は、列挙型の性質に起因しています。列挙型はメソッドに対する契約を持たないのではなく、ケースに対する契約を持っています。
通常のクラスの継承の例を考えてみましょう:
class A {}
class B extends A {}
function foo(A $a) {}
function bar(B $b) {
foo($b);
}
このコードは型安全です。なぜなら、BはAの契約に従っており、共変性/反変性の魔法により、メソッドに関する期待が保存されるためです。
しかし、列挙型はケースに対する契約を持ちます。例えば:
enum ErrorCode {
case SOMETHING_BROKE;
}
function quux(ErrorCode $errorCode)
{
// このコードはすべてのケースをカバーするように見えます
match ($errorCode) {
ErrorCode::SOMETHING_BROKE => true,
}
}
関数quux内のmatch文は、ErrorCodeのすべてのケースをカバーすることが静的に分析されます。
しかし、列挙型を拡張できる場合を考えてみましょう:
// 列挙型が拡張できる仮想のコード
enum MoreErrorCode extends ErrorCode {
case PEBKAC;
}
function fot(MoreErrorCode $errorCode) {
quux($errorCode);
}
fot(MoreErrorCode::PEBKAC);
通常の継承ルールに従うと、別の列挙型が親列挙型を拡張した場合、型チェックは通過するでしょう。
問題は、quux()内のmatch文がすべてのケースをカバーしなくなることです。MoreErrorCode::PEBKACについては知らないため、match文は例外をスローします。
このような理由から、列挙型は拡張できず、finalである必要があります。列挙型はその特定のケースに対して厳格な契約を持つため、拡張可能にすることは型の安全性を損なう可能性があるためです。
列挙型(Enum)サンプル
以下は、PHPの列挙型の使用例です。
例1: 基本的な制限された値
enum SortOrder
{
case Asc;
case Desc;
}
function query($fields, $filter, SortOrder $order = SortOrder::Asc) { ... }
この例では、query()
関数は $order
が SortOrder::Asc
または SortOrder::Desc
であることが保証されているため、エラーチェックやテストは不要です。それ以外の値が渡された場合、TypeError が発生するためです。
例2: 発展的な排他的な値
enum UserStatus: string
{
case Pending = 'P';
case Active = 'A';
case Suspended = 'S';
case CanceledByUser = 'C';
public function label(): string
{
return match($this) {
static::Pending => 'Pending',
static::Active => 'Active',
static::Suspended => 'Suspended',
static::CanceledByUser => 'Canceled by user',
};
}
}
この例では、ユーザーステータスは UserStatus::Pending、UserStatus::Active、UserStatus::Suspended、または UserStatus::CanceledByUser のいずれかで、排他的にそれらの値しか受け入れないことができます。関数はパラメータをUserStatusに対して型指定し、これらの4つの値のみを受け入れます。
これらの4つの値は label()
メソッドを持ち、人が読みやすい文字列を返します。この文字列は、データベースフィールドやHTMLセレクトボックスなどで使用できる「マシン名」のスカラー等価文字列とは独立しています。
foreach (UserStatus::cases() as $case) {
printf('<option value="%s">%s</option>\n', $case->value, $case->label());
}
このコードは、UserStatus::cases()
を使用して、すべての列挙型ケースを取得し、それぞれのラベルをHTMLの <option>
要素として出力します。
おわりに
今日は、PHPの列挙型(Enum)についてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント