什么是序列化

序列化(serialization)是指将对象以规定标记格式转换为可以存储的、结构完整的对象元数据,只是对象的描述元数据,对象的上下文关系或引用关系不会转换(比如数据库链接的连接关系)。反序列化就是用对象元数据还原成对象数据。

PHP中序列化

PHP可以通过内置的 serialize()unserialize() 函数来实作序列化操作,在对象中可编辑php的魔术方法来定义序列化时的特殊操作。

  1. serialize:序列化

    class Phone {
    public $ID;
    public $SN;
    public function __construct($ID, $SN) {
        $this->ID = $ID;
        $this->SN = $SN;
    }
    }
    $myPhone = new Phone(1001,'SN001');
    var_dump(serialize($myPhone));
    //输出结果
    string(53) "O:5:"Phone":2:{s:2:"ID";i:1001;s:2:"SN";s:5:"SN001";}"

    可以看到最后输出了一个长度为53位的字符串,将这串字符保存或转传后可以通过下面反序列化恢复myPhone对象。这串字符的具体标识含义可查看文末附注,这里我们还不需要关心它。

  2. unserialize:反序列化

    $myPhoneStr = 'O:5:"Phone":2:{s:2:"ID";i:1001;s:2:"SN";s:5:"SN001";}';
    var_dump(unserialize($myPhoneStr));
    //输出结果
    object(Phone)#2 (2) {
    ["ID"]=>
    int(1001)
    ["SN"]=>
    string(5) "SN001"
    }

    有时我们并不需要保留全部属性,有些敏感属性不需要暴露在外,这时可以定义__serialize()方法来设置可被序列化操作的属性。

  3. __serialize():魔术方法,格式化定义序列化操作时可暴露的属性

    class Phone {
    public $ID;
    public $SN;
    public function __construct($ID, $SN) {
                    $this->ID = $ID;
                    $this->SN = $SN;
            }
    
    public function __serialize(){
            return ['ID' => FALSE,'SN' => $this->SN];
        }
    
    }
    $myPhone = new Phone(1001,'SN001');
    var_dump(serialize($myPhone));
    //输出结果
    string(50) "O:5:"Phone":2:{s:2:"ID";b:0;s:2:"SN";s:5:"SN001";}"
    //
    $myPhoneStr = 'O:5:"Phone":2:{s:2:"ID";b:0;s:2:"SN";s:5:"SN001";}';
    var_dump(unserialize($myPhoneStr));
    //输出结果
    object(Phone)#2 (2) {
    ["ID"]=>
    bool(false)
    ["SN"]=>
    string(5) "SN001"
    }

    上面在对象中定义了在序列化时只暴露出SN属性,也可以单独设置某个属性,与之相似的方法还有__sleep(),下面简单说下它。

  4. __sleep():魔术方法,定义序列化操作时可暴露的属性 有很多同学介绍它,但我并不推荐使用它,虽然它拼写起来更简单方便,序列化效果也一样。主要2点原因: a、__serialize()会比__sleep()优先识别,若同时存在这两个,后者将不会生效。 b、 __sleep()不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误,而__serialize()不会有这个问题。

    public function __sleep(){
            return ['SN']; 
        }

    说了序列化时的魔术方法,但那只定义了数据的序列化格式,很多使用场景对数据的使用前提也需要定义操作,即反序列化的魔术方法__unserialize()

  5. __unserialize(array $data):魔术方法,定义反序列操作时的初始化操作 与__serialize()相对应,会接收__serialize()返回的数组,在此处方法内可以选择使用

    class Phone {
    public $ID;
    public $SN;
    public function __construct($ID, $SN) {
                    $this->ID = $ID;
                    $this->SN = $SN;
            }
    //序列化
    public function __serialize(){
            return ['ID' => $this->ID,'SN' => $this->SN];
         }
    //反序列化  
    public function __unserialize(array $data){
            $this->ID = $data['ID'];
            $this->SN = $data['SN'];
            if($this->ID == '1001'){
                throw new Exception("you can't use this phone");
            }
        }
    }
    $myPhone = new Phone(1001,'SN001');
    var_dump(serialize($myPhone));
    //输出结果
    string(53) "O:5:"Phone":2:{s:2:"ID";i:1001;s:2:"SN";s:5:"SN001";}"
    //
    $myPhoneStr = 'O:5:"Phone":2:{s:2:"ID";i:1001;s:2:"SN";s:5:"SN001";}';
    var_dump(unserialize($myPhoneStr));
    //输出结果
    Fatal error: Uncaught Exception: you can't use this phone

    上面定义了反序列化时判断若ID是1001则抛出异常:you can’t use this phone。与序列化类似的它也有另一个相同功能的方法__wakeup(),与__sleep()一样,它的优先级也小于unserialize。

  6. __wakeup():魔术方法,定义反序列操作时的初始化操作 与__sleep()相对应,没有值接收。若同时存在__unserialize()则此方法无效

    public function __wakeup()
    {
        if($this->ID == '1001'){
                throw new Exception("you can't use this phone");
            }
    }

    这里代码效果与5一样,我也不推荐使用这个。当然它和sleep存在肯定有他们的需要,拼写简单就比serialize要方便用。我的不推荐理由也是优先级识别的原因,他们不是先后顺序的问题,是会被忽略无效,我可不想因为想省一点劲找一天BUG找到是这回事。

使用场景

关于序列化就简单纪录一下,相关应用场境比如需要保留数据细节、传输数据信息、克隆数据、跨平台使用等。因为序列化是标准化的所以当我们可以不遗失细节的操作对象后,对各种场景编码的自由度或者说限制实现起来更方便。

附注

其实从上面小例子中也可以看出来它是怎么标识结构的,类似键值的深层数据格式。 如 O:5:"Phone":2:{s:2:"ID";b:0;s:2:"SN";s:5:"SN001";} ,O:5:”Phone” 代表是一个名字是Phone的Object,5是Phone的字符长度; 2:{} 代表其中包含2个内部细节; s:2:”ID” 代表String长度2的属性ID; b:0 代表 Bool 类型的 FALSE….其实不用多说什么,对于编码的同学来说一看就清楚。 额外注意: 序列化结果对 private 属性会在名前添加类名;protected 属性会在名前添加“*”;这些前置值在两边都有 null 字节。

By 雪峰

One thought on “php的序列化与反序列化”

回复 OQSi 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注