Open6

PHP8 の Serialize memo

at yasuat yasu

PHP8 から Serializable Interface + magic method になる

タイトル通り。忘れてたよ。

で、Serializable Interface の方は serialized(): ?string だけじゃなく __serialize(): array も実装することになる。 serialized の方はシリアライズ化した文字列を返すが __serialize は配列化して、PHP側が serialize する感じっぽい。

これの話: https://php.watch/versions/8.1/serializable-deprecated

Test

PHP8.3 で実行。

serialize.php はこんな感じ。

<?php

class OldType implements \Serializable {
  public function serialize(): ?string { return serialize('I am old man' ); }
  public function unserialize(string $data): void { print_r($data); }
}


$ss = serialize(new OldType());

print "=========================\n";
print "Serialize String: {$ss}\n\n";
print "=========================\n";

unserialize($ss);

実行結果。

❯ php serialize.php
PHP Deprecated:  OldType implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in /Users/yasui/tmp/serialize.php on line 3

Deprecated: OldType implements the Serializable interface, which is deprecated. Implement __serialize() and __unserialize() instead (or in addition, if support for old PHP versions is necessary) in /Users/yasui/tmp/serialize.php on line 3
=========================
Serialize String: C:7:"OldType":20:{s:12:"I am old man";}

=========================
s:12:"I am old man";%                                                                                                                                          
at yasuat yasu

magic method の方

serialize_magic.php

<?php

class NewType {
  public function __serialize(): array { return ["hello" => 'world' ]; }
  public function __unserialize(array $data): void { print_r($data); }
}

$ss = serialize(new NewType());

print "=========================\n";
print "Serialize String: {$ss}\n\n";
print "=========================\n";

unserialize($ss); 

結果

❯ php serialize_magic.php
=========================
Serialize String: O:7:"NewType":1:{s:5:"hello";s:5:"world";}

=========================
Array
(
    [hello] => world
)
at yasuat yasu

混ぜ返してみる

C:7:"OldType":20:{s:12:"I am old man";} を serialize_magic に通して見てみる。

serialize_mix.php

<?php

class NewType {
  public function __serialize(): array { return ["hello" => 'world' ]; }
  public function __unserialize(array $data): void { print_r($data); }
}

$ss = serialize(new NewType());

print "=========================\n";
print "Serialize String: {$ss}\n\n";
print "=========================\n";

unserialize($ss); 

実行

❯ php serialize_mix.php
=========================
PHP Warning:  Class OldType has no unserializer in /Users/yasui/tmp/serialize_mix.php on line 10

Warning: Class OldType has no unserializer in /Users/yasui/tmp/serialize_mix.php on line 10
at yasuat yasu

メソッド呼び出しの優先順位

シリアル化するときのメソッド呼び出しの順序

__serialize() と __sleep() が両方同じオブジェクトに定義されていた場合、 __serialize() だけが呼び出されます。 __sleep() は無視されます。 オブジェクトが Serializable インターフェイスを実装していた場合、 インターフェイスの serialize() メソッドは無視され、 __serialize() が代わりに使われます。

  1. 【__serializeがない時】_sleep()
  2. 【Serializableインターフェース実装時】__serialize()
    3. 【Serializable未実装】serialize()

__serialize() の目的は、 任意のオブジェクトの表現をシリアライズしやすいように定義することです。 配列の要素はオブジェクトのプロパティに対応していても構いませんが、必須ではありません。
 逆に、unserialize() は 特殊な名前 __unserialize() を持つかを調べます。 もしあれば、__serialize() が返した配列を復元し、この関数に渡します。 この関数では、その配列から必要に応じてオブジェクトのプロパティを復元して構いません。

上記の仕様だと __unserializeunserialize を実装した時は、__unserialize があれば unserialize($object->__unserialize($original_data)) のような挙動になるはず。

まず二つの実装

serialize_all.php

<?php
class OldType implements \Serializable {
  public function __serialize(): array { return ["hello" => 'world' ]; }
  public function __unserialize(array $data): void { print "[Call]" . __METHOD__."\n"; print_r($data); }
  public function serialize(): ?string { return serialize('I am old man' ); }
  public function unserialize(string $data): void { print "[Call]" . __METHOD__."\n"; print_r($data); }
}

$ss = 'C:7:"OldType":20:{s:12:"I am old man";}';

print "=========================\n";
unserialize($ss);

実行結果

❯ php serialize_all.php
=========================
[Call]OldType::unserialize
s:12:"I am old man";%                                                                                                                                          

__unserialize のみ実装

serialize_one.php

<?php
class OldType {
  public function __serialize(): array { return ["hello" => 'world' ]; }
  public function __unserialize(array $data): void { print "[Call]" . __METHOD__."\n"; print_r($data); }
}

$ss = 'C:7:"OldType":20:{s:12:"I am old man";}';

print "=========================\n";
unserialize($ss);

実行結果

❯ php serialize_one.php
=========================
PHP Warning:  Class OldType has no unserializer in /Users/yasui/tmp/serialize_one.php on line 10

Warning: Class OldType has no unserializer in /Users/yasui/tmp/serialize_one.php on line 10
at yasuat yasu

よくわかってない混乱ポイント

  1. public __unserialize(array $data): void だから unserialize を実装してないときは、内部的に __unserialize を呼び出して、unserialize でオブジェクトを復元する感じ…?
  2. PHP8.1 で使う時で、アップグレードしてきた物は Serializable を実装しているときは、public __serialize(): arraypublic __unserialize(array $data): void を実装しとけりゃとりあえず良い?