列挙型(Enum)は一部ですがシリアライズできます。
(クラスの一種なので当然か。)
PHPのドキュメントを見ると、Pure EnumのJSONエンコードはエラー例外が発生するとありますが、例外はスローされません。
サンプルを動かしながら説明します。
Enumそのものはシリアライズできない。
シリアライズは、クラスのオブジェクトや配列をデータベースに保存したり、他のところへ送信するときなどに使います。
直訳すると『直列化』で、複数の要素を1列にすることなんですが、プログラミングでは1行の文字列に変換します。
<?php
class CarClass
{
public $matuda = '1';
public $honda = '2';
public $toyota = '3';
public $suzuki = '4';
}
var_dump(serialize(new CarClass()));
string(102) "O:8:"CarClass":4:{s:6:"matuda";s:1:"1";s:5:"honda";s:1:"2";s:6:"toyota";s:1:"3";s:6:"suzuki";s:1:"4";}"
さて、ここから本題ですが、列挙型(Enum)は、そのものはシリアライズできません。
enum CarPureEnum
{
case MATUDA;
case HONDA;
case TOYOTA;
case SUZUKI;
}
var_dump(serialize(CarPureEnum));
PHP Fatal error: Uncaught Error: Undefined constant "CarPureEnum" in /home/vagrant/php8-test.php:21
Stack trace:
#0 {main}
thrown in /home/vagrant/php8-test.php on line 21
ちょっと考えてみれば分かります。クラスだってクラスの型はシリアライズできないから。
var_dump(serialize(CarClass));
PHP Fatal error: Uncaught Error: Undefined constant "CarClass" in /home/vagrant/php8-test.php:23
Stack trace:
#0 {main}
thrown in /home/vagrant/php8-test.php on line 23
Enumでオブジェクトになってるのは case で定義する定数です。これならシリアライズできます。
var_dump(serialize(CarPureEnum::MATUDA));
var_dump(serialize(CarPureEnum::HONDA));
var_dump(serialize(CarPureEnum::TOYOTA));
var_dump(serialize(CarPureEnum::SUZUKI));
string(26) "E:18:"CarPureEnum:MATUDA";"
string(25) "E:17:"CarPureEnum:HONDA";"
string(26) "E:18:"CarPureEnum:TOYOTA";"
string(26) "E:18:"CarPureEnum:SUZUKI";"
Enumのシリアライズは、'E' で表現します。クラスオブジェクトと構成が似ていて文字列長、値が続く。
(ちなみにクラスオブジェクトは 'O' になる。)
BackedEnumの場合は、クラスオブジェクトのプロパティのように変数と値が入りそうですが、結果はPureEnumと同じになります。
enum CarBackedEnum: string
{
case MATUDA = 'matuda';
case HONDA = 'honda';
case TOYOTA = 'toyota';
case SUZUKI = 'suzuki';
}
var_dump(serialize(CarBackedEnum::MATUDA));
var_dump(serialize(CarBackedEnum::HONDA));
var_dump(serialize(CarBackedEnum::TOYOTA));
var_dump(serialize(CarBackedEnum::SUZUKI));
string(28) "E:20:"CarBackedEnum:MATUDA";"
string(27) "E:19:"CarBackedEnum:HONDA";"
string(28) "E:20:"CarBackedEnum:TOYOTA";"
string(28) "E:20:"CarBackedEnum:SUZUKI";"
『シリアライズを復元するときに使うのは定数名であって値は不要でしょ?』ということらしい。
クラスオブジェクトではそうはいかなくてプロパティ値も重要です。インスタンスごとに値がちがうから。
Enum のアンシリアライズ
シリアライズしただけでは説明が不十分なので、復元(アンシリアライズ)もしてみましょう。
$seri_str = serialize(CarPureEnum::MATUDA);
var_dump($seri_str);
$unseri = unserialize($seri_str);
var_dump($unseri);
if (CarPureEnum::MATUDA === $unseri) {
echo 'OK' . PHP_EOL;
} else {
echo 'NG' . PHP_EOL;
}
string(26) "E:18:"CarPureEnum:MATUDA";"
enum(CarPureEnum::MATUDA)
OK
これはかんたんなので、サンプルコードで十分ですね?
Pure Enumはエラー例外をスローするとあるが...。
PHPにはJSONエンコード・デコードがありますが、これも一種のシリアライズです。
ただ気になるところが。
PHPドキュメントでは、Pure EnumのJSONエンコードはErrorがスローされると言ってるんですがじっさいはちがいます。
Pure Enum を JSON にシリアライズしようとすると、 Error がスローされます。 Backed Enum を JSON にシリアライズしようとすると、 適切な型の、スカラーの値だけが表現されます。 これらの振る舞いは、 JsonSerializable をオーバーライドすることで上書きできます。
PHP公式ドキュメント
try {
var_dump(json_encode(CarPureEnum::MATUDA));
} catch(Error $e) {
echo $e->getMessage() . PHP_EOL;
}
var_dump(json_encode(CarBackedEnum::MATUDA));
bool(false)
string(8) ""matuda""
Pure Enum では、例外スローではなく結果が false で返されます。
ここは気をつけましょう。一見、エラーになっていないように見えるので。
Backed Enum では定数値だけが入り定数名は保存されません。連想配列っぽくなるかなと思ってたので意外。
この結果から見ると、Pure Enum は入れる値が無いのでエラーになるのでしょう。
Enum を JSONエンコード・デコードする
JSONも復元(デコード)までしてみましょう。
$encode = json_encode(CarBackedEnum::MATUDA, JSON_FORCE_OBJECT);
var_dump($encode);
$decode = json_decode($encode, false);
var_dump($decode);
if (CarBackedEnum::MATUDA === $decode) {
echo 'OK' . PHP_EOL;
} else {
echo 'NG' . PHP_EOL;
}
string(8) ""matuda""
string(6) "matuda"
NG
JSONのデコード値とEnumの型はそのままでは比較できません。Enumのvalueを使って比較します。
if (CarBackedEnum::MATUDA->value === $decode) {
echo 'OK' . PHP_EOL;
} else {
echo 'NG' . PHP_EOL;
}
string(8) ""matuda""
string(6) "matuda"
OK
今回は Backed Enum の定数の型がstringだったので $decode をそのまま比較に使えました。
int の場合はキャストするなり、JSONデコードで int に変換するなどの工夫が必要。
PHP公式ドキュメント