Do You PHP はてブロ

Do You PHPはてなからはてブロに移動しました

特定のプロパティを除いてserializeする

via. php.internals: Re: need inverted __sleep?

前のエントリに関連しますが。


__sleepと__wakeupでもいい気がするんですけどどうなんでしょう。
http://php.mirror.camelnetwork.com/manual/ja/language.oop.magic-functions.php

__sleepでも同様のことができますね。


serialize() は、クラスに特殊な名前 __sleep の関数があるかどうかを調べます。 もしあれば、シリアル化の前にその関数を実行します。 この関数で、オブジェクトをクリアすることができます。 またこの関数は、シリアル化するオブジェクトについて、 すべての変数の名前を配列で返すことが前提となっています。

説明の最後にある"変数の名前"ですが、1つ前のエントリのようなNULL文字を含む名前になります。
ということで、php.internals: Re: need inverted __sleep?にあるサンプルを、public/protected/privateで大丈夫なように

<?php
function getVisibilityAsArray($class_name, array $var_names) {
    $result = array();
    $class = new ReflectionClass($class_name);
    foreach ($class->getProperties() as $property) {
        if (!in_array($property->getName(), $var_names)) {
            continue;
        }
        $prefix = '';
        if ($property->isProtected()) {
            $prefix = chr(0) . '*' . chr(0);
        } else if ($property->isPrivate()) {
            $prefix = chr(0) . $class_name . chr(0);
        }
        $result[] = $prefix . $property->getName();
    }
    return $result;
}

といった関数を用意して書き換えてみました。

<?php
class ClassA
{
    private $a;
    protected $b;
    public $c;

    private $prefixs = array();

    public function setExclude($prefix) {
        if (!is_array($prefix)) {
            $prefix = (array)$prefix;
        }
        $this->prefixs = $prefix;
        $this->prefixs[] = 'prefixs';
    }
    public function __sleep() {
        $excludes = getVisibilityAsArray(__CLASS__, $this->prefixs);
        return array_diff(array_keys((array)$this), $excludes);
    }
}
$obj = new ClassA();
var_dump(serialize($obj));

$obj = new ClassA();
$obj->setExclude(array('a'));
var_dump(serialize($obj));

$obj = new ClassA();
$obj->setExclude(array('b'));
var_dump(serialize($obj));

$obj = new ClassA();
$obj->setExclude(array('c'));
var_dump(serialize($obj));

$obj = new ClassA();
$obj->setExclude(array('a', 'b', 'not_exist'));
var_dump(serialize($obj));

実行結果は次の通り。

$ php __sleep.php
string(85) "O:6:"ClassA":4:{s:9:"ClassAa";N;s:4:"*b";N;s:1:"c";N;s:13:"ClassAnames";a:0:{}}"
string(40) "O:6:"ClassA":2:{s:4:"*b";N;s:1:"c";N;}"
string(45) "O:6:"ClassA":2:{s:9:"ClassAa";N;s:1:"c";N;}"
string(48) "O:6:"ClassA":2:{s:9:"ClassAa";N;s:4:"*b";N;}"
string(27) "O:6:"ClassA":1:{s:1:"c";N;}"
$ 

サブクラスでもうまく動きます。

<?php
class ClassB extends ClassA
{
    private $x;
    protected $y;
    public $z;
}
$obj = new ClassB();
$obj->setExclude(array('a'));
var_dump(serialize($obj));  // 元々、aはシリアライズされない

$obj = new ClassB();
$obj->setExclude(array('b'));
var_dump(serialize($obj));

$obj = new ClassB();
$obj->setExclude(array('c'));
var_dump(serialize($obj));

$obj = new ClassB();
$obj->setExclude(array('a', 'b', 'y', 'z', 'not_exist'));
var_dump(serialize($obj));

以下、実行結果。

$ php __sleep02.php
string(81) "O:6:"ClassB":5:{s:9:"ClassBx";N;s:4:"*y";N;s:1:"z";N;s:4:"*b";N;s:1:"c";N;}"
string(86) "O:6:"ClassB":5:{s:9:"ClassBx";N;s:4:"*y";N;s:1:"z";N;s:9:"ClassAa";N;s:1:"c";N;}"
string(89) "O:6:"ClassB":5:{s:9:"ClassBx";N;s:4:"*y";N;s:1:"z";N;s:9:"ClassAa";N;s:4:"*b";N;}"
string(68) "O:6:"ClassB":4:{s:9:"ClassBx";N;s:4:"*y";N;s:1:"z";N;s:1:"c";N;}"
$

ちなみに、Serializableインターフェース - Do You PHP はてなで触れたPHP Serialization, Stack Traces, and Exceptions | Articles - Fabien Potencierでも「なぜ__sleep使わないでSerializableインターフェースを使ったの?」というコメントがあがっていますが、


#4 Fabien ― February 12, 2009 07:41
@Eric: Because the Serializable interface is much more flexible. In this specific case, we don't use anything fancy, but for consistency, I always use the interface.

ということだそうです。