Избегание рекурсии с объектами Doctrine и JMSserializer

Я создаю REST API, используя Symfony2, Doctrine, FOSRestBundle и JMSSerializer.

Проблема, с которой я столкнулась, заключается в сериализации моих объектов, сериализатор тянет любые связанные объекты. Например, для задачи, которая является частью истории, которая является частью доски, поэтому при сериализации задачи я получаю вывод, включающий историю, которая включает в себя доску, которая затем включает в себя все другие истории на доске.

Есть ли простой способ ограничить это, и просто включить foreignIds вместо этого?

Проверьте файл Serializer / Handler / DoctrineProxyHandler.php на JMSSerializerBundle. Теперь, если вы прокомментируете эту строку:

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__) { //$data->__load(); } 

Это прекратит ленивую загрузку ваших объектов. Если это то, что вы ищете, тогда просто продолжайте и создайте свой собственный обработчик, где вы не будете ленивой загрузкой.

Если это неверно, я рекомендую вам настроить свои объекты перед отправкой их на JMSSerializerBundle по своему вкусу. Например, в любых связанных объектах я хочу ID, в то время как в других мне нужно собственное имя столбца, например код или имя, или что-то еще.

Я просто создаю копию объекта объекта, а затем начинаю получать поля, необходимые для отношений. Затем я сериализую эту копию. JMSSerializerBundle не будет лениться, потому что я уже предоставил правильные поля.

Используйте политику исключения JMS.

Пример использования аннотаций в категории, где вы не хотите включать дочерние элементы и связанные с продуктом объекты, которые должны быть включены:

 use ... JMS\SerializerBundle\Annotation\ExclusionPolicy, JMS\SerializerBundle\Annotation\Exclude, ...; /** * ... * @ExclusionPolicy("none") */ class Category { /** * ... * @Exclude */ private $children; /** * ... * @Exclude */ private $products; } 

Посмотрите на документы JMSSerializer для получения дополнительной информации.

РЕДАКТИРОВАТЬ:

Например, вы можете использовать неполное ключевое слово для выбора только тех данных, которые вам нужны. Хотя я не мог, на всю жизнь, отключить загрузку полностью связанных объектов (два уровня вниз), если я передаю объект объекта в сериализатор (даже при отключении нагрузки в DoctrineProxyHandler), но если я использую массив, не использует доктрину ленивой загрузки, хотя прокси (как и ожидалось).

Пример использования ваших объектов-примеров:

 $dql = "SELECT t, s, partial b.{id}, partial ss.{id} FROM Acme\AppBundle\Entity\Task t JOIN t.story s JOIN s.board b JOIN b.stories ss" $q = $this->_em-createQuery($dql); $result = $q->getArrayResult(); 

Таким образом, вы получите что-то вроде:

 [ { id: 33, title: "My Task", story: [ { id: 554, board: [ { id: 14, stories: [ { id: 554 }, { id: 3424 }, { id: 3487 } ] } ] } ] } ] 

PS Я действительно заинтригован этой «проблемой». В любом случае, я увижу решение о том, как сериализовать объект объекта без использования результата массива.

Просто обновление в последней версии JMSSerializer, место, на которое вы должны посмотреть, – это

JMS \ Serializer \ EventDispatcher \ Subscriber \ DoctrineProxySubscriber

вместо

Serializer \ Handler \ DoctrineProxyHandler

Чтобы переопределить поведение ленивой загрузки по умолчанию, нужно определить его собственный подписчик событий.

В вашем приложении / config.yuml добавьте следующее:

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

Обновление: в конце концов я написал собственную упрощенную версию инструмента сериализации: https://github.com/dlin-me/array-converter-bundle

Вот функция для выбора идентификаторов связанных друг с другом или один ко многим связанным объектам общим способом без использования объединений.

 function selectWithAssociations($doctrine, $className) { $em = $doctrine->getManager(); $meta = $em->getClassMetadata($className); //explicitly get IDs of associated entities $assocClauses = array(); foreach ($meta->getAssociationMappings() as $assocName => $assoc) { if (isset($assoc['joinTable'])) { //todo: doesn't handle many to many associations } else { $assocClauses[] = ", IDENTITY(e.$assocName) AS $assocName"; } } //run custom DQL query $q = $em->createQuery('SELECT e AS _d' . implode('', $assocClauses) . ' FROM ' . $className . ' e'); $result = $q->getArrayResult(); return $result; } 

Вот класс, который предотвращает ленивую загрузку одной или нескольких ассоциаций, которые могут использоваться как JMS Serializer ExclusionStrategy.

 use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Proxy\Proxy; use JMS\Serializer\Context; use JMS\Serializer\Exclusion\ExclusionStrategyInterface; use JMS\Serializer\Metadata\ClassMetadata; use JMS\Serializer\Metadata\PropertyMetadata; use JMS\Serializer\SerializationContext; /** * Class OnlyLoadedAssociationsExclusionStrategy * * http://stackoverflow.com/questions/11851197/avoiding-recursion-with-doctrine-entities-and-jmsserializer */ class OnlyLoadedAssociationsExclusionStrategy implements ExclusionStrategyInterface { public function shouldSkipClass(ClassMetadata $metadata, Context $context) { } public function shouldSkipProperty(PropertyMetadata $property, Context $context) { if ($context instanceof SerializationContext){ $vistingSet=$context->getVisitingSet(); //iterate over object to get last object foreach ($vistingSet as $v){ $currentObject=$v; } $propertyValue=$property->getValue($currentObject); if ($propertyValue instanceof Proxy){ // skip not loaded one association if (!$propertyValue->__isInitialized__){ return true; } } if ($propertyValue instanceof PersistentCollection){ // skip not loaded many association if (!$propertyValue->isInitialized()){ return true; } } } return false; } } 

Пример использования:

 $serializationContext->addExclusionStrategy( new OnlyLoadedAssociationsExclusionStrategy() );