2012-08-07 12 views
15

Buduję API REST za pomocą Symfony2, Doctrine, FOSRestBundle i JMSSerializer.Unikanie rekursji z obiektami Doctrine i JMSserializer

Problem, który mam do czynienia podczas serializowania moich podmiotów, serializator ściąga wszystkie powiązane podmioty. Np. Dla zadania, które jest częścią opowieści, która jest częścią planszy, więc podczas serializowania zadania otrzymuję wynik zawierający historię zawierającą planszę, która następnie obejmuje wszystkie inne historie na tablicy.

Czy istnieje prosty sposób na ograniczenie tego, a zamiast tego wystarczy podać zagraniczne numery identyfikacyjne?

+0

myślę to samo pytanie zostało ben pisał tutaj: http://stackoverflow.com/questions/6706485/how-to-encode-doctrine-entities-to -json-in-symfony-2-0-ajax-application. Znajdziesz kilka odpowiedzi za pomocą JMSSerializer lub nie. –

Odpowiedz

8

Sprawdź plik Serializer/Handler/DoctrineProxyHandler.php na JMSSerializerBundle. Teraz, jeśli skomentujesz tę linię:

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

Przestanie leniwie ładować twoje jednostki. Jeśli tego właśnie szukasz, po prostu śmiało i create your own handler, gdzie nie leniwego obciążenia.

Jeśli nie jest to prawidłowe, zalecam spersonalizowanie swoich jednostek przed wysłaniem ich do JMSSerializerBundle według własnego gustu. Na przykład w powiązanych podmiotach chcę mieć identyfikator, podczas gdy w innych potrzebuję niestandardowej nazwy kolumny, np. Kodu, nazwy lub czegokolwiek.

Po prostu utworzę kopię mojego obiektu podmiotu, a następnie zacznę uzyskiwać potrzebne pola dla relacji. Następnie serializuję tę kopię. JMSSerializerBundle nie będzie leniwy, ponieważ już podałem odpowiednie pola.

+0

Wygląda na to, że to może dla mnie zadziałać, nie miałem okazji pracować nad tym w tym tygodniu, ale przyjrzę się w ten weekend. – wgcrouch

+0

Już to wymyśliłem kilka miesięcy temu. Dziękuję, że to zrobiłem. http://stackoverflow.com/questions/11575345/disable-doctrine-2-lazy-loading-when-using-jms-serializer –

17

Użyj zasady wykluczeń JMS.

Przykład wykorzystania adnotacji od kategorii podmiotu, których nie chcesz, aby obejmować dzieci i podmiotów związanych z produktów, które należy uwzględnić:

use ... 
    JMS\SerializerBundle\Annotation\ExclusionPolicy, 
    JMS\SerializerBundle\Annotation\Exclude, 
    ...; 

/** 
* ... 
* @ExclusionPolicy("none") 
*/ 
class Category 
{ 
    /** 
    * ... 
    * @Exclude 
    */ 
    private $children; 

    /** 
    * ... 
    * @Exclude 
    */ 
    private $products; 

} 

Spójrz na JMSSerializer docs aby uzyskać więcej informacji.

EDIT:

Na przykład można stosować częściowe słowa kluczowego, aby wybrać tylko te dane, które potrzebujesz. Chociaż nie mogłem, dla mojego życia, wyłączyć ładowanie pełnych powiązanych obiektów (dwa poziomy w dół), jeśli przekażę obiekt obiektu do serializera (nawet po wyłączeniu obciążenia w DoctrineProxyHandler), , ale jeśli użyję tablicy, niż nie używa doktryny leniwego ładowania przez proxy (zgodnie z oczekiwaniami ofc).

Przykład wykorzystania Twojego przykład podmioty:

$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(); 

ten sposób można dostać coś takiego:

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

} 
] 

PS: Intryguje mnie ten "problem". W każdym razie zobaczę, jak wymyślić rozwiązanie, jak serializować obiekt encji bez użycia wyniku tablicy.

+3

Przyjazna aktualizacja: od [wer. 0.11] (https://github.com/schmittjoh/JMSSerializerBundle/blob/master/UPGRADING.md#upgrading-from-010-to-011) rdzeń JMS Seralizer został wyodrębniony z pakietu, więc zamiast "JMS \ SerializerBundle \ Adnotacja \ ExclusionPolicy" będzie to "JMS \ Serializer \ Adnotacja \ ExclusionPolicy" i "JMS \ SerializerBundle \ Adnotacja \ Exclude" będzie "JMS \ Serializer \ Adnotacja \ Exclude". –

9

właśnie aktualizacja w najnowszej wersji JMSSerializer, miejsce należy patrzeć na to

JMS \ serializer \ EventDispatcher \ Abonent \ DoctrineProxySubscriber

zamiast

serializer \ Handler \ DoctrineProxyHandler

Aby przesłonić domyślne zachowanie leniwego obciążenia, należy zdefiniować własnego subskrybenta zdarzenia.

W swojej aplikacji/config.yuml dodać to:

parameters: 
    ... 
    jms_serializer.doctrine_proxy_subscriber.class: Your\Bundle\Event\DoctrineProxySubscriber 

można skopiować klasy z JMS \ serializer \ EventDispatcher \ Subscriber \ DoctrineProxySubscriber do swojej \ Bundle \ Event \ DoctrineProxySubscriber i zakomentuj $ object -> __ load(); Linia

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

Aktualizacja: skończyło się pisać własne uproszczoną wersję narzędzia serializacji: https://github.com/dlin-me/array-converter-bundle

+1

Podoba mi się to rozwiązanie, ale to nie działa dla mnie. Stworzyłem własny DoctrineProxySubscriber i sprawdziłem, czy jest on używany podczas serializacji, ale nigdy nie dociera do skomentowanej linii i zawsze leniwie ładuje powiązane podmioty. Nie chcę serializera, aby tworzyć zapytania do DB, ale z drugiej strony chcę serializować dane, które już zostały pobrane. Byłbym wdzięczny za wszelkie pomysły. – Radzikowski

+0

Skomentowałem wiersz $ object -> __ load i rzeczy poszły ** prawie ** jakbym tego potrzebował ... teraz zwraca 'id_thing: {id_thing: 1}' unteetuje obiekt .. czy jest jakikolwiek sposób na zwrócenie 'id_thing: tylko 1'? – KnF

+0

Ah, kolejna rzecz ... Nie używam Symfony2 .. tylko JMSSerializer i Doctrine .. Jak utworzyć niestandardowego subskrybenta? – KnF

0

Oto funkcję do wyboru identyfikatory jeden-do-jednego lub jeden-do-wielu stowarzyszonych w A ogólny sposób bez używania złączeń.

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

Oto klasa, która zapobiega leniwe ładowanie jednego lub wielu związków, które mogą być używane jako JMS Serializatora ExclusionStrategy. Przykładem

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

Zastosowanie:

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

Również opublikowano tutaj: http://stackoverflow.com/questions/11575345/disable-doctrine-2-lazy-loading-when-using-jms-serializer –

+0

Powinieneś unikać kopiowania i wklejania odpowiedzi na wiele pytań. Gdy to zrobisz, [flaga zostanie automatycznie podniesiona do uwagi moderatora] (http://meta.stackoverflow.com/questions/270311/can-we-auto-flag-answers-that-are-clearly-copy-pasted/ 270341 # 270341). Zamiast tego wystarczy jedno z pytań jako duplikat drugiego. – CubeJockey

Powiązane problemy