Symfony2: Profiler trying to serialize objects or how to build your own router

The other day, i wanted to write a controller that expects an object as an action parameter, with the help of a custom router. Writing the router was as easy as adding a field to the array i return in the match() method. But then sometimes the controller has to send a redirection response. In production mode, everything works fine.
But in debug mode, i got a - very non-telling - error about serializing:

Notice: serialize(): "controller" returned as member variable from __sleep() but does not exist in
/home/david/liip/symfony-cmf/cmf-sandbox/vendor/symfony/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php line 30

When you redirect, the profiler serializes a lot of things so you are able to see what the original request was on the redirection target page.
To figure out what was causing the problem, i hacked into vendor/symfony/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php to unset pieces of $this->data until i could see what was causing the problem (look for Object() in the stacktrace about parameters to serialize() )

Sure enough, when unsetting my object, the problem went away. Now serializing the object itself would even work, the problem in the end was in an object referenced from my object. Doctrine uses proxy classes here to avoid loading you half of your database if it might not be needed. But serializing the proxied thing led to failure. Doctrine recommends to avoid serializing entities. I could work around the problem by having my entity class return an empty array in __sleep() so the referenced class would never be serialized. But i would need to do that with every entity which is tricky.

Lukas suggested putting the object into the request attributes instead of into the route defaults. And indeed that works perfectly. My object is no longer serialized, but the controller action can still expect it as parameter.

In short:

Your controller action declares parameters it expects, i.e.

class MyController
{
    public function myAction($myParameter)
    {
        ...
    }
}

Symfony looks both in the route defaults and in the request attributes for a field named 'myParameter' to pass it to your action.

When implementing your own router, you have two options of making that parameter available:

class MyRouter implements RouterInterface
public function match()
{
    $defaults = array();
    $defaults['_controller'] = '@MyBundle:MyController:myAction';
    $defaults['myParameter'] = $value; // this adds to route default, profiler will serialize it
    return $defaults;
}

Or the better option if you want to pass an object:

...
    $this->container->get('request')->attributes->set('myParameter', $value);
...

For the real-world use case, seee DoctrineRouter::match.

Comments

serializing proxies

was pointed to this blog post in case you really need to serialize: use a proxy object you write on your own to serialize and unserialize:
http://danosipov.com/blog/?p=377

not sure how well this translates into symfony2, as there is no global registry afaik.