当前位置: 首页 > 后端技术 > PHP

在PHP中使用Serializable接口自定义类的序列化

时间:2023-03-29 18:08:28 PHP

关于PHP中的对象序列化,我们在很久以前的[]()一文中提到过__sleep()和__weakup()这两个神奇的方法。今天我们介绍另一种控制序列化内容的方法,就是使用Serializable接口。它的用法与上述两种法术很相似,但又略有不同。Serializable接口类A实现Serializable{private$data;公共函数__construct(){echo'__construct',PHP_EOL;$this->data="这是A类";}publicfunctionserialize(){echo'serialize',PHP_EOL;返回序列化($this->data);}publicfunctionunserialize($data){echo'unserialize',PHP_EOL;$this->data=unserialize($data);}公共函数__destruct(){echo'__destruct',PHP_EOL;}公共函数__weakup(){echo'__weakup',PHP_EOL;}公共函数__sleep(){echo'__destruct',PHP_EOL;}}$a=newA();$aSerialize=serialize($a);var_dump($aSerialize);//"C:1:"A":23:{s:15:"ThisisClassA";}"$a1=unserialize($aSerialize);var_dump($a1);这段代码使用Serializable接口进行序列化。注意实现了Serializable接口的类中的__sleep()和__weakup()魔术方法是无效的,在序列化时不会进入。Serializable接口需要实现两个方法,serialize()方法和unserialize()方法,它们和那两个魔术方法是一模一样的吗?当然,使用方法是一样的。在这里,我们再普及一下连载的知识。对象序列化只能序列化它们的属性,不能序列化它们的方法。如果当前能找到对应的类模板,那么就可以恢复这个类的方法。如果这个类的模板还没有定义,那么恢复后的类就没有方法,只有属性。我们分析这段代码中的序列化字符串:“C:”指的是当前数据的类型。这个我以后再说。实现了Serializable接口的对象的序列化结果是C:,而没有实现这个接口的对象序列化结果是O:"A:",显然对应的是类名,也就是::班级的班级“{xxx}”。对象结构与JSON相同,同样使用花括号。不同类型数据的序列化结果我们来看一下不同类型数据序列化的结果。要知道,在PHP中,除了句柄类型的数据,其他标量类型或者数组、对象都可以序列化。它们在序列化字符串中是如何表示的?$int=110;$string='110';$bool=FALSE;$null=NULL;$array=[1,2,3];var_dump(serialize($int));//“我:110;”var_dump(序列化($string));//"s:3:"110";"var_dump(serialize($bool));//"b:0;"var_dump(序列化($null));//"N;"var_dump(serialize($array));//"a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}"上面的内容还是比较容易理解的。不过我们一一解释:Number类型:i:String类型:s::Boolean类型:b:NULL类型:N;Array:a::使用Serializable接口序列化对象时需要注意的事项。上面序列化后的字符串的类型被标识为“C:”,那么我们来看看一个没有实现Serializable接口的对象序列化后会发生什么。//普通对象类型序列化的结果classB{private$data="ThisisClassB";}$b=newB();$bSerialize=serialize($b);var_dump($bSerialize);//"O:1:"B":1:{s:7:"Bdata";s:15:"这是B类";}"var_dump($bSerialize);var_dump(unserialize("O:1:\"B\":1:{s:7:\"\0B\0data\";s:15:\"这是B类\";}"));//对象(B)#4(1){//["data":"B":private]=>string(15)"ThisisClassB"//}果然,它开头的类型标识是“O:”。那么我们可以看到“C:”大概率是当前序列化的内容是类类型,而不是对象类型。它们之间实际上没有显着差异,包括在官方文档中找不到具体说明。如果大家有做过这方面的研究或者有相关资料,可以评论留言一起讨论。另外,如果我们手动将对象从“O:”转换为“C:”怎么办?//用C:替换O:var_dump(unserialize(str_replace('O:','C:',$bSerialize)));//false抱歉,无法恢复。那么反过来,我们将上面实现了Serializable接口的类A的序列化字符串中的“C:”转换为“O:”呢?//警告:反序列化'A'的错误数据格式var_dump(unserialize(str_replace('C:','O:',$aSerialize)));//false嗯,会提示警告,然后就不能恢复了。从这点来看,我们的反序列化还是很聪明的,稍有不同就无法进行恢复操作。未定义类的反序列化最后,我们看一下在没有定义类的情况下直接反序列化一个对象。//模拟一个未定义的类Dvar_dump(unserialize("O:1:\"D\":2:{s:7:\"\0D\0data\";s:15:\"ThisisClassD\";s:3:\"int\";i:220;}"));//对象(__PHP_Incomplete_Class)#4(3){//["__PHP_Incomplete_Class_Name"]=>string(1)"D"//["data":"D":private]=>string(15)"ThisisClassD"//["int"]=>int(220)//}//O:ReplacewithC:var_dump(unserialize(str_replace('O:','C:',"O:1:\"D\":2:{s:7:\"\0D\0data\";s:15:\"这是D类\";s:3:\"int\";i:220;}")));//false从代码中可以看出,“C:”类型的字符串仍然无法反序列化成功。关键是,如果是C:开头的序列化字符串,必须是已经定义并实现了Serializable接口的类,才能反序列化成功。另外我们可以发现,当序列化后的字符串中的模板不存在时,反序列化后的类的类名是__PHP_Incomplete_Class_Name类,不像反序列化成功的类模板直接是一个普通的类名。总结其实从以上几个方面来看,个人觉得如果要保存数据或者传输数据,序列化并不是最好的选择。毕竟包含类型和长度会让格式更加严格,反序列化后的内容如果没有对应的类模板定义,也不是特别好用。不如直接使用JSON方便易读。当然,根据具体情况具体分析,我们还是需要结合场景选择合适的使用方式。测试代码:https://github.com/zhangyue0503/dev-blog/blob/master/php/202003/source/%E4%BD%BF%E7%94%A8Serializable%E6%8E%A5%E5%8F%A3%E6%9D%A5%E8%87%AA%E5%AE%9A%E4%B9%89PHP%E4%B8%AD%E7%B1%BB%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96.php参考文档:https://www.php.net/manual/zh/class.serializable.php============各媒体平台可以搜索【硬核项目经理】