PHPのenum(PHP 8.1以降)は、取りうる値(ケース)の決まった集合を持つ型を定義します:enum Status { case Active; case Banned; }。ケースはStatus::Activeのように使い、Statusで型宣言した引数はそれ以外を受け付けません。Backed Enum(enum Status: string)は、各ケースに保存してStatus::from()で戻せる値も与えます。
enumができる前は、同じことを文字列の定数の一覧で書いていて、'actve'のようなタイプミスがデータベースまで届くのを止めるものはありませんでした。引数の型をStatusにすれば、渡せるのは3つのケースだけです。
Pure EnumとBacked Enum
Pure Enumのケースは名前だけです。Backed Enumは名前のあとにintかstringの型を宣言し、すべてのケースがその型の一意な値を持つ必要があります。どのケースにも->nameがあり、->valueがあるのはBackedのケースだけです。
データベースのカラム、URL、フォームの項目、JSONのように、値がプログラムの外に出るならBacked Enumを選びましょう。パーサーの状態のように、コードの中にしか存在しない値ならPure Enumを選びます。
from()とtryFrom()で値を変換する
Backed Enumには、保存した値をケースに戻す2つの静的メソッドがあります。from()は未知の値にValueErrorを投げ、tryFrom()はnullを返すので、既定値のための??と相性がよいです。
ユーザーから送られたものにはtryFrom()を、自分のコードが書いた値にはfrom()を使います。後者では未知の値はバグを意味し、知らせてほしいからです。引数は普通の引数の型ルールに従います。intのenumでは、デフォルトのモードなら'5'が先に5に変換されるのでPriority::from('5')はPriority::Normalを返し、Priority::from('5x')はTypeErrorで、declare(strict_types=1)の下ではどんな文字列もTypeErrorです。tryFrom()も同じで、Priority::tryFrom('abc')はnullを返さずに例外を投げるので、intのenumに対するフォームの入力は先にfilter_var($raw, FILTER_VALIDATE_INT)で確認しましょう。
cases()ですべてのケースを一覧にする
Enum::cases()は、宣言順にすべてのケースの配列を返します。ドロップダウンを作ったり、リストを検証したり、array_columnですべての値を取得したりする方法です。
実行してプランを選び、Chooseを押すと、$_POST['plan']に値が入った状態で同じスクリプトがもう一度実行され、tryFrom()が送信された文字列をPlanのケースに戻します。plan=goldのような偽造された値はnullになり、どのオプションも選択されません。
enumのメソッド、定数、インターフェース
enumはメソッド、静的メソッド、定数を持て、インターフェースを実装できます。メソッドの中では$thisが現在のケースなので、各ケースにデータを結びつけるにはmatch ($this)が自然な方法です。enumが持てないのはプロパティで、ケースごとのデータはメソッドから得ます。
matchでのenum
matchは===で比較し、enumのケースは自分自身とだけ同一なので、matchとenumは相性がよいです。ケースを書き忘れ、そのケースがmatchに届くと、PHPは黙って何も返さない代わりにUnhandledMatchErrorを投げます。
matchとswitchの違いについてはmatchを参照してください。
JSONと文字列でのenum
json_encode()はBackedのケースをその値として書き出します。Pure Enumはエンコードできず、enumは文字列ではなくオブジェクトなので、どのケースもechoでは失敗します。代わりに->valueか->nameを出力しましょう。
echo Role::Admin;はError(「Object of class Role could not be converted to string」)を投げ、enumは__toString()を実装できません。戻すときはJSONから普通の文字列が来るので、示したとおりfrom()かtryFrom()で変換します。エンコードについて詳しくはjson_encodeを参照してください。
enumのよくあるミス
何度も出てくるエラーが3つあります。ケースは決してその値と等しくならない、ケースは配列のキーにできない、enumにnewは使えない、の3つです。
ケースそのものをキーにしたマップが必要なら、オブジェクトをキーとして受け付けるSplObjectStorageかWeakMapを使います。
よくある質問
PHPでenumの値を取得するには?
Backed Enumなら->valueを読みます:Status::Active->valueは'active'です。どのケースにも、ケース名を文字列で表す->name('Active')があります。Pure Enum(: stringや: intなし)には->nameしかありません。
PHPのenumのfrom()とtryFrom()の違いは何ですか?
どちらもBacked Enumの値をケースに変換します。Status::from('active')はケースを返し、その値を持つケースがなければValueErrorを投げます。Status::tryFrom('nope')は代わりにnullを返します。ユーザーの入力にはtryFrom()を、不正な値がバグであるときはfrom()を使います。
PHPでenumのすべての値を取得するには?
Status::cases()は宣言順にすべてのケースを返します。Backed Enumの値にはarray_column(Status::cases(), 'value')を、名前にはarray_column(Status::cases(), 'name')を使います。
Pure EnumとBacked Enumの違いは何ですか?
Pure Enum(enum Suit { case Hearts; })のケースは名前だけです。Backed Enum(enum Suit: string { case Hearts = 'H'; })は各ケースに一意なintかstringの値を与え、データベースへの保存、フォームやJSONへの出力、from()やtryFrom()での変換に必要です。
PHPのenumはメソッドを持てますか?
持てます。enumはメソッド、静的メソッド、定数を持て、インターフェースを実装できます。メソッドの中では$thisが現在のケースなので、return match ($this) { self::Active => 'green', self::Banned => 'red' };がよく使われるパターンです。enumはプロパティを持てません。