結論:__sleep()と__wakeup()を使う
以下のようなSimpleXMLElementをプロパティとして持ったクラスのオブジェクトを、Serializeしてキャッシュしたい場合があります。
<?php
namespace App;
use SimpleXMLElement;
class MyClass
{
/**
* @var SimpleXMLElement
*/
public $xml;
/**
* @param SimpleXMLElement $xml
* @return void
*/
public function __construct(SimpleXMLElement $xml)
{
$this->xml = $xml;
}
}
例えばこのクラスのオブジェクトをLaravelでそのままキャッシュしようとすると、「SimpleXMLElementをシリアライズできないよ」というエラーで怒られます。
解決策としてPHPのマジックメソッドである__sleep()と__wakeup()を使用します。
先ほどのMyClassに以下のメソッドを追記。
public function __sleep()
{
$this->xml = $this->xml->asXml();
return ['xml'];
}
public function __wakeup()
{
$this->xml = simplexml_load_string($this->xml);
}
シリアライズされる前に__sleep()が呼ばれ、その中ではSimpleXMLElementをXML文字列に変換しています。
そしてアンシリアライズされる時に__wakeup()の中で再度SimpleXMLElementに変換するような形。
参考:PHP: マジックメソッド - Manual
http://php.net/manual/ja/language.oop5.magic.php
Laravelの時の注意点
Laravelではrememberというキャッシュがなければオリジンを取得し、そして保存して返すというメソッドがあります。
保存して返すというところがミソですが、これは以下のような問題を引き起こします。
- MyClassをキャッシュへ保存しようとする
- 保存する際に__sleep()メソッドによりxmlプロパティがstringへ変換される
- xmlプロパティがstringであるオブジェクトがrememberメソッドより返される
このように、初回(オリジンを取得する際)に__wakeup()が呼ばれないという事態に陥ってしまいます。
これは以下のように対処します。
$item= Cache::remember('item', 30, function () {
return new MyClass($someSimpleXMLElement);
});
if (is_string($item->xml)) {
$item->__wakeup();
}
return $item;