mirror of
https://github.com/php/doc-ja.git
synced 2026-03-23 22:52:11 +01:00
* 引数(arguments)とパラメータ(parameters)の区別を修正 Closes #62 * 引数/パラメータの使い分けを修正(Copilot review反映) * Use パラメータ consistently per README_Glossary.md
1115 lines
32 KiB
XML
1115 lines
32 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- $Revision$ -->
|
|
<!-- EN-Revision: 2e5f2910f3a2a95dcbdaf580ba57a0b60b072c2a Maintainer: mumumu Status: ready -->
|
|
<chapter xml:id="language.enumerations" xmlns="http://docbook.org/ns/docbook">
|
|
<title>列挙型(Enum)</title>
|
|
<sect1 xml:id="language.enumerations.overview">
|
|
<title>列挙型の概要</title>
|
|
<?phpdoc print-version-for="enumerations"?>
|
|
|
|
<para>
|
|
列挙型(Enumerations) または Enum を使うと、
|
|
開発者は取りうる値を限定した独自の型を定義できます。
|
|
これによって、"不正な状態を表現できなくなる" ので、
|
|
ドメインモデルを定義する時に特に役立ちます。
|
|
</para>
|
|
|
|
<para>
|
|
列挙型は、多くのプログラミング言語に備わっており、
|
|
様々な異なる機能があります。
|
|
PHP では、列挙型は特別な種類のオブジェクトです。
|
|
列挙型そのものはクラスですし、そこで定義される case は全て、
|
|
そのクラスのインスタンスです。
|
|
これは、列挙型で定義される case は有効なオブジェクトであり、
|
|
型チェックを含む、オブジェクトが使えるあらゆる場所で使えるということです。
|
|
</para>
|
|
|
|
<para>
|
|
列挙型のもっとも有名な例は、組み込みの論理型(boolean) です。
|
|
これは、有効な値 &true; と &false; を持つ列挙型です。
|
|
列挙型によって、開発者は任意の値の一覧を、
|
|
安定した形で定義できるようになります。
|
|
</para>
|
|
</sect1>
|
|
<sect1 xml:id="language.enumerations.basics">
|
|
<title>列挙型の基礎</title>
|
|
|
|
<para>
|
|
列挙型はクラスに似ていますし、
|
|
クラスやインターフェイス、トレイトと名前空間を共有します。
|
|
列挙型はオートローディングも可能です。
|
|
列挙型は新しい型を定義しますが、
|
|
固定の、限られた数の有効な値を持ちます。
|
|
</para>
|
|
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
enum Suit
|
|
{
|
|
case Hearts;
|
|
case Diamonds;
|
|
case Clubs;
|
|
case Spades;
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
上記の宣言によって、新しい列挙型
|
|
<literal>Suit</literal> が作られますが、
|
|
これが持つ有効な値は4つだけです。
|
|
<literal>Suit::Hearts</literal>、<literal>Suit::Diamonds</literal>、<literal>Suit::Clubs</literal>、そして <literal>Suit::Spades</literal> です。
|
|
これらの有効な値のうち、ひとつだけを変数に代入できます。
|
|
関数は列挙型に対する型チェックを行えますし、
|
|
その場合は列挙型の値だけが渡せます。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
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');
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
列挙型は、0個以上の
|
|
<literal>case</literal> を定義できます。
|
|
<literal>case</literal> を定義する数に上限はありません。
|
|
case が0個の列挙型も文法的には有効ですが、役に立ちません。
|
|
</para>
|
|
|
|
<para>
|
|
列挙型の case は、PHP のラベルと同じ規則に従います。
|
|
<link linkend="language.constants">定数</link> のページを参照ください。
|
|
</para>
|
|
|
|
<para>
|
|
デフォルトでは、case は本質的にスカラーの値に依存していません。
|
|
つまり、<literal>Suit::Hearts</literal>
|
|
は <literal>"0"</literal> に等しくないということです。
|
|
むしろ、それぞれの case はその名前が付いたシングルトンオブジェクトです。
|
|
以下のコードがそれを示しています:
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
$a = Suit::Spades;
|
|
$b = Suit::Spades;
|
|
|
|
$a === $b; // true
|
|
|
|
$a instanceof Suit; // true
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
このことは、列挙型の値は決して
|
|
<literal><</literal> や <literal>></literal>
|
|
で比較できないことも意味しています。
|
|
なぜなら、それらの比較をオブジェクトで行っても無意味だからです。
|
|
列挙型の値を使ってこれらの比較を行っても、常に &false; を返します。
|
|
</para>
|
|
|
|
<para>
|
|
関連するデータのない、こうしたタイプの case を、
|
|
"Pure Case" と呼びます。
|
|
Pure Case のみを含む列挙型を、"Pure Enum" と呼びます。
|
|
</para>
|
|
|
|
<para>
|
|
全ての Pure Case は、その列挙型のインスタンスとして実装されています。
|
|
列挙型は内部的にクラスとして表現されます。
|
|
</para>
|
|
|
|
<para>
|
|
全ての case は読み取り専用のプロパティ
|
|
<literal>name</literal> を持ちます。
|
|
これは大文字小文字が区別される、case そのものの名前です。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
print Suit::Spades->name;
|
|
// "Spades" と表示
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
列挙型の名前を動的に取得している場合、
|
|
case の存在を確認したり、読み取る用途として
|
|
<function>defined</function> や <function>constant</function>
|
|
関数が使えます。
|
|
しかしながら、これらの関数の利用はおすすめできません。
|
|
なぜなら、
|
|
<link linkend="language.enumerations.backed">Backed Enum</link>
|
|
で大半の用途を満たせるはずだからです。
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.backed">
|
|
<title>値に依存した列挙型(Backed Enum)</title>
|
|
|
|
<para>
|
|
デフォルトでは、列挙型はスカラー値の情報を持っていません。
|
|
単なるシングルトンオブジェクトです。
|
|
しかし、列挙型の case をデータベースや、
|
|
類似のデータストアで読み書きする必要があるケースが多くあります。
|
|
よって、ビルトインの (容易にシリアライズできる)
|
|
スカラー値を列挙型の中で定義すると便利です。
|
|
</para>
|
|
|
|
<para>
|
|
列挙型にスカラー値を定義するには、以下のようにします:
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
enum Suit: string
|
|
{
|
|
case Hearts = 'H';
|
|
case Diamonds = 'D';
|
|
case Clubs = 'C';
|
|
case Spades = 'S';
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
スカラー値を持つ case を、"Backed Case" と呼びます。
|
|
なぜなら、オブジェクトよりもシンプルな値に依存して(Backed)いるからです。
|
|
全ての case が Backed Case である列挙型を "Backed Enum" と呼びます。
|
|
Backed Enum には Backed Case のみを含めることができます。
|
|
Pure Enum には Pure Case だけを含めることができます。
|
|
</para>
|
|
|
|
<para>
|
|
Backed Enum は、整数、または文字列の値を持つことができます。
|
|
そして、単一の列挙型が一度に持つことの出来る型はひとつだけです
|
|
(つまり、<literal>int|string</literal> のような union
|
|
型はサポートしていないということです)。
|
|
列挙型がスカラー情報を持つとマークすると、
|
|
全ての case はユニークなスカラーの値を明示的に定義しなければいけません。
|
|
自動生成されるスカラー値 (例: 整数の連番) は存在しません。
|
|
Backed Enum の case の値は、全てユニークでなければいけません。
|
|
つまり、ふたつの Backed Enum の case は、
|
|
同じスカラー値を持ってはいけないということです。
|
|
しかし、定数は case を参照していても構わないので、
|
|
別名を作成することはできます。
|
|
<link linkend="language.enumerations.constants">列挙型と定数</link>
|
|
も参照ください。
|
|
</para>
|
|
|
|
<para>
|
|
スカラー値は、定数のスカラー式でなければいけません。
|
|
PHP 8.2.0 より前のバージョンでは、
|
|
スカラー値はリテラルか、リテラルを表す式でなければいけませんでした。
|
|
これは、定数や定数式はサポートされていなかったことを意味します。
|
|
つまり、<literal>1 + 1</literal> は許されますが、
|
|
<literal>1 + SOME_CONST</literal> は許されていませんでした。
|
|
</para>
|
|
|
|
<para>
|
|
Backed Enum の case は、追加の読み取り専用のプロパティ
|
|
<literal>value</literal> を持っています。
|
|
これは、Backed Enum の定義で指定された値です。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
print Suit::Clubs->value;
|
|
// "C" と表示
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
<literal>value</literal> プロパティを強制的に読み取り専用にするため、
|
|
そのリファレンスを変数には代入できません。
|
|
つまり、以下のようなコードはエラーになります:
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
$suit = Suit::Clubs;
|
|
$ref = &$suit->value;
|
|
// Error: Cannot acquire reference to property Suit::$value
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
Backed Enum は内部的に
|
|
<interfacename>BackedEnum</interfacename> インターフェイスを実装しています。
|
|
このメソッドは、以下の2つのメソッドを公開しています:
|
|
</para>
|
|
|
|
<simplelist>
|
|
<member>
|
|
<literal>from(int|string): self</literal>
|
|
スカラー値を受け取り、対応する Enum の case を返します。
|
|
対応する case がない場合は、
|
|
<classname>ValueError</classname>
|
|
がスローされます。
|
|
このメソッドは、入力のスカラー値が信頼でき、
|
|
存在しない enum
|
|
の値はアプリケーションを停止すべきエラーとみなせる場合に役立ちます。
|
|
</member>
|
|
<member>
|
|
<literal>tryFrom(int|string): ?self</literal>
|
|
スカラー値を受け取り、対応する Enum の case を返します。
|
|
対応する case がない場合は、&null; を返します。
|
|
このメソッドは、入力のスカラー値が信頼できない場合で、
|
|
呼び出し側が独自のエラーハンドリングや、
|
|
デフォルト値のロジックを実装したい場合に役立ちます。
|
|
</member>
|
|
</simplelist>
|
|
|
|
<para>
|
|
<literal>from()</literal> と <literal>tryFrom()</literal>
|
|
メソッドは、標準の 弱い/強い 型付けのルールに従います。
|
|
弱い型付けのルールでは、整数または文字列を受け入れ、
|
|
システムは値をそれに従って自動変換します。
|
|
厳密な型付けモードの場合、文字列型の Backed Enum に整数値を渡す
|
|
(またはその逆をする) と、<classname>TypeError</classname> が発生します。
|
|
float についても、全ての場合に同じ型付けのルールに従います。
|
|
それ以外の型については、どちらのモードでも TypeError が発生します。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
$record = get_stuff_from_database($id);
|
|
print $record['suit'];
|
|
|
|
$suit = Suit::from($record['suit']);
|
|
// 不正なデータを渡すと、次のようなエラーが発生 -> ValueError: "X" is not a valid scalar value for enum "Suit"
|
|
print $suit->value;
|
|
|
|
$suit = Suit::tryFrom('A') ?? Suit::Spades;
|
|
// 不正なデータに対しては null を返すので、Suit::Spades が代わりに使われます。
|
|
print $suit->value;
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
Backed Enum において、手動で <literal>from()</literal> や
|
|
<literal>tryFrom()</literal> メソッドを定義すると、
|
|
致命的なエラーが発生します。
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.methods">
|
|
<title>列挙型とメソッド</title>
|
|
|
|
<para>
|
|
列挙型では、(Pure Enum と Backed Enum ともに)
|
|
メソッドを含めることもできますし、
|
|
インターフェイスを実装することもできます。
|
|
列挙型がインターフェイスを実装する場合、
|
|
そのインターフェイスでの型チェックは、
|
|
実装した列挙型の全ての case で有効です。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
interface Colorful
|
|
{
|
|
public function color(): string;
|
|
}
|
|
|
|
enum Suit implements Colorful
|
|
{
|
|
case Hearts;
|
|
case Diamonds;
|
|
case Clubs;
|
|
case Spades;
|
|
|
|
// インターフェイスの規約を満たすための実装
|
|
public function color(): string
|
|
{
|
|
return match($this) {
|
|
Suit::Hearts, Suit::Diamonds => 'Red',
|
|
Suit::Clubs, Suit::Spades => 'Black',
|
|
};
|
|
}
|
|
|
|
// インターフェイスの一部ではないが、正しいコード
|
|
public function shape(): string
|
|
{
|
|
return "Rectangle";
|
|
}
|
|
}
|
|
|
|
function paint(Colorful $c)
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
paint(Suit::Clubs); // 動作します
|
|
|
|
print Suit::Diamonds->shape(); // "Rectangle" と表示
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
上記の例の <literal>Suit</literal> の4つのインスタンスは、
|
|
全てふたつのメソッド <literal>color()</literal> と
|
|
<literal>shape()</literal> を持ちます。
|
|
コードの呼び出しと型チェックに限れば、
|
|
これらのメソッドは他のオブジェクトのインスタンスと全く同じ振る舞いをします。
|
|
</para>
|
|
|
|
<para>
|
|
Backed Enum の場合、
|
|
インターフェイスの宣言は依存する型の宣言の直後に置きます。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
interface Colorful
|
|
{
|
|
public function color(): string;
|
|
}
|
|
|
|
enum Suit: string implements Colorful
|
|
{
|
|
case Hearts = 'H';
|
|
case Diamonds = 'D';
|
|
case Clubs = 'C';
|
|
case Spades = 'S';
|
|
|
|
// インターフェイスの規約を満たすための実装
|
|
public function color(): string
|
|
{
|
|
return match($this) {
|
|
Suit::Hearts, Suit::Diamonds => 'Red',
|
|
Suit::Clubs, Suit::Spades => 'Black',
|
|
};
|
|
}
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
メソッドの内部では、
|
|
変数 <literal>$this</literal> が定義され、
|
|
case のインスタンスを参照します。
|
|
</para>
|
|
|
|
<para>
|
|
メソッドには任意の複雑な処理を記述できますが、
|
|
実際、通常は静的な値を返したり、
|
|
case 毎に異なる結果を提供するために
|
|
&match; を
|
|
<literal>$this</literal> と共に使う処理を行います。
|
|
</para>
|
|
|
|
<para>
|
|
この場合は、Red と Black の値を持ち、
|
|
それらを返す <literal>SuitColor</literal> という列挙型を定義する方が、
|
|
より良いデータモデリングのプラクティスであることにも注意して下さい。
|
|
しかし、そうすると上の例が複雑になるでしょう。
|
|
</para>
|
|
|
|
<para>
|
|
上で示した階層は、論理的には次のようなクラス階層に似ています
|
|
(しかし、以下のコードは実際には動作しません):
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
interface Colorful
|
|
{
|
|
public function color(): string;
|
|
}
|
|
|
|
final class Suit implements UnitEnum, Colorful
|
|
{
|
|
public const Hearts = new self('Hearts');
|
|
public const Diamonds = new self('Diamonds');
|
|
public const Clubs = new self('Clubs');
|
|
public const Spades = new self('Spades');
|
|
|
|
private function __construct(public readonly string $name) {}
|
|
|
|
public function color(): string
|
|
{
|
|
return match($this) {
|
|
Suit::Hearts, Suit::Diamonds => 'Red',
|
|
Suit::Clubs, Suit::Spades => 'Black',
|
|
};
|
|
}
|
|
|
|
public function shape(): string
|
|
{
|
|
return "Rectangle";
|
|
}
|
|
|
|
public static function cases(): array
|
|
{
|
|
// 不正なメソッド。列挙型では、手動で cases() メソッドを定義できないからです。
|
|
// "値のリスト" のページも参照ください。
|
|
}
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
メソッドには public, private, protected が指定できますが、
|
|
列挙型では実際 private と protected は同じものです。
|
|
なぜなら、継承が許されていないからです。
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.static-methods">
|
|
<title>列挙型と static メソッド</title>
|
|
|
|
<para>
|
|
列挙型は、static メソッドを持つこともできます。
|
|
列挙型で static メソッドを使う主な理由は、
|
|
コンストラクタの代わりをさせるためです。たとえば、以下のようなものです:
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
enum Size
|
|
{
|
|
case Small;
|
|
case Medium;
|
|
case Large;
|
|
|
|
public static function fromLength(int $cm): self
|
|
{
|
|
return match(true) {
|
|
$cm < 50 => self::Small,
|
|
$cm < 100 => self::Medium,
|
|
default => self::Large,
|
|
};
|
|
}
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
static メソッドには public, private, protected が指定できますが、
|
|
列挙型では実際 private と protected は同じものです。
|
|
なぜなら、継承が許されていないからです。
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.constants">
|
|
<title>列挙型と定数</title>
|
|
|
|
<para>
|
|
列挙型には定数も含めることができます。
|
|
定数には public, private, protected が指定できますが、
|
|
列挙型では実際 private と protected は同じものです。
|
|
なぜなら、継承が許されていないからです。
|
|
</para>
|
|
|
|
<para>
|
|
列挙型の定数は、以下のように case を参照していても構いません:
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
enum Size
|
|
{
|
|
case Small;
|
|
case Medium;
|
|
case Large;
|
|
|
|
public const Huge = self::Large;
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.traits">
|
|
<title>トレイト</title>
|
|
|
|
<para>
|
|
列挙型ではトレイトを使えます。
|
|
列挙型におけるトレイトは、クラスにおけるそれと同じ振る舞いをします。
|
|
注意すべきことは、列挙型で <literal>use</literal>
|
|
されるトレイトには、プロパティが含まれていてはいけないという点です。
|
|
<literal>use</literal> されるトレイトには、
|
|
メソッドと static メソッドと、定数だけを含めることができます。
|
|
プロパティを持つトレイトの場合、致命的なエラーが発生します。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
interface Colorful
|
|
{
|
|
public function color(): string;
|
|
}
|
|
|
|
trait Rectangle
|
|
{
|
|
public function shape(): string {
|
|
return "Rectangle";
|
|
}
|
|
}
|
|
|
|
enum Suit implements Colorful
|
|
{
|
|
use Rectangle;
|
|
|
|
case Hearts;
|
|
case Diamonds;
|
|
case Clubs;
|
|
case Spades;
|
|
|
|
public function color(): string
|
|
{
|
|
return match($this) {
|
|
Suit::Hearts, Suit::Diamonds => 'Red',
|
|
Suit::Clubs, Suit::Spades => 'Black',
|
|
};
|
|
}
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.expressions">
|
|
<title>定数式における列挙型の値</title>
|
|
|
|
<para>
|
|
列挙型の case は、定数として表現されているので、
|
|
ほとんどの定数式の中で静的な値として使えます。
|
|
つまり、プロパティのデフォルト値や、
|
|
static 変数のデフォルト値、パラメータのデフォルト値、
|
|
グローバル定数やクラス定数の値として使えます。
|
|
列挙型の case 以外の値は使えませんが、
|
|
通常の定数は、列挙型の case を参照できます。
|
|
</para>
|
|
|
|
<para>
|
|
しかしながら、列挙型の中で <classname>ArrayAccess</classname>
|
|
のような暗黙のマジックメソッドの呼び出しを行うことは、
|
|
定数や static な定義では許されません。
|
|
なぜなら、結果の値が保証できなくなりますし、
|
|
副作用がまったくないことも保証できないからです。
|
|
関数コールやメソッドコール、そしてプロパティへのアクセスは、
|
|
引き続き定数式では不正な操作として扱われます。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
// これは、完全に正しい列挙型の定義です
|
|
enum Direction implements ArrayAccess
|
|
{
|
|
case Up;
|
|
case Down;
|
|
|
|
public function offsetExists($offset): bool
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function offsetGet($offset): mixed
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function offsetSet($offset, $value): void
|
|
{
|
|
throw new Exception();
|
|
}
|
|
|
|
public function offsetUnset($offset): void
|
|
{
|
|
throw new Exception();
|
|
}
|
|
}
|
|
|
|
class Foo
|
|
{
|
|
// これは、許されています。
|
|
const DOWN = Direction::Down;
|
|
|
|
// これは、許可されません。結果の値が保証できない可能性があります。
|
|
const UP = Direction::Up['short'];
|
|
// Fatal error: Cannot use [] on enums in constant expression
|
|
}
|
|
|
|
// これは、完全に正しいコードです。定数式ではないからです。
|
|
$x = Direction::Up['short'];
|
|
var_dump("\$x is " . var_export($x, true));
|
|
|
|
$foo = new Foo();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.object-differences">
|
|
<title>オブジェクトとの違い</title>
|
|
|
|
<para>
|
|
列挙型はクラスやオブジェクトの上に構築されていますが、
|
|
オブジェクトに関連する機能を全てサポートしているわけではありません。
|
|
特に、列挙型では状態を持つことが禁止されています。
|
|
</para>
|
|
|
|
<simplelist>
|
|
<member>コンストラクタやデストラクタは禁止されています。</member>
|
|
<member>継承はサポートされていません。列挙型は継承できませんし、何かを継承されることもありません。</member>
|
|
<member>staticまたはオブジェクトのプロパティは許可されていません。</member>
|
|
<member>列挙型の case を clone することはサポートされていません。なぜなら、case はシングルトンインスタンスでなければならないからです。</member>
|
|
<member>以下に示すもの以外の <link linkend="language.oop5.magic">マジックメソッド</link> は許可されていません。</member>
|
|
<member>列挙型は、常に使う前に宣言しておかなければなりません。</member>
|
|
</simplelist>
|
|
|
|
<para>
|
|
以下のオブジェクトの機能は利用可能ですし、
|
|
他のオブジェクトと同じ振る舞いをします:
|
|
</para>
|
|
|
|
<simplelist>
|
|
<member>public, private, protected メソッド</member>
|
|
<member>public, private, protected な static メソッド</member>
|
|
<member>public, private, protected な定数</member>
|
|
<member>列挙型は、任意の数のインターフェイスを実装できます。</member>
|
|
<member>
|
|
列挙型と case には、
|
|
<link linkend="language.attributes">アトリビュート</link>
|
|
を付加することができます。
|
|
<constant>TARGET_CLASS</constant> ターゲットフィルタには、
|
|
列挙型そのものも含まれます。
|
|
<constant>TARGET_CLASS_CONST</constant> ターゲットフィルタには、
|
|
列挙型の case も含まれます。
|
|
</member>
|
|
<member>
|
|
マジックメソッド
|
|
<link linkend="object.call">__call</link>,
|
|
<link linkend="object.callstatic">__callStatic</link>,
|
|
<link linkend="object.invoke">__invoke</link>
|
|
</member>
|
|
<member>
|
|
マジック定数 <constant>__CLASS__</constant> と
|
|
<constant>__FUNCTION__</constant> は、通常通り動作します。
|
|
</member>
|
|
</simplelist>
|
|
|
|
<para>
|
|
マジック定数
|
|
<literal>::class</literal> を列挙型で使うと、
|
|
任意の名前空間を含んだ型の名前として評価されます。
|
|
これはオブジェクトと全く同じです。
|
|
case インスタンスにおける <literal>::class</literal> も、
|
|
列挙型として評価されます。これは、その型のインスタンスの場合と同じです。
|
|
</para>
|
|
|
|
<para>
|
|
さらに、列挙型の case は
|
|
<literal>new</literal> を使って直接インスタンス化できませんし、
|
|
<methodname>ReflectionClass::newInstanceWithoutConstructor</methodname>
|
|
を使っても同様です。
|
|
これらは両方、エラーになります。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
$clovers = new Suit();
|
|
// Error: Cannot instantiate enum Suit
|
|
|
|
$horseshoes = (new ReflectionClass(Suit::class))->newInstanceWithoutConstructor()
|
|
// Error: Cannot instantiate enum Suit
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.listing">
|
|
<title>値のリスト</title>
|
|
|
|
<para>
|
|
Pure Enum と Backed Enum は、
|
|
共に内部インターフェイス <interfacename>UnitEnum</interfacename>
|
|
を実装しています。
|
|
<literal>UnitEnum</literal> には
|
|
static メソッド <literal>cases()</literal> が含まれています。
|
|
<literal>cases()</literal> は、
|
|
定義されている全ての case を宣言された順に含めた配列を返します。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
Suit::cases();
|
|
// [Suit::Hearts, Suit::Diamonds, Suit::Clubs, Suit::Spades] を生成
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
列挙型において、手動で <literal>cases()</literal>
|
|
メソッドを定義すると、致命的なエラーが発生します。
|
|
</para>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.serialization">
|
|
<title>列挙型をシリアライズする</title>
|
|
|
|
<para>
|
|
列挙型は、オブジェクトとは違う形でシリアライズされます。
|
|
特別に、列挙型には case 名を指定する新しいシリアライズコード
|
|
"E" が付与されています。
|
|
アンシリアライズのルーチンはそれを使って、
|
|
既存のシングルトンの値を変数に設定できます。
|
|
これにより、以下のようなコードが動作することが保証されます:
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
Suit::Hearts === unserialize(serialize(Suit::Hearts));
|
|
|
|
print serialize(Suit::Hearts);
|
|
// E:11:"Suit:Hearts";
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
アンシリアライズする際に、
|
|
マッチする列挙型と case がシリアライズした値に見つからなかった場合は、
|
|
警告が発生し、&false; が返されます。
|
|
</para>
|
|
|
|
<para>
|
|
Pure Enum を JSON にシリアライズしようとすると、
|
|
Error がスローされます。
|
|
Backed Enum を JSON にシリアライズしようとすると、
|
|
適切な型の、スカラーの値だけが表現されます。
|
|
これらの振る舞いは、
|
|
<classname>JsonSerializable</classname>
|
|
を実装することでオーバーライドできます。
|
|
</para>
|
|
|
|
<para>
|
|
<function>print_r</function> 向けに、
|
|
列挙型の case の出力はオブジェクトと少し違う形式になっています。
|
|
これは、混乱を最小限に抑えるためです。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
enum Foo {
|
|
case Bar;
|
|
}
|
|
|
|
enum Baz: int {
|
|
case Beep = 5;
|
|
}
|
|
|
|
print_r(Foo::Bar);
|
|
print_r(Baz::Beep);
|
|
|
|
/* 上記は、以下を出力
|
|
|
|
Foo Enum (
|
|
[name] => Bar
|
|
)
|
|
Baz Enum:int {
|
|
[name] => Beep
|
|
[value] => 5
|
|
}
|
|
*/
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.object-differences.inheritance">
|
|
|
|
<title>列挙型が継承できない理由</title>
|
|
|
|
<simpara>
|
|
クラスは、メソッドの使い方を約束(契約)するものです:
|
|
</simpara>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
class A {}
|
|
class B extends A {}
|
|
|
|
function foo(A $a) {}
|
|
|
|
function bar(B $b) {
|
|
foo($b);
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<simpara>
|
|
上のコードは型安全といえます。なぜなら、
|
|
B は A との契約を守っており、かつ共変性/反変性のマジックによって、
|
|
メソッドへのあらゆる期待が満たされるからです。例外は別です。
|
|
</simpara>
|
|
|
|
<simpara>
|
|
一方で、列挙型は case について契約するものです。メソッドではありません:
|
|
</simpara>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
enum ErrorCode {
|
|
case SOMETHING_BROKE;
|
|
}
|
|
|
|
function quux(ErrorCode $errorCode)
|
|
{
|
|
// 以下のようなコードを書くと、全ての case をカバーします
|
|
match ($errorCode) {
|
|
ErrorCode::SOMETHING_BROKE => true,
|
|
};
|
|
}
|
|
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<simpara>
|
|
<code>quux</code> 関数内の &match; 式は、
|
|
ErrorCode の全ての case をカバーしているかを静的に解析できます。
|
|
</simpara>
|
|
|
|
<simpara>
|
|
ここで以下のように、列挙型が継承可能だとしましょう:
|
|
</simpara>
|
|
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
// 列挙型が final でなかったと仮定した、思考実験のコード
|
|
// このコードは実際には動作しないので注意
|
|
enum MoreErrorCode extends ErrorCode {
|
|
case PEBKAC;
|
|
}
|
|
|
|
function fot(MoreErrorCode $errorCode) {
|
|
quux($errorCode);
|
|
}
|
|
|
|
fot(MoreErrorCode::PEBKAC);
|
|
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<simpara>
|
|
通常の継承のルールでは、あるクラスの子クラスは親クラスの型チェックを通過します。
|
|
</simpara>
|
|
|
|
<simpara>
|
|
上のコードの問題点は、
|
|
<code>quux()</code> 関数の &match; 式が全ての case をカバーしてはいないということです。
|
|
なぜなら、<code>MoreErrorCode::PEBKAC</code>
|
|
がカバーされていないので、&match; 式が例外をスローするからです。
|
|
</simpara>
|
|
|
|
<simpara>
|
|
こうした理由から、列挙型は final 扱いであり、継承できなくなっています。
|
|
</simpara>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.enumerations.examples">
|
|
&reftitle.examples;
|
|
|
|
<para>
|
|
<example>
|
|
<title>基本的な、値を限定する例</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
enum SortOrder
|
|
{
|
|
case Asc;
|
|
case Desc;
|
|
}
|
|
|
|
function query($fields, $filter, SortOrder $order = SortOrder::Asc)
|
|
{
|
|
/* ... */
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
<para>
|
|
<literal>query()</literal> 関数は、
|
|
<literal>$order</literal> が
|
|
<literal>SortOrder::Asc</literal>
|
|
または <literal>SortOrder::Desc</literal>
|
|
であることが保証されることを知っているので、安全に処理できます。
|
|
それら以外の値を渡しても、
|
|
<classname>TypeError</classname>
|
|
になるので、それ以上のチェックやテストは不要です。
|
|
</para>
|
|
</example>
|
|
</para>
|
|
|
|
<para>
|
|
|
|
<example>
|
|
<title>唯一の値を指定する、高度な例</title>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
enum UserStatus: string
|
|
{
|
|
case Pending = 'P';
|
|
case Active = 'A';
|
|
case Suspended = 'S';
|
|
case CanceledByUser = 'C';
|
|
|
|
public function label(): string
|
|
{
|
|
return match($this) {
|
|
self::Pending => 'Pending',
|
|
self::Active => 'Active',
|
|
self::Suspended => 'Suspended',
|
|
self::CanceledByUser => 'Canceled by user',
|
|
};
|
|
}
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
|
|
<para>
|
|
この例では、ユーザーのステータスは
|
|
<literal>UserStatus::Pending</literal>、
|
|
<literal>UserStatus::Active</literal>、
|
|
<literal>UserStatus::Suspended</literal> または
|
|
<literal>UserStatus::CanceledByUser</literal>
|
|
のうちのひとつに限られています。
|
|
関数はパラメータの型を <literal>UserStatus</literal>
|
|
に指定できますし、これら4つの値だけを受け入れます。
|
|
</para>
|
|
|
|
<para>
|
|
これら4つの値は、それぞれ
|
|
<literal>label()</literal> メソッドを持ちます。
|
|
これは、人間が読みやすい文字列を返します。
|
|
この文字列は、"マシン上の" スカラー値とは別のものです。
|
|
このスカラー値は、データベースのフィールドや、
|
|
HTML の select ボックスで使うことができます。
|
|
</para>
|
|
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
|
|
foreach (UserStatus::cases() as $case) {
|
|
printf('<option value="%s">%s</option>\n', $case->value, $case->label());
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</example>
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-omittag:t
|
|
sgml-shorttag:t
|
|
sgml-minimize-attributes:nil
|
|
sgml-always-quote-attributes:t
|
|
sgml-indent-step:1
|
|
sgml-indent-data:t
|
|
indent-tabs-mode:nil
|
|
sgml-parent-document:nil
|
|
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
|
sgml-exposed-tags:nil
|
|
sgml-local-catalogs:nil
|
|
sgml-local-ecat-files:nil
|
|
End:
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|