Доктрина 2: отключить ленивую загрузку/генерацию прокси.

Используя Doctrine 2, возможно ли:

  • Исключить свойство из сгенерированного прокси-класса?
  • Отключить ленивую загрузку/генерацию прокси вообще?

У меня проблемы с сериализацией моих сущностей (используя Symfony и JMS Serializer). Я хочу сериализовать только связанные объекты, которые я явно извлекаю в своем запросе.

Решение, описанное в f.e. Отключить ленивую загрузку Doctrine 2 при использовании JMS Serializer? работает только частично. Если у вас есть виртуальная собственность:

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

связанный класс по-прежнему загружается через прокси в процессе сериализации.


person Jaap van Hengstum    schedule 12.01.2015    source источник


Ответы (2)


Это лучшее решение, которое я придумал до сих пор, чтобы решить вышеуказанную проблему. Это не требует изменения кода JMSSerializer. Полный код находится в этом Gist: https://gist.github.com/Jaap-van-Hengstum/0d400ea4f986d8f8a044

Хитрость заключается в создании пустого «поддельного» класса:

namespace MyApp\ApiBundle\Serializer;

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

и в пользовательском DoctrineProxySubscriber установите тип события для этого класса. Таким образом, JMSSerializer будет использовать этот тип для обработки аннотаций, поэтому он не запускает прокси-сервер Doctrine при обнаружении аннотаций, таких как @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'),
        );
    }
}

Затем вы можете использовать обработчик JMSSerializer, чтобы добавить собственный обработчик для пустого класса. Этот обработчик будет просто включать идентификатор объекта в сериализованный 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;
    }
}

Чтобы настроить Symfony для использования этих классов:

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 }
person Jaap van Hengstum    schedule 12.01.2015
comment
Ничего себе, все это из-за ошибки, из-за которой Symfony не может сериализовать сущности, встроенные в другие сущности. - person eggmatters; 14.07.2016

Я думаю, что это давняя ошибка в JMSSerializer. Doctrine действительно неплохо справляется с созданием прокси/объектов. В одном случае я отредактировал исходный код JMSSerializer, чтобы отключить загрузку объектов с прокси. Меня всегда это очень раздражало.

Возможный обходной путь № 1: установите значения NULL перед сериализацией. Вы потеряете ссылку на прокси для дальнейшего использования, и да, это очень уродливо, но работает.

Возможный обходной путь № 2: я могу ошибаться, но мне кажется, что разработка JMSSerializer застопорилась. Вы можете разветвить проект на свой собственный GitHub, отключить строки, которые выполняют выборку, и вместо этого использовать свой собственный форк.

person Jovan Perovic    schedule 12.01.2015
comment
Я думаю, что у Doctrine должна быть возможность отключить ленивую загрузку (а не нетерпеливую загрузку). ИМХО, ленивая загрузка в ORM - это анти-шаблон/дырявая абстракция. В любом случае я придумал решение, которое не требует изменения кода JMSSerializer. Я не знаю, является ли это официальным способом сделать это, но, похоже, он работает для меня. - person Jaap van Hengstum; 13.01.2015