PHP入門:列挙型(Enum)について -vol.1-

スポンサーリンク
about_enum_with_php_vol1 用語解説
about_enum_with_php_vol1
この記事は約8分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

今日は、PHPの列挙型(Enum)についてご紹介します。

スポンサーリンク

背景

PHP8.2を利用したAPIを開発しているときに列挙型(Enum)を利用したので、そのときの調査内容を備忘としてのこしました。

こちらのサイトを参考にしています。

列挙型(Enum)の概要

列挙型、または “Enums” は、開発者が特定の可能な値の一つに限定されたカスタム型を定義できるようにするものです。これは特にドメインモデルを定義する際に役立ち、”無効な状態を表現不可にする”ことが可能です。

Enumは、多くの言語でさまざまな機能を持って現れます。PHPでは、Enumは特別な種類のオブジェクトです。Enumそのものはクラスであり、その可能なケースはそのクラスの単一のインスタンスオブジェクトです。これはEnumケースが有効なオブジェクトであり、型チェックを含む、オブジェクトが使用できるどこでも使用できることを意味します。

列挙型の最も一般的な例は、組み込みのブール型で、trueとfalseという合法的な値を持つ列挙型です。Enumは、開発者が自分自身の強力な列挙型を任意に定義できるようにします。

列挙型(Enum)の基本

列挙型(Enums)はクラスと似ており、クラス、インターフェース、トレイトと同じ名前空間を共有します。また、クラスと同じくオートロードもサポートされています。Enumは、固定の限られた数の有効な値を持つ新しい型を定義します。

enum Suit
{
    case Hearts;
    case Diamonds;
    case Clubs;
    case Spades;
}

この宣言は、Suitという名前の新しい列挙型を作成し、4つの有効な値のみを持つことができます: Suit::Hearts、Suit::Diamonds、Suit::Clubs、Suit::Spades。変数にはこれらの有効な値のいずれかを割り当てることができます。関数は列挙型に対して型チェックが行われ、その場合、その型の値のみが渡されます。

function pick_a_card(Suit $suit) { ... }

$val = Suit::Diamonds;

// OK
pick_a_card($val);
// OK
pick_a_card(Suit::Clubs);
// TypeError: pick_a_card(): Argument #1 ($suit) must be of type Suit, string given
pick_a_card('Spades');

列挙型にはゼロ個以上のケース定義を持つことができ、上限はありません。ゼロケースの列挙型も構文的には有効ですが、あまり役に立たないことが多いです。

列挙型のケースに対しては、PHPのラベルと同じ構文規則が適用されます。

デフォルトでは、ケースはスカラー値に直接バックアップされません。つまり、Suit::Heartsは “0” と等しくありません。代わりに、各ケースはその名前のシングルトンオブジェクトにバックアップされます。つまり:

$a = Suit::Spades;
$b = Suit::Spades;

$a === $b; // true

$a instanceof Suit;  // true

また、列挙型の値は互いに < または > で評価できません。それらの比較はオブジェクトでは意味を持たないため、列挙型の値を操作する際、これらの比較は常に false を返します。

このような関連データのないケースは”Pure Case”と呼ばれます。”Pure Case”のみを含む列挙型は”Pure Enum” と呼ばれます。

すべての”Pure Case”は、その列挙型のインスタンスとして実装されています。列挙型は内部的にはクラスとして表現されます。

すべてのケースには読み取り専用のプロパティ「name」があり、そのケース自体の大文字と小文字を区別した名前です。

print Suit::Spades->name;
// "Spades" を表示

名前が動的に取得される場合、defined() 関数や constant() 関数を使用して列挙型ケースの存在を確認または読み取ることもできます。ただし、通常は Backed Enum を使用する方が適しています。

Backed Enumerations

「Backed Enumerations」は、通常、列挙ケースにスカラー相当物が存在しない状態であり、ケースは単なるシングルトンオブジェクトです。しかし、データベースや類似のデータストアへのデータを往復できるようにする必要がある場合が多く、スカラー相当物(シンプルにシリアル化できる値)を内部的に定義することは便利です。

スカラー相当物を定義するには、次の構文を使用します:

enum Suit: string
{
    case Hearts = 'H';
    case Diamonds = 'D';
    case Clubs = 'C';
    case Spades = 'S';
}

スカラー相当物を持つケースは「Backed Case」と呼ばれ、より単純な値で「Backed(裏付け)」されています。すべての「Backed Case」を含む列挙型は「Backed Enum」と呼ばれます。「Backed Enum」には「Backed Case」のみが含まれます。”Pure Enum” には”Pure Case”のみが含まれます。

「Backed Enum」は int または string 型で「Backed(裏付け)」でき、特定の列挙型は一度に1つの型のみをサポートします(つまり、int|stringの結合はありません)。列挙型がスカラー相当物を持つとマークされている場合、すべてのケースには一意のスカラー相当物が明示的に定義されている必要があります。自動生成されたスカラー相当物(例: 連続した整数)は存在しません。バックアッドケースは一意である必要があり、2つのバックアッド列挙ケースは同じスカラー相当物を持つことはできません。ただし、定数はケースを参照し、実質的にエイリアスを作成することができます。

同等の値はリテラルまたはリテラル式である必要があります。定数や定数式はサポートされていません。つまり、1 + 1 は許容されますが、1 + SOME_CONST は許容されません。

「Backed Case」には、定義で指定された値である追加の読み取り専用プロパティ「value」があります。

print Suit::Clubs->value;
// "C" を表示

valueプロパティを読み取り専用に強制するために、変数をそのプロパティへの参照として割り当てることはできません。つまり、次のようなコードはエラーをスローします:

$suit = Suit::Clubs;
$ref = &$suit->value;
// エラー: Suit::$value プロパティの参照を取得できません

「Backed Enum」は内部的にバックアッドEnumインターフェースを実装し、2つの追加のメソッドを公開します:

  • from(int|string): self はスカラーを受け取り、対応するEnumケースを返します。見つからない場合、ValueErrorをスローします。これは、入力スカラーが信頼できる場合や、欠落した列挙値をアプリケーションを停止させるエラーと考える場合に主に役立ちます。
  • tryFrom(int|string): ?self はスカラーを受け取り、対応するEnumケースを返します。見つからない場合、nullを返します。これは、入力スカラーが信頼できない場合や、呼び出し側が独自のエラーハンドリングやデフォルト値ロジックを実装したい場合に主に役立ちます。

from() および tryFrom() メソッドは標準の弱い/強い型のルールに従います。弱い型モードでは、整数または文字列を渡すことが許容され、システムは値を適切に強制します。浮動小数点数を渡すことも許容され、強制されます。厳格な型モードでは、文字列バックアッドEnumに整数を渡すこと(またはその逆)は、いずれの場合もTypeErrorを引き起こします。他のパラメータータイプは、両方のモードでTypeErrorをスローします。

$record = get_stuff_from_database($id);
print $record['suit'];

$suit =  Suit::from($record['suit']);
// 無効なデータは、ValueErrorをスローします: "X" は列挙型 "Suit" の有効なスカラー値ではありません
print $suit->value;

$suit = Suit::tryFrom('A') ?? Suit::Spades;
// 無効なデータはnullを返し、したがってSuit::Spadesが代わりに使用されます。
print $suit->value;

Backed Enum において、手動で from() や tryFrom() メソッドを定義すると、 致命的なエラーが発生します。

おわりに

今日は、PHPの列挙型(Enum)についてご紹介しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました