6

symfonyのシリアライザコンポーネント(JMSSerializerではない)に循環参照制限を設定する方法はありますか?Symfonyシリアライザ - 循環参照をグローバルに設定

私はFOSRestBundleを持つRESTアプリケーションと、シリアル化する必要がある他のエンティティを含むいくつかのエンティティを持っています。しかし、循環参照エラーが発生しています。

私はこのようにそれを設定する方法を知っている:

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

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

しかし、これは、複数のコントローラ(私のためにオーバーヘッド)で行う必要があります。 config(.yml)にグローバルに設定したいとします。

framework: 
    serializer: 
     enabled: true 
     circular_limit: 5 

これはシリアライザのAPIリファレンスが見つかりませんでしたので、それは可能かどうかです。

答えて

5

私が見つけた唯一の方法は、独自のオブジェクトノーマライザを作成して循環参照ハンドラを追加することです。

最小作業一つであることができる:

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

そして(-1000)とデフォルトのよりslithly優先度の高いサービスとして宣言:この正規化は、あろう

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

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

プロジェクトのどこでもデフォルトで使用されます。

5

私はSymfonyのソースを読んで、それを動作させるためにいくつかのトリックを試してきました(私のプロジェクトで、サードパーティのバンドルをインストールせずに、その機能ではなく)。私はCompilerPasshttps://symfony.com/doc/current/service_container/compiler_passes.html)... 3つの段階で動作します使用:

1.は、app/AppKernel.phpにロードするために私の最初のバンドルですので、私はAppBundleを選びましバンドル

buildメソッドを定義します。

の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.カスタムを書くCompilerPass

のSymfonyシリアライザはserializerサービスで全てです。だから私はちょうどそれを取得し、そのinstanciationをキャッチするために、それにconfiguratorオプションを追加しました。

の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.コンフィギュラを書く...ここ

、あなたはカスタムCompilerPass(Iに書いたものを、次のクラスを作成しますchoosed AppConfigurer)...カスタムコンパイラパスで選択した名前の後に名前が付けられたインスタンスメソッドを持つクラス(私はconfigureNormalizerを選択しました)。

このメソッドは、symfony内部シリアライザが作成されるときに呼び出されます。

symfonyのシリアライザは、プライベート/保護されたプロパティとして正規化し、デコーダと、このような事を含まれています。だから、PHPの\Closure::bindメソッドを使用してsymfonyシリアライザの範囲を$thisとしてラムダのような関数(PHP Closure)にしました。

ノルマライザ($this->normalizers)をループすると、ビヘイビアをカスタマイズできます。実際には、これらのノミナライザのすべてが循環参照ハンドラ(DateTimeNormalizerなど)を必要としているわけではありません。

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

結論

としては、先に述べた、私はFOSRestBundleも第三者バンドルなどを望んでいたdind'tので、私は私のプロジェクトのためにそれをやりました私は解決策としてインターネットを見てきました。その部分ではなく(セキュリティのためかもしれません)。私のコントローラーは今のように...

<?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

優れたソリューション!コンパイラが作成された場所を渡すのは、これが理由です。 –

+0

あまりにも多くの可能性、あまりにも短い時間、あまりにも簡潔で良いとあまりにも広すぎるドキュメント。そして、OPが述べたように、この完璧な解決策を得るまで、彼は一週間を過ごしました。それは私たちの多くが持っていない贅沢です。 :(したがって、私は個人的にこの解決策に行くだろう:https://stackoverflow.com/a/44286659/261332。 – userfuser

+0

@urserfuserあなたのコメントと共に、その解決策を読んだ。私が見つけた解決策を投稿した理由(2) "完璧な解決策"の時点でそうは言いませんが、シリアライザ(JSON、XML、YAMLなど)をインスタンス化したかったわけではありません。私のAPIの各ルート(3)OOPの設計では、私はそのClosure :: bindを誇りに思っていません。それはphpのコアであり、PHPのデザインリークとよく似ています。 Symfonyは自動的にJSON、YAML、またはXMLフォーマットを同様に処理するので、私はあなたの選択によってこのソリューションを選択します。 –