Search code examples
phpparameter-passingmagic-function

Passing a variable to __wakeup()


I am storing certain objects in the database. Each node in the database carries a serialized object. The node can have children, and thus, the object can have children too. Therefore, i want to fill a $children property in the object with an array containing its immediate children. $children should get filled when the object is unserialized.

In trying to avoid doing this from the outside i thought i'd rather let the object itself do it. However, the object does not know which database entry it belongs to. So when __wakeup() is called, the object lacks the required information to load its own, corresponding database entry.

What would seem best, would be to be able to pass a variable to __wakeup(), although i am aware that unserialize() offers no such option.

I was not able to find any information regarding this. I was about to conclude that it won't be possible, other than using a global, which is not a route i'd choose to take.

Is it possible? If yes, how?


Solution

  • The magic __wakeup() method does not allow passing of arguments. The idea here is to have everything the object needs in order to unserialize itself into a valid state in the serialized form already, e.g. it expects the object to be in a valid state when it gets serialized.

    This means, if the information which database entry a child belongs to is missing but is required for the object to be in a valid state, you should add it into the object in the first place. Then you can simply access that value when unserializing the object. This would be the cleanest solution.

    Another option would be to resort to globals. I agree, no one wants that.

    Yet another equally doubtful and incredibly magic option would be to mess with the serialized form before unserializing the object to add the missing information.

    Here's a proof of concept:

    function unserialize_with_args($string, array $args, $key = 'tmp')
    {
        // serialize $args to $key for inserting into $string
        $tmps = sprintf(
            's:%d:"%s";%s}',
            strlen($key),
            $key,
            serialize($args)
        );
    
        // increase property count in original string
        $string = preg_replace_callback(
            '/^(O:\d+:"[a-z]+":)(\d+)/i',
            function (array $matches) {
                return $matches[1] . ($matches[2] + 1);
            },
            $string
        );
    
        // insert $tmps, unserialize, remove tmps
        $instance = unserialize(substr_replace($string, $tmps, -1));
        unset($instance->$key);
    
        return $instance;
    }
    

    With that you can do:

    class Foo
    {
        public function __wakeup()
        {
            print_r($this->tmp);
        }
    }
    
    unserialize_with_args(
        serialize(new Foo), 
        [1,2,3]
    );
    

    Running that code will print (demo):

    Array
    (
        [0] => 1
        [1] => 2
        [2] => 3
    )
    

    This works because the function adds a property tmp to the serialized form holding the serialized array that was passed as $args. So when the object is woken up, it will have access to these values. The function will remove that property before the unserialized object is returned, so you can only access it during __wakeup.

    But just to be clear about this solution: this is desperate measures. Try to make the missing information available in the serialized form in the first place instead.