【问题标题】:PHP Serialized Object ExplanationPHP序列化对象说明
【发布时间】:2015-11-11 19:52:47
【问题描述】:

我的项目与返回带有 PHP 序列化对象的字符串的 API 通信。通常,响应是一个序列化的数组,并且反序列化没有问题。但是,我们刚刚遇到了一个端点,它返回了一个我们以前没有遇到过的对象,我不确定如何创建一个可用于反序列化对象的类。

这是从 API 返回的内容(仅显示令人困惑的部分):

s:8:" * _data";a:1:{
    i:4057353;C:25:"Transfer_Sales_Order_Item":201:{
        a:5:{
            s:8:"quantity";i:1;
            s:19:"id_sales_order_item";s:7:"4057353";
            s:4:"name";s:44:"Empacadora Selladora FoodSaver al Alto Vacio";
            s:10:"paid_price";d:796.13999999999999;
            s:3:"sku";s:15:"LI200HL37WWCLMX";
        }
    }
}

完整的序列化字符串:

O:16:"Service_Response":8:{s:11:" * _success";b:1;s:17:" * _errorMessages";a:0:{}s:19:" * _successMessages";a:0:{}s:22:" * _validationMessages";a:0:{}s:18:" * _noticeMessages";a:0:{}s:14:" * redirectUrl";N;s:23:" Service_Abstract _type";N;s:14:" * _resultData";a:1:{s:24:"NotAvailablesForTracking";O:40:"Transfer_Sales_Order_ItemCollection_test":6:{s:19:" * _objectClassName";s:25:"Transfer_Sales_Order_Item";s:15:" * _idAttribute";s:19:"id_sales_order_item";s:47:" Vendor_Transfer_AbstractCollection _indexDirty";b:0;s:8:" * _data";a:1:{i:4057353;C:25:"Transfer_Sales_Order_Item":201:{a:5:{s:8:"quantity";i:1;s:19:"id_sales_order_item";s:7:"4057353";s:4:"name";s:44:"Empacadora Selladora FoodSaver al Alto Vacio";s:10:"paid_price";d:796.13999999999999;s:3:"sku";s:15:"LI200HL37WWCLMX";}}}s:17:" * _setDataStrict";b:1;s:9:" * locale";N;}}}

看起来它是一个只有一个数组的对象/类(?)数组?我试图寻找 C 符号的含义,但没有成功。知道Transfer_Sales_Order_Item 的结构应该是什么样的吗?

我的猜测是因为原始类实现了ArrayAccess,但我尝试了它并且未序列化仍然抱怨它没有要反序列化的目标。

目前我们通过在类上实现Serializableunserialize() 方法来解决它,该方法将类的内容作为序列化字符串获取。然后,这通常会反序列化为一个数组,然后我们就从那里开始了。

这是我们用来反序列化字符串的代码:

class Transformer
{
    public static function debugUnserialize($className)
    {
        throw new \RuntimeException(sprintf('Cannot unserialize object %s', $className, 500);
    }

    public function transform($value)
    {
        $previousCallback = ini_get('unserialize_callback_func');
        ini_set('unserialize_callback_func', self::class . '::debugUnserialzie');
        $data = unserialize($value);
        ini_set('unserialize_callback_func', $previousCallback);
        return $data;
    }
}

添加空类Transfer_Sales_Order_Item后,PHP抛出

Warning: Class Transfer_Sales_Order_Item has no unserializer

请注意,这不是unserialize_callback_func 回调的例外情况。所以它甚至没有到达那里。

【问题讨论】:

  • @h2ooooooo 现在检查 - 使用换行符它不会反序列化过去 _data....
  • @MichaelBerkowski 看来你是对的!我已经删除了我的评论,因为这在这件事上毫无意义。 :)
  • 简单地 preg_replace()'ing 所有额外的空格并没有帮助。
  • @h2ooooooo 反序列化会引发错误,因为它没有C:25:"Transfer_Sales_Order_Item" 的目标类。如果您添加Transfer_Sales_Order_Item,它仍然会抱怨没有目标并且没有将数组映射到的属性。非常令人费解。
  • @Adrian, C: 在 php 5.6 中被替换为 O:,这意味着发回序列化的服务器是 php5.6 之前的。这意味着数据类型是需要unserialize_callback_func 才能正确处理反序列化的对象。这是unserialize() 上的文档,其中包含使用回调的示例

标签: php serialization


【解决方案1】:

在你的脚本中添加这个,它应该可以解决你的问题

<?php
function unserialize_callback_func($fqcn){
    $explode = explode("\\", $fqcn);
    $namespace = implode("\\", array_slice($explode, 0, count($explode)-1));
    $className = end($explode);
    $dev = "
        namespace $namespace;
        class $className{

        }
    ";
    eval($dev);
};
ini_set('unserialize_callback_func', 'unserialize_callback_func');

一旦你的 ini_set 被注册,它会在每次unserialize 遇到一个不存在的对象时调用unserialize_callback_func()。不过调用unserialize()之前需要先注册

此回调函数将创建一个任意命名的类,允许反序列化将每个属性设置为可公开访问的属性。

如果您的服务器上不允许使用eval(),您可以在函数中定义所需的每个类

编辑:

既然你给了我你的课程和完整的序列化字符串,我已经修复了你的课程。这是我用来测试的脚本:

<?php

$data = 'O:16:"Service_Response":8:{s:11:" * _success";b:1;s:17:" * _errorMessages";a:0:{}s:19:" * _successMessages";a:0:{}s:22:" * _validationMessages";a:0:{}s:18:" * _noticeMessages";a:0:{}s:14:" * redirectUrl";N;s:23:" Service_Abstract _type";N;s:14:" * _resultData";a:1:{s:24:"NotAvailablesForTracking";O:40:"Transfer_Sales_Order_ItemCollection_test":6:{s:19:" * _objectClassName";s:25:"Transfer_Sales_Order_Item";s:15:" * _idAttribute";s:19:"id_sales_order_item";s:47:" Vendor_Transfer_AbstractCollection _indexDirty";b:0;s:8:" * _data";a:1:{i:4057353;C:25:"Transfer_Sales_Order_Item":201:{a:5:{s:8:"quantity";i:1;s:19:"id_sales_order_item";s:7:"4057353";s:4:"name";s:44:"Empacadora Selladora FoodSaver al Alto Vacio";s:10:"paid_price";d:796.13999999999999;s:3:"sku";s:15:"LI200HL37WWCLMX";}}}s:17:" * _setDataStrict";b:1;s:9:" * locale";N;}}}';

class Transformer
{
    public static function debugUnserialize($className)
    {
        $explode = explode("\\", $className);
        $namespace = implode("\\", array_slice($explode, 0, count($explode)-1));
        $className = end($explode);
        $dev = "
            ".(!empty($namespace)?"namespace $namespace;":"")."
            class $className implements Serializable {
                public function unserialize(".'$data'.") {
                    ".'$this->data'." = unserialize(".'$data'.");
                }
                public function serialize() {
                    return serialize(".'$this->data'.");
                }
                public function getData(){
                    return ".'$this->data'.";
                }
            }
        ";

        eval($dev);
    }

    public function transform($value)
    {
        $previousCallback = ini_get('unserialize_callback_func');
        ini_set('unserialize_callback_func', self::class . '::debugUnserialize');
        $data = unserialize($value);
        ini_set('unserialize_callback_func', $previousCallback);
        return $data;
    }
}

$trans = new Transformer();
var_dump($trans->transform($data));exit;

编辑 2:

您正在接收以这种方式序列化的数据,因为当在一个类上调用serialize() 时,如果没有实现序列化差异,它将以看起来像一个数组的方式提取所有属性。我使用您提供的部分信息来创建测试脚本:

<?php
class Transfer_Sales_Order_Item {
    public
        $quantity = 1
    ,   $id_sales_order_item = '4057353'
    ,   $name = 'Empacadora Selladora FoodSaver al Alto Vacio'
    ,   $paid_price = 796.14
    ,   $sku = 'LI200HL37WWCLMX'
    ;
}

echo serialize(new Transfer_Sales_Order_Item);

将输出:

O:25:"Transfer_Sales_Order_Item":5:{s:8:"quantity";i:1;s:19:"id_sales_order_item";s:7:"4057353";s:4:"name";s:44:"Empacadora Selladora FoodSaver al Alto Vacio";s:10:"paid_price";d:796.13999999999999;s:3:"sku";s:15:"LI200HL37WWCLMX";}

但是改用这个类:

class Transfer_Sales_Order_Item {
    private
        $quantity = 1
    ,   $id_sales_order_item = '4057353'
    ,   $name = 'Empacadora Selladora FoodSaver al Alto Vacio'
    ,   $paid_price = 796.14
    ,   $sku = 'LI200HL37WWCLMX'
    ;
}

会输出每个属性前缀的类名,以表明该属性只属于该类。

O:25:"Transfer_Sales_Order_Item":5:{s:35:"Transfer_Sales_Order_Itemquantity";i:1;s:46:"Transfer_Sales_Order_Itemid_sales_order_item";s:7:"4057353";s:31:"Transfer_Sales_Order_Itemname";s:44:"Empacadora Selladora FoodSaver al Alto Vacio";s:37:"Transfer_Sales_Order_Itempaid_price";d:796.13999999999999;s:30:"Transfer_Sales_Order_Itemsku";s:15:"LI200HL37WWCLMX";}

为了保持一致性,如果将其更改为protected* 将作为前缀添加到每个属性,以表明该属性属于类继承结构内的所有类。

所有这一切的原因,是一种显示属性如何存在于类中的简单方法,以及显示此类事物的字符串表示形式。

【讨论】:

  • 你用序列化的字符串测试了吗?
  • 我们已经实现了一个自动加载的空类。我们最终得到相同的警告,即该类没有反序列化器。我们不知道包含数组的属性应该是什么,因为它不在序列化字符串中。我们可以访问 API 的源代码,但我们不能修改它。原始类使用魔术方法来实现属性的 getter 和 setter,并实现 ArrayAccess。这就是我所知道的。
  • 对不起,当我说我们实现了 __wakeup() 我错了。我们实现unserialize() 的方式与您在上面实现它的方式类似。所以是的,这解决了它。你不需要实现unserialize_callback_func,你可以确保类可以自动加载。
  • 它仍然没有解释 C:25:classname:208{a:1:{}} 的含义,包含的数组没有属性定义。我知道C 可能已在 5.6 中被替换(这不是手册所说的),但它没有解释数组如何存在于没有属性的对象中。
  • @Adrian unserialize() 的更改日志确实表明它已更改。我再次编辑了我的答案,以显示关于序列化如何与类一起工作的测试运行
猜你喜欢
  • 2012-04-15
  • 2014-06-15
  • 2018-01-09
  • 1970-01-01
  • 1970-01-01
  • 2015-11-07
  • 1970-01-01
  • 1970-01-01
  • 2013-02-12
相关资源
最近更新 更多