Adrian
Adrian

Reputation: 1370

PHP Serialized Object Explanation

My project communicates with an API that returns a string with a PHP serialized object. Normally, the response is a serialized array & there's no issue unserializing it. However, we just encountered an endpoint where it returns an object we had not encountered before and I'm not sure how to create a Class that can be used to unserialize the object.

This is what comes back from the API (just showing the part that is confusing):

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";
        }
    }
}

Full serialized string:

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;}}}

It seems like it's an array of Objects / Classes (?) that just has an array? I've tried to look for what the C symbol means, but no success. Any idea of what the structure of Transfer_Sales_Order_Item should look like?

My guess is because the original class implements ArrayAccess, but I tried that & unserialized still complained it didn't have a target to unserialize to.

For the moment we solved it by implementing Serializable on the class & the unserialize() method which gets the contents of the class as a serialized string. This then unserializes normally to an array & we went from there.

This is the code we're using to unserialize the string:

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;
    }
}

After adding an empty class Transfer_Sales_Order_Item, PHP throws

Warning: Class Transfer_Sales_Order_Item has no unserializer

Note that this is not the exception from the unserialize_callback_func callback. So it doesn't even get there.

Upvotes: 0

Views: 605

Answers (1)

iam-decoder
iam-decoder

Reputation: 2564

Add this somewhere in your script and it should solve your problem

<?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');

once your ini_set is registered, it will call unserialize_callback_func() each time unserialize runs into an object that doesn't exist. However, it needs to be registered prior to calling unserialize()

this callback function will create a class named whatever you want, allowing unserialize to set each property as a publicly accessible property.

if eval() is not allowed on your server, you can define each class needed inside the function

EDIT:

Since you gave me your class, and the full serialize string, I've fixed your class up. This is the script I used to test it all out:

<?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;

EDIT 2:

you're receiving data serialized in that way because, when serialize() is called on a class, without implementing a serialize difference, it will pull out all of the properties in such a way that it looks like an array. I used part of your provided information to create a test script:

<?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);

will output:

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";}

however using this class instead:

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'
    ;
}

will output the class name prefixed to each property to show that the property belongs solely to the class.

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";}

and for consistency sake if it were changed to protected, an * will be prefixed to each property to show that the property belongs to all classes inside the class inheritance structure.

the reason for all of this, is a simple way of showing how the properties exist in a class, and a string representation to show such a thing.

Upvotes: 0

Related Questions