Я использую Doctrine 2 ORM в моем проекте Zend и нуждаюсь в сериализации моих Entities в JSON в нескольких случаях.
ATM я использую Querybuilder и присоединяюсь ко всем таблицам, которые мне нужны. Но мой сериализатор вызывает доктрину ленивую загрузку каждого связанного объекта, что приводит к довольно огромным объемам данных и провоцирует рекурсию.
Теперь они ищут способ полностью отключить поведение Doctrines lazy loading.
Мой путь к выбору данных будет следующим:
$qb= $this->_em->createQueryBuilder() ->from("\Project\Entity\Personappointment", 'pa') ->select('pa', 't', 'c', 'a', 'aps', 'apt', 'p') ->leftjoin('pa.table', 't') ->leftjoin('pa.company', 'c') ->leftjoin('pa.appointment', 'a') ->leftjoin('a.appointmentstatus', 'aps') ->leftjoin('a.appointmenttype', 'apt') ->leftjoin('a.person','p')
Я хотел бы, чтобы в моем результирующем наборе содержались только выбранные таблицы и ассоциации.
Любая помощь будет принята с благодарностью.
В последней версии JMSSerializer место, на которое вы должны посмотреть, – это
JMS\Serializer\EventDispatcher\Subscriber\DoctrineProxySubscriber
вместо
Serializer\Handler\DoctrineProxyHandler
Чтобы переопределить поведение ленивой загрузки по умолчанию, нужно определить его собственный подписчик событий.
В вашем app/config.yml
добавьте следующее:
parameters: ... jms_serializer.doctrine_proxy_subscriber.class: Your\Bundle\Event\DoctrineProxySubscriber
вы можете скопировать класс из JMS \ Serializer \ EventDispatcher \ Subscriber \ DoctrineProxySubscriber в свой \ Bundle \ Event \ DoctrineProxySubscriber и прокомментировать $ object -> __ load (); линия
public function onPreSerialize(PreSerializeEvent $event) { $object = $event->getObject(); $type = $event->getType(); // If the set type name is not an actual class, but a faked type for which a custom handler exists, we do not // modify it with this subscriber. Also, we forgo autoloading here as an instance of this type is already created, // so it must be loaded if its a real class. $virtualType = ! class_exists($type['name'], false); if ($object instanceof PersistentCollection) { if ( ! $virtualType) { $event->setType('ArrayCollection'); } return; } if ( ! $object instanceof Proxy && ! $object instanceof ORMProxy) { return; } //$object->__load(); Just comment this out if ( ! $virtualType) { $event->setType(get_parent_class($object)); } }
После поиска ответа в Доктрине моя команда выяснила, что JMS Serializer является «проблемой». Это вызвало автоматическое использование Doctrine Proxies. Мы написали патч для JMS Serializer, чтобы избежать ленивой загрузки.
Мы внедрили собственный DoctrineProxyHandler, который просто не запускает механизм lazyloading Doctrines и не зарегистрировал его в нашем массиве SerializationHandlers.
class DoctrineProxyHandler implements SerializationHandlerInterface { public function serialize(VisitorInterface $visitor, $data, $type, &$handled) { if (($data instanceof Proxy || $data instanceof ORMProxy) && (!$data->__isInitialized__ || get_class($data) === $type)) { $handled = true; if (!$data->__isInitialized__) { //don't trigger doctrine lazy loading //$data->__load(); return null; } $navigator = $visitor->getNavigator(); $navigator->detachObject($data); // pass the parent class not to load the metadata for the proxy class return $navigator->accept($data, get_parent_class($data), $visitor); } return null; }
Теперь я могу просто выбрать свою таблицу, присоединиться к ассоциациям, которые мне нужны – и мой JSON будет содержать только данные, которые я выбрал, вместо бесконечных ассоциаций и рекурсий глубины 🙂
$qb= $this->_em->createQueryBuilder() ->from("\Project\Entity\Personappointment", 'pa') ->select('pa', 't', 'c', 'a') ->leftjoin('pa.table', 't') ->leftjoin('pa.company', 'c') ->leftjoin('pa.appointment', 'a')
JSON будет просто содержать
{ Personappointment: { table {fields}, company {fields}, appointment {fields}} Personappointment: { table {fields}, company {fields}, appointment {fields}} Personappointment: { table {fields}, company {fields}, appointment {fields}} . . }
Это вполне можно назвать уродливым костылем, но вы можете просто выбрать () данные, которые вам действительно нужны, а затем убрать результат в массив с помощью метода getArrayResult () объекта Query …
При использовании построителя запросов Doctrine вы не можете отключить ленивую загрузку связанных классов моделей. Если вы хотите обойти такое поведение, вам лучше запросить данные с DBAL Doctrine.
Не используйте \Doctrine\ORM\QueryBuilder
но \Doctrine\DBAL\Query\QueryBuilder
.
$qb = new QueryBuilder($this->_em->getConnection()); $expr = $qb->expr(); $qb->select('pa.*', 't.*', 'c.*', 'a.*', 'aps.*', 'apt.*', 'p.*') ->from('person_appointment', 'pa') ->leftJoin('pa', 'table', 't', $expr->eq('pa.table_id', 't.table_id')) // put other joints here // ... ->leftjoin('a', 'person', 'p', $expr->eq('a.person_id', 'p.person_id'));
Дело в том, что вы прагматично используете своего или по умолчанию подписчика,
@DavidLin ответ:
вы можете скопировать класс из JMS \ Serializer \ EventDispatcher \ Subscriber \ DoctrineProxySubscriber в свой \ Bundle \ Event \ DoctrineProxySubscriber и прокомментировать $ object -> __ load (); линия
<?php /* * Copyright 2013 Johannes M. Schmitt <schmittjoh@gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ namespace Your\Bundle\Event; use Doctrine\ORM\PersistentCollection; use Doctrine\ODM\MongoDB\PersistentCollection as MongoDBPersistentCollection; use Doctrine\ODM\PHPCR\PersistentCollection as PHPCRPersistentCollection; use Doctrine\Common\Persistence\Proxy; use Doctrine\ORM\Proxy\Proxy as ORMProxy; use JMS\Serializer\EventDispatcher\PreSerializeEvent; use JMS\Serializer\EventDispatcher\EventSubscriberInterface; class AvoidDoctrineProxySubscriber implements EventSubscriberInterface { public function onPreSerialize(PreSerializeEvent $event) { $object = $event->getObject(); $type = $event->getType(); // If the set type name is not an actual class, but a faked type for which a custom handler exists, we do not // modify it with this subscriber. Also, we forgo autoloading here as an instance of this type is already created, // so it must be loaded if its a real class. $virtualType = ! class_exists($type['name'], false); if ($object instanceof PersistentCollection || $object instanceof MongoDBPersistentCollection || $object instanceof PHPCRPersistentCollection ) { if ( ! $virtualType) { $event->setType('ArrayCollection'); } return; } if ( ! $object instanceof Proxy && ! $object instanceof ORMProxy) { return; } //Avoiding doctrine lazy load proxyes //$object->__load(); if ( ! $virtualType) { $event->setType(get_parent_class($object)); } } public static function getSubscribedEvents() { return array( array('event' => 'serializer.pre_serialize', 'method' => 'onPreSerialize'), ); } }
И инициализируйте сериализацию следующим образом :
$serializer = JMS\Serializer\SerializerBuilder::create() //remove this to use lazy loading ->configureListeners(function(JMS\Serializer\EventDispatcher\EventDispatcher $dispatcher) { $dispatcher->addSubscriber(new Your\Bundle\Event\AvoidDoctrineProxySubscriber()); }) // !remove this to use lazy loading ->build(); //and serialize the data with/without Lazy serializer->serialize($data, 'json');