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

PHP序列化和反序列化

时间:2023-03-30 05:08:17 PHP

Serialization序列化格式在PHP中,序列化是在PHP中存储或传递值而不丢失其类型和结构的过程。序列化函数的原型如下:stringserialize(mixed$value)先看下面的例子:classCC{public$data;私人$通行证;公共函数\_\_construct($data,$pass){$this->data=$data;$this->pass=$pass;}}$number=34;$str='uusama';$bool=true;$null=NULL;$arr=array('a'=>1,'b'=>2);$cc=newCC('uu',true);var\_dump(序列化($number));var\_dump(序列化($str));var\_dump(序列化($bool));var\_dump(serialize($null));var\_dump(serialize($arr));var\_dump(serialize($cc));输出结果为:string(5)"i:34;"string(13)"s:6:"uusama";"string(4)"b:1;"string(2)"N;"string(30)"a:2:{s:1:"a";i:1;s:1:"b";i:2;}"string(52)"O:2:"CC":2:{s:4:"data";s:2:"uu";s:8:"CCpass";b:1;}"所以不同类型序列化得到的字符串格式为:String:s:size:value;Integer:i:value;Boolean:b:value;(save1or0)Null:N;Array:a:size:{keydefinition;valuedefinition;(repeatedperelement)}Object:O:strlen(objectname):objectname:objectsize:{s:strlen(propertyname):propertyname:propertydefinition;(每个属性重复y)}序列化对象从上面的例子我们可以看出,在序列化一个对象时,只会保存属性值,那么对象中的常量会被保存吗?如果是继承,父类的变量会不会被保存?CB类{public$CB\_data='cb';}CC类扩展CB{constSECOND=60;公共$数据;私人$通行证;公共函数\_\_construct($data,$pass){$this->data=$data;$this->pass=$pass;}publicfunctionsetPass($pass){$this->pass=$pass;}}$cc=newCC('uu',true);var\_dump(serialize($cc));输出结果为:string(75)"O:2:"CC":3:{s:4:"data";s:2:"uu";s:8:"CCpass";b:1;s:7:"CB\_data";s:2:"cb";}"显然,序列化时对象,它不会保存常量的值。对于父类中的变量,它会被保留。对象序列化定制在序列化对象时,我们不需要在对象中保存一些敏感的属性。我们应该如何处理?当调用serialize()函数序列化一个对象时,该函数会检查类中是否有魔术方法__sleep()。如果存在,将在执行序列化操作之前调用此方法。您可以通过重载此方法来自定义序列化行为。该方法的原型如下:publicarray\_\_sleep(void)该方法返回一个数组,其中包含对象中应序列化的所有变量名。如果该方法没有返回任何内容,则序列化为NULL并产生E_NOTICE级别错误__sleep()无法返回父类的私有成员的名称。这样做会产生E_NOTICE级别的错误。这时只能使用Serializable接口来代替。常用于清理大对象,避免保存过多的冗余数据。请参见以下示例:classUser{constSITE='uusama';公共$用户名;公共$昵称;私人$密码;公共函数\_\_construct($username,$nickname,$password){$this->username=$username;$this->昵称=$昵称;$this->密码=$密码;}//重载序列化的方法调用publicfunction\_\_sleep(){//返回需要序列化的变量名,过滤掉密码变量returnarray('username','nickname');}}$user=newUser('uusama','uu','123456');var\_dump(serialize($user));返回结果如下,显然序列化时忽略了password字段的值。string(67)"O:4:"用户":2:{s:8:"用户名";s:6:"uusama";s:8:"昵称";s:2:"uu";}"序列化对象存储通过上面的介绍,我们可以将一个复制的对象或者数据序列化为一个序列字符串,保存值的同事也保存了他们的结构体。我们可以将序列化后的值保存在文件或缓存中。不建议存入数据库,可读性检查,不易迁移维护,不易查询。$user=newUser('uusama','uu','123456');$ser=serialize($user);//保存在本地文件\_put\_contents('user.ser',$ser);如何使用序列化通过上面的讲解,我们可以将对象序列化为字符串保存起来,那么如何将这些序列化后的字符串恢复到原来的状态呢?PHP提供反序列化函数:mixedunserialize(string$str)unserialize()反序列化函数用于将单个序列化变量转换回PHP值。如果传入的字符串无法反序列化,则返回FALSE并产生一个E_NOTICE返回值为转换后的值,可以是整数``float,string,arrayorobject如果要反序列化的变量是对象,则返回成功重构对象后,PHP会自动尝试调用__wakeup()成员函数(如果存在)。请参见以下示例:classUser{constSITE='uusama';公共$用户名;公共$昵称;私人$密码;私人订单;公共函数\_\_construct($username,$nickname,$password){$this->username=$username;$this->昵称=$昵称;$this->密码=$密码;}//定义反序列化后调用的方法publicfunction\_\_wakeup(){$this->password=$this->username;}}$user\_ser='O:4:"用户":2:{s:8:"用户名";s:6:"uusama";s:8:"昵称";s:2:"uu";}';var\_dump(unserialize($user\_ser));输出是:object(User)#1(4){\["username"\]=>string(6)"uusama"\["nickname"\]=>string(2)"uu"\["password":"User":private\]=>string(6)"uusama"\["order":"User":private\]=>NULL}可以得出如下结论:__wakeup()函数是在构造对象后执行的,所以当$this->username的值不为空反序列化时,会尝试匹配变量值复制到未定义类处理的序列化对象。在上面的例子中,我们在调用反序列化函数unserialize(),User类是预先定义好的,如果我们不定义它会发生什么?$user\_ser='O:4:"用户":2:{s:8:"用户名";s:6:"uusama";s:8:"昵称";s:2:"uu";}';var\_dump(unserialize($user\_ser));本例中,我们没有定义任何User类,反序列化正常,没有报错。结果如下:object(\_\_PHP\_Incomplete\_Class)#1(3){\["\_\_PHP\_Incomplete\_Class\_Name"\]=>string(4)"User"\["username"\]=>string(6)"uusama"\["nickname"\]=>string(2)"uu"}注意和之前定义User类的结果相比,这里反序列化得到的对象是__PHP_Incomplete_Class,指定未定义类的类名。如果此时我们使用这个反序列化的未知对象,就会抛出E_NOTICE。好像不能用也不是解决办法,那怎么处理呢?有两种选择。定义__autoload()等函数,指定发现未定义类时加载的定义文件。您可以通过php.ini、ini_set()或.htaccess定义unserialize_callback_func。每次实例化未定义的类时都会调用它。以上两种方案的实现如下://unserialize\_callback\_funcAvailablefromPHP4.2.0ini\_set('unserialize\_callback\_func','mycallback');//设置你的回调函数functionmycallback($classname){//只需要包含包含类定义的文件//$classname表示需要哪个类}//建议使用下面的函数而不是\_\_autoload()spl\_autoload\_register(function($class\_name){//动态加载未定义类的定义文件require\_once$class\_name.'.php';});PHP预定义的序列化接口Serializable还记得上面序列化过程遇到的问题吗:__sleep()方法中不能返回父类对象,方法是实现序列化接口Serializable。接口原型如下:Serializable{abstractpublicstringserialize(void)abstractpublicmixedunserialize(string$serialized)}需要注意的是,如果定义的类实现了Serializable接口,那么在进行序列化和反序列化时,PHP就可以了将不再调用__sleep()方法和__wakeup()方法。CB类实现Serializable{public$CB\_data='';私人$CB\_password='ttt';公共函数setCBPassword($password){$this->CB\_password=$password;}publicfunctionserialize(){echo\_\_METHOD\_\_."\\n";返回序列化($this->CB\_password);}publicfunctionunserialize($serialized){echo\_\_METHOD\_\_."\\n";}}类CC扩展CB{constSECOND=60;公共$数据;私人$通行证;公共函数\_\_construct($data,$pass){$this->data=$data;$this->pass=$pass;}publicfunction\_\_sleep(){//输出调用了该方法名echo\_\_METHOD\_\_."\\n";}publicfunction\_\_wakeup(){//输出调用了该方法名echo\_\_METHOD\_\_."\\n";}}$cc=newCC('uu',true);$ser=serialize($cc);var\_dump($ser);$un\_cc=unserialize($ser);var\_dump($un\_cc);运行结果为:CB::serializestring(24)"C:2:"CC":10:{s:3:"ttt";}"CB::unserializeobject(CC)#2(4){\["data"\]=>NULL\["pass":"CC":private\]=>NULL\["CB\_data"\]=>string(0)""\["CB\_password":"CB":private\]=>string(3)"ttt"}可以完全定义serialize()方法,这个方法返回的值是序列化后花括号里面的值,只要自定义序列化和反序列化的规则一致即可。题外话在我看来,序列化和反序列化是一种传输抽象的思想数据。通过定义序列化和反序列化的规则,我们可以实现将PHP中的对象序列化为字节流,然后传输给其他语言或系统,在远程调用中非常方便。更多学习内容可以从码农到架构师的修炼之路