4
votes

Using Doctrine 2, is it possible to either:

  • Exclude a property from the generated proxy class?
  • Disable lazy loading / proxy generation altogether?

I'm having problems serializing my entities (using Symfony and JMS Serializer). I want to only serialize the associated entities that I explicitly fetch in my query.

The solution described in f.e. Disable Doctrine 2 lazy loading when using JMS Serializer? is only partially working. When you have a virtual property:

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as Serializer;

/**
 * Profile
 *
 * @ORM\Table(name="profile")
 * @ORM\Entity
 */
class Profile
{
    // ...

    /**
     * @return string
     *
     * @Serializer\VirtualProperty()
     */
    public function getLabel()
    {
        return implode(' ', [$this->firstname, $this->lastname]) . " ({$this->email})";
    }
}

the associated class is still loaded through the proxy in the serialization process.

2

2 Answers

3
votes

This is the best solution I came up with so far to solve the above problem. It doesn't involve changing the JMSSerializer code. Full code is in this Gist: https://gist.github.com/Jaap-van-Hengstum/0d400ea4f986d8f8a044

The trick is to create an empty "fake" class:

namespace MyApp\ApiBundle\Serializer;

class SerializerProxyType
{
  // this class is supposed to be empty
}

and in the custom DoctrineProxySubscriber, set the event type to that class. This way JMSSerializer will use that type for annotation processing so it doesn't trigger the Doctrine proxy when encountering annotations like @VirtualProperty.

class DoctrineProxySubscriber implements EventSubscriberInterface
{
    public function onPreSerialize(PreSerializeEvent $event)
    {
        $object = $event->getObject();
        $type = $event->getType();

        ...
        // This line is commented, so proxy loading on serializing is disabled
        // $object->__load();

        if ( ! $virtualType) {
            // This line is commented because a different type is used
            // $event->setType(get_parent_class($object));

            // This assumes that every Doctrine entity has a single 'Id' primary
            // key field.
            $event->setType('MyApp\ApiBundle\Serializer\SerializerProxyType',
                ["id" => $object->getId()]);
        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            array('event' => 'serializer.pre_serialize', 'method' => 'onPreSerialize'),
        );
    }
}

Then you can use a JMSSerializer handler to add a custom handler for the empty class. This handler will just include the ID of the entity in the serialized json/xml:

class DoctrineProxyHandler implements SubscribingHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public static function getSubscribingMethods()
    {
        $methods = [];

        foreach (array('json', 'xml') as $format)
        {
            $methods[] = [
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format' => $format,
                'type' => 'MyApp\\ApiBundle\\Serializer\\SerializerProxyType',
                'method' => 'serializeTo' . ucfirst($format),
            ];
        }

        return $methods;
    }

    public function serializeToJson(VisitorInterface $visitor, $entity, array $type, Context $context)
    {
        $object = new \stdClass();
        $object->id = $type['params']['id'];

        return $object;
    }

    public function serializeToXml(XmlSerializationVisitor $visitor, $entity, array $type, Context $context)
    {
        $visitor->getCurrentNode()->appendChild(
            $node = $visitor->getDocument()->createElement('id', $type['params']['id'])
        );

        return $node;
    }
}

To configure Symfony to use these classes:

parameters:
    jms_serializer.doctrine_proxy_subscriber.class: MyApp\ApiBundle\Serializer\DoctrineProxySubscriber

services:
  doctrineproxy_handler:
    class: MyApp\ApiBundle\Serializer\DoctrineProxyHandler
    tags:
        - { name: jms_serializer.subscribing_handler }
1
votes

I think this is a long-going bug in JMSSerializer. Doctrine actually does pretty decent job when generating proxies/objects. In one instance I edited JMSSerializer source in order to disable loading objects from proxies. I always found that really annoying.

Possible workaround #1: Set NULL values prior to serialization. You will loose proxy reference for further use and, yes, it's very ugly but it does the job.

Possible workaround #2: I might be wrong but I get the feeling that development of JMSSerializer has stalled. You could fork the project to your own GitHub, disable lines that do the fetching and use your own fork instead.