ツイート
シェア
LINEで送る
B! はてぶでブックマーク
Pocketでブックマーク
RSSフィード

PHP8.1, 列挙型(Enum)のシリアライズ。JSON化の説明はちょっと違うんじゃないか?

php
イラストダウンロードサイト【イラストAC】
の画像をもとに加工しています。

列挙型(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と同じになります。

BackedEnumのシリアライズ
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 に変換するなどの工夫が必要。

前の投稿
PHP8.1, 列挙型(Enum)にトレイト(trait)が追加できる。
PHP8.1, 読み取り専用のプロパティ追加。一度値を代入すると上書きできない。
次の投稿

コメントを残す