TekRog
thumbnail

【PHP8.1】列挙型(Enum)について徹底解説

更新日:(作成日: )

カテゴリー:

シェア!

PHPの列挙型について解説します。PHPの列挙型はメソッドを持つことが出来たり、インターフェースの実装、トレイトの利用も可能で様々な表現ができます。本記事は公式ドキュメントをもとに解説しますが、要点のみを抑えて簡潔な表現を心がけたり、具体例を増やすことによって分かりやすさに重点を置いています。

列挙型の概要

列挙型は、開発者が独自に定義した値のみを取る型です。enumで宣言します。
以下に具体例を挙げます。

<?php enum Size { case Small; case Medium; case Large; }

Sizeというenumを宣言しました。
このときSizeは、caseで指定した3つの値(Small, Medium, Large)のみを取ります。
caseは0個以上、好きなだけ定義することが可能です。

次のようにして::演算子でアクセスします。

$a = Size::Small; $b = Size::Large;

これが一体何の役に立つかというと、例えば、関数の引数の値を限定できて型安全になります。
次のコードはSizeで定義された3つのcaseのみを受け取ることが保証されます。

function hoge (Size $size) { var_dump($size); } hoge(Size::Small); //OK //hoge(3); //NG

概要を押さえたところで、PHPの列挙型について詳しく取り上げていきます。

(参考) 列挙型はPHP8.1からの新機能です。

PHP8.1には他にも多くの新機能があります。

列挙型のインスタンス

列挙型はクラスに似ています。::演算子で参照されたcase値は、列挙型のインスタンスとなります。

$a = Size::Small; var_dump($a instanceof Size); //bool(true)

また、このインスタンスはシングルトンです。

$a = Size::Small; $b = Size::Small; var_dump($a === $b); //bool(true)

それぞれのインスタンスは読み取り専用のnameプロパティを持ちます。caseで定義した名前そのものを返します。

$a = Size::Small; $b = Size::Medium; var_dump($a->name); //string(5) "Small" var_dump($b->name); //string(6) "Medium"

Pure EnumとBacked Enum

列挙型にはPure EnumとBacked Enumがあります。

前章のように定義された列挙型をPure Enumと言います。
再掲します。

enum Size { case Small; case Medium; case Large; }

もう一方で、次のように定義したEnumをBacked Enumと言います。

enum Size: string { case Small = "s"; case Medium = "m"; case Large = "l"; }

その特徴は、case式が右辺にスカラー値を持たせることが可能なことです。
int型かstring型のみを持つことができ、その型を列挙型名の後ろの:の後に記述します。
ただし、スカラー値はすべてユニークにする必要があります。

valueプロパティを使うことで、定義したスカラー値を参照することが可能です。

$a = Size::Small; $b = Size::Medium; var_dump($a->value); //string(1) "s" var_dump($b->value); //string(1) "m"

また、fromメソッドを用いることでスカラー値からcase名を取得することも可能です。
これには2つのメソッドがあり、該当case名がない場合にエラーを投げる from()メソッドと、null値を返すtryFrom()があります。

$a = Size::from("s"); var_dump($a); //enum(Size::Small) $b = Size::tryFrom("hoge") ?? Size::Large; var_dump($b); //enum(Size::Large)

列挙型とメソッド

列挙型はメソッドを持つことが可能で、インスタンスからアクセスできます。

<?php enum Size: string { case Small = "s"; case Medium = "m"; case Large = "l"; //$thisで自身のcaseを返す public function thisCase(): Size { return $this; } //どのcaseでも一律に文字列を返す public function hoge(): string { return "hoge"; } } $a = Size::Small; $b = Size::Large; var_dump($a->thisCase()); //enum(Size::Small) var_dump($b->hoge()); //string(4) "hoge"

ここで、メソッドにpublic修飾子をつけていることからわかるように、アクセス修飾子の指定が可能です。
しかし、列挙型は継承できないため、protectedもprivateも同じものとなります。

メソッド内の$thisで自身のcaseを返すので、例えばmatch式と組み合わせてcaseによって条件分岐するメソッドの実装も可能です。

<?php enum Size: string { case Small = "s"; case Medium = "m"; case Large = "l"; //$thisで自身のcaseを返す public function price(): int { return match($this) { Size::Small => 10, Size::Medium => 100, Size::Large => 1000 }; } } $a = Size::Small; $b = Size::Large; var_dump($a->price()); //int(10) var_dump($b->price()); //int(1000)

上の例ではBacked Enumにメソッドを定義しましたが、Pure Enumでも同様のことが可能です。

列挙型とinterface

列挙型はinterfaceを実装することも可能です。

<?php interface ISize { public function price(): int; } enum Size: string implements ISize { case Small = "s"; case Medium = "m"; case Large = "l"; //$thisで自身のcaseを返す public function price(): int { return match($this) { Size::Small => 10, Size::Medium => 100, Size::Large => 1000 }; } } $a = Size::Small; function hoge(ISize $size): int { return $size->price(); } var_dump(hoge($a)); //int(10)

Sizeはインターフェースを実装しており、関数hogeではポリモーフィズムがきちんと働いています。

列挙型とstaticメソッド

staticメソッドの定義もできます。
列挙型ではコンストラクタが使えないため、代用としてstaticメソッドが使えます、

<?php enum Size: string { case Small = "s"; case Medium = "m"; case Large = "l"; public static function fromPrice(int $price): static { return match(true) { $price < 10 => static::Small, $price < 100 => static::Medium, default => statoc.Large }; } } var_dump(Size::fromPrice(50)); //enum(Size::Medium)

列挙型と定数

constで定数の定義も可能です。自身のcaseも代入可能です。

<?php enum Size: string { case Small = "s"; case Medium = "m"; case Large = "l"; public const Hoge = "hoge"; public const Fuga = self::Small; } var_dump(Size::Hoge); //string(4) "hoge" var_dump(Size::Fuga); //enum(Size::Small)

列挙型とトレイト

トレイトの利用も可能です。
ただし列挙型でuseされるトレイトは、プロパティを含めるとエラーが出るので注意が必要です。

<?php interface ISize { public function price(): int; } trait SizeTrait { public function price(): int { return match($this) { Size::Small => 10, Size::Medium => 100, default => 1000 }; } } enum Size: string { use SizeTrait; case Small = "s"; case Medium = "m"; case Large = "l"; } $a = Size::Small; var_dump($a->price()); //int(10)

クラスとの違い

今まで何度か出てきたものを含めて、クラスとの違いについて改めてまとめます。

  • コンストラクタ、デストラクタは禁止
  • 継承は無い。よってprotectedもprivateも同じ。
  • staticまたはプロパティは禁止
  • シングルトンなのでcloneできない
  • マジックメソッドは一部のみ利用可能(__call, __callStatic, __invoke)
  • newでインスタンス化できない

値のリスト

cases()メソッドでcaseのリストを取得できます。

enum Size: string { case Small = "s"; case Medium = "m"; case Large = "l"; } var_dump(Size::cases()); /** * array(3) { * [0]=> *enum(Size::Small) * [1]=> * enum(Size::Medium) * [2]=> * enum(Size::Large) *} */

応用例

最後に、公式ドキュメントで紹介されている応用例を紹介します。

<?php 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', }; } } foreach (UserStatus::cases() as $case) { printf('<option value="%s">%s</option>\n', $case->value, $case->label()); } /** *<option value="P">Pending</option> *<option value="A">Active</option> *<option value="S">Suspended</option> *<option value="C">Canceled by user</option> * */

UserStatusは4つの状態のうち1つに限定されます。
またすべてのインスタンスはlabelメソッドを持つので、例のforeach文のようにループを回せます。

まとめ

PHPにおける列挙型とその使い方について解説しました。基本的な解説に留めましたが、データベース操作などと組み合わせると役に立ちそうです。