4

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.

Community
  • 1
  • 1
Jaap van Hengstum
  • 4,494
  • 4
  • 30
  • 34

2 Answers2

3

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 }
Jaap van Hengstum
  • 4,494
  • 4
  • 30
  • 34
  • 1
    Wow, all of that because of a bug where Symfony can't serialize Entities embedded within other Entitites. – eggmatters Jul 14 '16 at 18:38
1

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.

Jovan Perovic
  • 19,846
  • 5
  • 44
  • 85
  • 1
    I think Doctrine should have an option to disable lazy loading (and not eager load). IMHO, lazy loading in an ORM is an anti pattern / leaky abstraction. Anyway I came up with a solution that doesn't involve changing the JMSSerializer code. I don't know if it is the "official" way to do this, but it seems to work for me. – Jaap van Hengstum Jan 12 '15 at 21:50