PHP の Enum を復習

2022-08-17
PHP
Enum

PHP: 列挙型(Enum) - Manual

PHP 8.1 から使えるようになりました。Enum 自体は、ユーザランド実装の亜種を使うことが多くて、PHP 組み込みの Enum の経験が足りてなかったので、今回学びなおしてみることにした。

復習

例として適当かどうかは別として、Animal を考えてみよう。

1
2
3
4
5
6
7
<?php

enum Animal
{
case Cat;
case Dog;
}

これで、 Animal は型として使えるようになったので、こんな引数定義が可能になる。

1
2
3
4
5
6
7
8
9
<?php

function cry(Animal $animal)
{
var_dump($animal);
}

cry(Animal::Cat);
cry(Animal::Dog);

この結果は、こうなります。

1
2
enum Animal::Cat;
enum Animal::Dog;

ここまでは、列挙型の基本。上の cry メソッドは、Animal 型のみが入ります。他の型を入れると、実行時にエラーになりますし、PHPStan などでエラーを検出することがかのうです。

例えば、文字列を入れると…

1
2
3
4
PHP Fatal error:  Uncaught TypeError: cry(): Argument #1 ($animal) must be of type Animal, string given, called in /tmp/hoge.php on line 15 and defined in /tmp/hoge.php:9
Stack trace:
#0 /tmp/hoge.php(15): cry('Cat')
.....(以下略

cry メソッドは意図した値のみが渡ってくるので、堅牢なプログラムになりました。

Backed Enum

PHP マニュアルにも、まさにそのままの説明が載っています。

列挙型の case をデータベースや、 類似のデータストアで読み書きする必要があるケースが多くあります。 よって、ビルトインの (シリアライズ可能であることが自明な) スカラー値を持つ case があると、本質的に役に立ちます。

さきほどの例を使って続けると、例えばデータベースのカラムに animal_type というものがあったとして、1 が Cat、 2 が Dog を表すとします。(ここでは、データベースアンチパターンについては無視します)

この時に、 Animal::Cat が データベースの値である 1 を表してくれると便利ですし、1, 2 以外の値がカラムに登録される心配が少なくなります。Backed Enum の値に設定できるのは、スカラー型(int, string) のみです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

enum Animal: int
{
case Cat = 1;
case Dog = 2;
}

function cry(Animal $animal)
{
var_dump($animal->value);
}

cry(Animal::Cat);
cry(Animal::Dog);

結果は、こうなります。

1
2
int(1)
int(2)

from, tryFrom

Backed Enum については、from tryFrom でスカラー型から Enum の Case を返すことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

enum Animal: int
{
case Cat = 1;
case Dog = 2;
}

var_dump(Animal::from(1));
// 出力 -> enum Animal::Cat : int(1);
var_dump(Animal::from(2));
// 出力 -> enum Animal::Dog : int(2);


var_dump(Animal::tryFrom(3));
// 出力 -> NULL
var_dump(Animal::from(3));
// 出力 -> PHP Fatal error: Uncaught ValueError: 3 is not a valid backing value for enum "Animal"

fromtryFrom の違いは、異常値にたいして エラーにするか、NULLを返すかです。

その他

定数式での列挙型

列挙型のCaseはconstでも使えるけど、Backed Enum の値は使えない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

enum Animal: int
{
case Cat = 1;
case Dog = 2;
}


class Foo {

//これは出来る
const Bar = Animal::Cat;
private $bar = Animal::Cat;

//これは出来ない
const Bar2 = Animal::Cat->value;
private $bar2 = Animal::Cat->value;
}

Case に合わせた柔軟な値の取り扱いをする場合は、 Enum 型の方にメソッドを定義したほうが良さそう。

値のリスト

cases メソッドが定義されているので、Loop で回すときは、こっちを使うと便利

1
2
3
4
5
6
7
8
9
<?php

enum Animal: int
{
case Cat = 1;
case Dog = 2;
}

var_dump(Animal::cases());

配列が出力されます。

1
2
3
4
5
6
array(2) {
[0] =>
enum Animal::Cat : int(1);
[1] =>
enum Animal::Dog : int(2);
}

個人的な感想

Backed Enum の値の参照は、変数定義、コンスト定義時には使えないので、この辺りを柔軟に使いたい場合は、Enum側を拡張してあげる方向で考えたほうがスッキリしそう。

try, tryFrom, cases は単純に知らなかったので、今回ちゃんとマニュアルを読み直してよかった。