PHP8.1が出たのでmyclabs/php-enumから列挙型(Enum)に置き換えが出来るか試してみた
PHP Advent Calendar 2021の10日目の記事です。
この記事は、はじめに
2021年11月25日にPHP8.1がリリースされましたー
新機能の中には、待望?の列挙型(Enum)が含まれています。
PHPでは、これまで列挙型を作るときにはmyclabs/php-enum
などのパッケージを使っていることが多かったかと思いますが、今回PHPでサポートされるようになったので、myclabs/php-enum
から置き換えができるのか試してみます。
環境
- PHP 8.1.0
- myclabs/php-enum 1.8.3
myclabs/php-enum
を使って作った下記のサンプルコードを列挙型(Enum)に置き換えられるか試してみたいと思います。
declare(strict_types=1);
use MyCLabs\Enum\Enum;
class Cardsuit extends Enum
{
public const CLUBS = 1;
public const DIAMONDS = 2;
public const HEARTS = 3;
public const SPADES = 4;
public function label(): string
{
return match($this->value) {
self::CLUBS => 'クラブ',
self::DIAMONDS => 'ダイヤモンド',
self::HEARTS => 'ハート',
self::SPADES => 'スペード',
};
}
}
置き換えを試してみた
置き換えを試してみるのは次の通りです。
- 定義
- 使い方
- staticメソッド
- メソッド
定義
サンプルコードを列挙型で定義した場合は次のように書くことが出来そうです。
declare(strict_types=1);
enum Cardsuit: int
{
case CLUBS = 1;
case DIAMONDS = 2;
case HEARTS = 3;
case SPADES = 4;
public function label(): string
{
return match($this) {
self::CLUBS => 'クラブ',
self::DIAMONDS => 'ダイヤモンド',
self::HEARTS => 'ハート',
self::SPADES => 'スペード',
};
}
}
列挙型にはスカラー値を持つもの(Backed Enum)と持たないもの(Pure Enum)があります。
置き換えをする場合はスカラー値を持つ列挙型(Backed Enum)を使うことになりそうです。
また、メソッド、staticメソッドを作成することができるようなので独自に作ったメソッドも置き換えは可能かと思われますが、テストは必須ですね。
さらに定数を定義することもできるようです。
が、定数を使うなら列挙型に置き換える必要もない気がしますね。
定義に関することで最後に列挙型はtrait
も使うことができるようです。
使い方
// myclabs/php-enum
$cardsuit = Cardsuit::DIAMONDS();
echo $cardsuit->label(); // クラブ
// 列挙型
$cardsuit = Cardsuit::CLUBS;
echo $cardsuit->label(); // クラブ
メソッドではないので()
が不要になりますね。
また、これまで定数として使っていた場合に、置き換えたあとだと、列挙型のインスタンスに変わるので注意が必要ですね。
インスタンスの生成後は定義したメソッドの実行もできました。
staticメソッド
myclabs/php-enum
で使えるstaticメソッドは下記になります。
- from
- keys
- values
- toArray
- isValid
- isValidKey
- assertValidValue
- search
- __callStatic
from
列挙型のBacked Enum
の場合は、同じ名前のfromメソッドがあります。
// myclabs/php-enum
$cardsuit = Cardsuit::from(4);
var_dump($cardsuit);
// object(Cardsuit)#3 (2) {
// ["value":protected]=>
// int(4)
// ["key":"MyCLabs\Enum\Enum":private]=>
// string(6) "SPADES"
}
// 列挙型
$cardsuit = Cardsuit::from(4);
var_dump($cardsuit);
// enum(Cardsuit::SPADES)
インスタンスを返しているので、同じ動作になりそうです。
keys、values、toArray
列挙型にはこの3つと同様のメソッドはなさそうですが、
値のリストを取得するメソッドのcases
が使えるようなので、独自に定義すれば対応することが出来そうです。
var_dump(Cardsuit::cases());
// array(4) {
// [0]=>
// enum(Cardsuit::CLUBS)
// [1]=>
// enum(Cardsuit::DIAMONDS)
// [2]=>
// enum(Cardsuit::HEARTS)
// [3]=>
// enum(Cardsuit::SPADES)
//}
isValid、isValidKey、assertValidValue、search
列挙型には同様のメソッドはなさそうです。
独自に定義すれば対応することが出来そうです。
__callStatic
列挙型はすべてのマジックメソッドが使えるわけではないようです
以下に示すもの以外の マジックメソッド は許可されていません。
__call, __callStatic, __invoke
__callStatic
は使えるようなので独自に実装すれば、そのままで置き換えが出来そうな感じがします。
メソッド
myclabs/php-enum
で使えるメソッドは下記になります。
- __construct
- __wakeup
- __toString
- getValue
- getKey
- equals
- jsonSerialize
__construct、__wakeup、__toString
staticメソッドのところで書いた通り、この3つは列挙型では使えないマジックメソッドのようです。
__construct
が使えなくなったので、staticメソッドのfrom
メソッドに置き換える必要があります。
$cardsuit = new Cardsuit(1); // Fatal error: Uncaught Error: Cannot instantiate enum Cardsuit
$cardsuit = Cardsuit::from(1);
また、__toString
も使えなくなったので、出力している箇所はvalue
プロパティを使うように書き換える必要があります。
尚、value
プロパティはBacked Enum
の場合だけ、定義されているみたいです。
$cardsuit = Cardsuit::CLUBS;
echo $cardsuit; // Fatal error: Uncaught Error: Object of class Cardsuit could not be converted to string
echo $cardsuit->value; // 1
getValue、getKey
列挙型にはメソッドではなく、専用プロパティvalue
、name
が定義されていて、そのプロパティを使うことで置き換えは出来そうです。
$cardsuit = Cardsuit::CLUBS;
echo $cardsuit->value; // 1
echo $cardsuit->name; // CLUBS
equals
列挙型には同様のメソッドはなさそうです。
独自に定義すれば対応することが出来そうです。
jsonSerialize
列挙型もシリアライズは可能ですが、違う形でシリアライズされるようです。
// myclabs/php-enum
$cardsuit = Cardsuit::CLUBS();
echo serialize($cardsuit);
// O:8:"Cardsuit":2:{s:8:"*value";i:1;s:22:"MyCLabs\Enum\Enumkey";s:5:"CLUBS";}
// 列挙型
$cardsuit = Cardsuit::CLUBS;
echo serialize($cardsuit) . PHP_EOL;
// E:14:"Cardsuit:CLUBS";
独自にメソッドを定義してみよう
今回、独自にメソッドを定義すれば対応することが出来そうなので実際に作ってみました。
汎用的なものとして作ろうと思うので、今回はtrait
を使います。
尚、列挙型はクラスではないので継承は使えないです。
declare(strict_types=1);
trait EnumTrait
{
public function getValue(): mixed
{
return $this->value;
}
public function getKey(): string
{
return $this->name;
}
public function equals($enum): bool
{
return $enum instanceof self
&& $this === $enum
&& static::class === \get_class($enum);
}
public static function keys(): array
{
return \array_keys(static::toArray());
}
public static function values()
{
$cases = static::cases();
$values = [];
foreach ($cases as $enum) {
$values[$enum->name] = $enum;
}
return $values;
}
public static function toArray(): array
{
$cases = static::cases();
$array = [];
foreach ($cases as $enum) {
$array[$enum->name] = $enum->value;
}
return $array;
}
public static function isValid($value): bool
{
return \in_array($value, static::toArray(), true);
}
public static function isValidKey($key): bool
{
$array = static::toArray();
return isset($array[$key]) || \array_key_exists($key, $array);
}
public static function assertValidValue($value): void
{
if (static::search($value) === false) {
throw new \UnexpectedValueException();
}
}
public static function search($value)
{
return \array_search($value, static::toArray(), true);
}
public static function __callStatic($name, $arguments)
{
$cases = static::cases();
foreach ($cases as $case) {
if ($name === $case->name) {
return $case;
}
}
}
}
作ってみましたが、myclabs/php-enum
とほぼ一緒でしたね。
まとめ
まとめると次のような感じです。
- (当たり前ですが)すべてを置き換えることは無理でした
- 独自にメソッドを追加することで置き換えが可能なものもあります
- 定数として使っている箇所は列挙型のインスタンスに変わるので修正が必要です
はじめに書いた通り、この記事は列挙型を触るための半分?趣味的な内容でした。
新規で列挙型を使う分にはいいかもしれないですが、依存パッケージを減らしたいなどの理由がない限りすでにあるものは無理に置き換える必要はない気がしますね。
仮にmyclabs/php-enum
から置き換えを検討している人がいるなら、この結果が何かの参考になればと思います。
Discussion
rector というツールで単純なパターンは自動置き換えもできたりします https://github.com/rectorphp/rector-src/pull/30
ありがとうございます。
すでにRectorに置き換えルールがあったんですね。
確認してみます