PHP Advent Calendar 2021 5日目の記事です。
昨日は@EbiTTさんの Laravelのキューで並列処理をするには でした。

PHP8.1のEnumの挙動を色々試したので、サンプルコードとともにご紹介しようと思います。

ちなみに昨日、Symfonyのアドベントカレンダーに、SymfonyのEnumTypeで遊んだ記事を書いたのも私です。
Enumをどれだけ待望していたかわかると思います(笑)。
というのも、弊社でメインの開発領域として扱っているリスティング広告やSNS広告にはめちゃくちゃ「○○オプションリスト」みたいなのが多くて、今まで自作のEnumTraitを何度も作っては自作ゆえの不具合に泣く数年を過ごしていました。 今年PHP8.0で開発し始めた新システムをサクッとPHP8.1に上げられそうなので、自作のEnumTraitを一日も早く捨てたいです!

基本のEnum

<?php

enum Language
{
    case Java;
    case PHP;
    case TypeScript;
    case JavaScript;
}
<?php

class LanguageTest extends TestCase
{
    public function test()
    {
        $this->assertEquals('PHP', Language::PHP->name);
        try {
            $v = Language::PHP->value;
        } catch (\Throwable $e) {
            $this->assertTrue(str_contains($e->getMessage(), 'Undefined property'));
        }
        $this->assertTrue(Language::PHP !== 'PHP');
        $this->assertTrue(Language::PHP instanceof Language);
        $cases = Language::cases();
        $this->assertCount(4, $cases);
        $this->assertSame($cases[1], Language::PHP);
    }
}

Enumで取りうる値は case と呼びます。
数字から始まるcaseは定義できませんでした(PHPでは数字から変数名もダメなので、同じルールみたいです)
定義した case PHPPHP 部分を取るときは Language::PHP->name で文字列として取り出せます。
基本のEnumではvalueを利用することはできません。( Language::PHP->value で1が取れると思ったら取れませんでした…)
Enum内に定義されている全てのcaseを配列で取り出すときは Language::cases() で取れます。
コードの1行目に書いた Language::PHP と2行目に書いた Language::PHP=== でtrue判定でした。
Enumを使うfunctionやmethodやプロパティでの型宣言はEnumのクラス名で指定できます。( function foo(Language $lang) {...} と書けます)

Backed Enum

<?php

enum Language: string
{
    case Java = 'java';
    case PHP = 'php';
    case TypeScript = 'ts';
    case JavaScript = 'js';
}
<?php

class LanguageTest extends TestCase
{
    public function test()
    {
        $this->assertEquals('PHP', Language::PHP->name);
        $this->assertEquals('php', Language::PHP->value);
        $this->assertTrue(Language::PHP !== 'PHP');
        $this->assertTrue(Language::PHP instanceof Language);
        $cases = Language::cases();
        $this->assertCount(4, $cases);
        $this->assertSame($cases[1], Language::PHP);
        $this->assertSame(Language::PHP, Language::from('php'));
    }
}

enum Language :string のように各caseの値の型を決めたenumはBacked Enumと呼ばれます。
case TypeScript = 'ts'; と定義したとき、Language::TypeScript->nameTypeScript が、Language::TypeScript->valuets が取れます。
Language::from('php') のようにcaseの値をfromに渡すとEnumのインスタンスを得ることができます。
その他の挙動はBasic Enumとは同じです。

Enumのメソッド

<?php

enum Language
{
    case Java;
    case PHP;
    case TypeScript;
    case JavaScript;

    public function getTargetDomain(): string
    {
        return match($this) {
            self::PHP, self::Java => 'backend',
            self::JavaScript, self::TypeScript => 'frontend',
        };
    }
}

Enumにもメソッドを生やすことができます。
メソッド内からはEnumインスタンス自体を $this で通常のクラスと同様に参照できました。
ただし function __toString(): string だけは定義できないようです :sweat_drops: (PhpStormに怒られました…)

スクリーンショット 2021-12-04 23 17 00

<?php

enum Language
{
    case Java;
    case PHP;
    case TypeScript;
    case JavaScript;

    public static function getBackendLanguages(): array
    {
        return [
            self::Java,
            self::PHP,
        ];
    }

    public static function getFrontendLanguages(): array
    {
        return [
            self::TypeScript,
            self::JavaScript,
        ];
    }
}

もちろんstatic methodも生やすことができました。

まとめ

ここまでで実際のプロダクトコードでPHPのEnumを使っていくための良い準備運動ができたと思います!
個人的には__toString()が生やせないのはちょっと衝撃でした。

今回試したコードは https://github.com/77web/my-php8.1-enum-playground に公開してあるので、実際にお手元で動かしてみたい方はどうぞ。

明日は @rana_kualu さんの記事です!