6

C'è un modo per impostare il limite di riferimento circolare nel componente serializzatore di Symfony (non JMSSerializer) con alcuna configurazione o qualcosa del genere?serializzatore Symfony - imposta il riferimento circolare globale

Possiedo un'applicazione REST con FOSRestBundle e alcune entità che contengono altre entità che devono essere serializzate. Ma sto incontrando errori di riferimento circolari.

so come impostare in questo modo:

$encoder = new JsonEncoder(); 
$normalizer = new ObjectNormalizer(); 

$normalizer->setCircularReferenceHandler(function ($object) { 
    return $object->getName(); 
}); 

Ma questo deve essere fatto in più di un controllore (in testa per me). Voglio impostarlo globalmente nel config (.yml) ad es. In questo modo:

framework: 
    serializer: 
     enabled: true 
     circular_limit: 5 

Trovato nessun riferimento API serializzatore per questo quindi mi chiedo è possibile o no?

risposta

5

L'unico modo che ho trovato è creare il proprio normalizzatore di oggetto per aggiungere il gestore di riferimento circolare.

Un minimo funzionante si può essere:

<?php 

namespace AppBundle\Serializer\Normalizer; 

use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; 
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; 
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; 
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; 
use Symfony\Component\Serializer\NameConverter\NameConverterInterface; 

class AppObjectNormalizer extends ObjectNormalizer 
{ 
    public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null) 
    { 
     parent::__construct($classMetadataFactory, $nameConverter, $propertyAccessor, $propertyTypeExtractor); 

     $this->setCircularReferenceHandler(function ($object) { 
      return $object->getName(); 
     }); 
    } 
} 

quindi dichiarare come un servizio con una priorità slithly superiore a quella predefinita (che è -1000):

<service 
    id="app.serializer.normalizer.object" 
    class="AppBundle\Serializer\Normalizer\AppObjectNormalizer" 
    public="false" 
    parent="serializer.normalizer.object"> 

    <tag name="serializer.normalizer" priority="-500" /> 
</service> 

Questo normalizzatore sarà usato di default ovunque nel tuo progetto.

5

Per una settimana ho letto la fonte di Symfony e ho provato alcuni trucchi per farlo funzionare (sul mio progetto e senza installare un pacchetto di terze parti: non per quella funzionalità) e alla fine ne ho ricevuto uno. Ho usato CompilerPass (https://symfony.com/doc/current/service_container/compiler_passes.html) ... che opera in tre fasi:

1. Definire build metodo in bundle

ho scelto AppBundle perché è mia primo fascio di caricare in app/AppKernel.php.

src/AppBundle/AppBundle.php

<?php 

namespace AppBundle; 

use Symfony\Component\DependencyInjection\ContainerBuilder; 
use Symfony\Component\HttpKernel\Bundle\Bundle; 

class AppBundle extends Bundle 
{ 
    public function build(ContainerBuilder $container) 
    { 
     parent::build($container); 
     $container->addCompilerPass(new AppCompilerPass()); 
    } 
} 

2. Scrivi la vostra abitudine CompilerPass

Symfony serializzatori sono tutti sotto il servizio serializer. Quindi l'ho appena recuperato e ho aggiunto ad esso un'opzione configurator, al fine di coglierne l'istanziazione.

src/AppBundle/AppCompilerPass.php

<?php 

namespace AppBundle; 

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 
use Symfony\Component\DependencyInjection\ContainerBuilder; 
use Symfony\Component\DependencyInjection\Reference; 



class AppCompilerPass implements CompilerPassInterface 
{ 
    public function process(ContainerBuilder $container) 
    { 
     $container 
      ->getDefinition('serializer') 
      ->setConfigurator([ 
       new Reference(AppConfigurer::class), 'configureNormalizer' 
      ]); 
    } 
} 

3. Scrivi la configuratore ...

Qui, si crea una classe a seguito quello che hai scritto nel costume CompilerPass (I scelto AppConfigurer) ...Una classe con un metodo di istanza chiamato dopo quello che hai scelto nel passaggio del compilatore personalizzato (ho scelto configureNormalizer).

Questo metodo verrà chiamato quando verrà creato il serializzatore interno di symfony.

Il serializzatore symfony contiene normalizzatori e decodificatori e cose del genere come privato/proprietà protette. Questo è il motivo per cui ho usato il metodo \Closure::bind di PHP per impostare il serializzatore di symfony come $this nella mia funzione lambda (PHP Closure).

Quindi un ciclo attraverso i nomalizzatori ($this->normalizers) aiuta a personalizzare i loro comportamenti. In realtà, non tutti quei nomalizzatori necessitano di gestori di riferimento circolari (come DateTimeNormalizer): il motivo della condizione lì.

src/AppBundle/AppConfigurer.php

<?php 

namespace AppBundle; 



class AppConfigurer 
{ 
    public function configureNormalizer($normalizer) 
    { 
     \Closure::bind(function() use (&$normalizer) 
     { 
      foreach ($this->normalizers as $normalizer) 
       if (method_exists($normalizer, 'setCircularReferenceHandler')) 
        $normalizer->setCircularReferenceHandler(function ($object) 
        { 
         return $object->getId(); 
        }); 
     }, $normalizer, $normalizer)(); 
    } 
} 

Conclusione

Come detto in precedenza, l'ho fatto per il mio progetto da quando ho voluto dind FOSRestBundle né alcun bundle di terze parti come Ho visto su Internet una soluzione: non per quella parte (potrebbe essere per la sicurezza). I miei controller ora stanno ...

<?php 

namespace StoreBundle\Controller; 

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 
use Symfony\Bundle\FrameworkBundle\Controller\Controller; 



class ProductController extends Controller 
{ 
    /** 
    * 
    * @Route("/products") 
    * 
    */ 
    public function indexAction() 
    { 
     $em = $this->getDoctrine()->getManager(); 
     $data = $em->getRepository('StoreBundle:Product')->findAll(); 
     return $this->json(['data' => $data]); 
    } 

    /** 
    * 
    * @Route("/product") 
    * @Method("POST") 
    * 
    */ 
    public function newAction() 
    { 
     throw new \Exception('Method not yet implemented'); 
    } 

    /** 
    * 
    * @Route("/product/{id}") 
    * 
    */ 
    public function showAction($id) 
    { 
     $em = $this->getDoctrine()->getManager(); 
     $data = $em->getRepository('StoreBundle:Product')->findById($id); 
     return $this->json(['data' => $data]); 
    } 

    /** 
    * 
    * @Route("/product/{id}/update") 
    * @Method("PUT") 
    * 
    */ 
    public function updateAction($id) 
    { 
     throw new \Exception('Method not yet implemented'); 
    } 

    /** 
    * 
    * @Route("/product/{id}/delete") 
    * @Method("DELETE") 
    * 
    */ 
    public function deleteAction($id) 
    { 
     throw new \Exception('Method not yet implemented'); 
    } 

} 
+0

Soluzione eccellente! Questo è il vero motivo per cui il compilatore è passato dove creato. –

+0

Troppe possibilità, troppo poco tempo, troppo poco conciso e buono e troppo doc troppo generico. E come risultato di tutto ciò - come OP ha menzionato - ha trascorso una settimana fino a quando non è arrivato a questa soluzione perfetta. Questo è un lusso che molti di noi non hanno. :(Pertanto, ho personalmente andare per questa soluzione: https://stackoverflow.com/a/44286659/261332. – userfuser

+0

@urserfuser Ho letto quella soluzione, insieme con il tuo commento. (1) Non tutti hanno il lusso di tempo, ecco perché ho postato la soluzione che ho trovato (2) A partire dalla "soluzione perfetta", lo dirò non così ma non volevo istanziare un serializzatore (JSON, XML, YAML, ecc ...) per ogni percorso della mia API. (3) A partire dal design OOP, non sono orgoglioso di questo \ Closure :: bind. Attraverso esso è fondamentale per PHP, vedo molto come una perdita di progettazione PHP. (4) E infine , Sceglierò comunque questa soluzione a tua scelta, perché Symfony gestirà automaticamente sia il formato JSON, YAML o XML. –

Problemi correlati