【问题标题】:Symfony serializer - set circular reference globalSymfony 序列化器 - 设置循环引用全局
【发布时间】:2016-02-12 14:33:16
【问题描述】:

有没有办法在 Symfony 的序列化程序组件(不是 JMSSerializer)中设置循环引用限制,并使用任何配置或类似的东西?

我有一个带有 FOSRestBundle 的 REST 应用程序和一些包含其他实体的实体,这些实体也应该被序列化。但我遇到了循环引用错误。

我知道如何设置它:

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

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

但这必须在多个控制器中完成(对我来说是开销)。 我想在配置(.yml)中全局设置它,例如像这样:

framework: 
    serializer:
        enabled: true
        circular_limit: 5

没有找到这方面的序列化 API 参考,所以我想知道这是否可能?

【问题讨论】:

    标签: rest symfony serialization fosrestbundle circular-reference


    【解决方案1】:

    一周以来,我一直在阅读 Symfony 源代码并尝试一些技巧来使其工作(在我的项目中并且没有安装第三方包:不是为了那个功能),我终于得到了一个。我使用了 CompilerPass (https://symfony.com/doc/current/service_container/compiler_passes.html)...分三个步骤工作:

    1。在bundle中定义build方法

    我选择了AppBundle,因为它是我的第一个要在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。写下您的自定义CompilerPass

    Symfony 序列化程序都在serializer 服务下。所以我只是获取了它并添加了一个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 中编写的内容创建一个类(我选择了 AppConfigurer)... .

    这个方法会在 symfony 内部序列化器被创建时被调用。

    symfony 序列化器包含规范化器和解码器以及诸如私有/受保护属性之类的东西。这就是为什么我使用 PHP 的 \Closure::bind 方法将 symfony 序列化器作为 $this 作用于我的 lambda-like 函数(PHP Closure)。

    然后通过 nomalizers ($this-&gt;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 或任何第三方捆绑包,正如我在 Internet 上看到的那样作为解决方案:不是为了那个部分(可能是为了安全)。我的控制器现在是...

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

    【讨论】:

    • 优秀的解决方案!这就是编译器通过创建位置的原因。
    • 太多的可能性,太少的时间,太少的简洁和好的文档和太多太宽泛的文档。由于所有这些 - 正如 OP 所提到的 - 他花了一周的时间才找到这个完美的解决方案。这是我们许多人所没有的奢侈品。 :(因此,我个人会选择这个解决方案:stackoverflow.com/a/44286659/261332
    • @urserfuser 我阅读了该解决方案以及您的评论。 (1)不是每个人都有时间,这就是为什么我发布我找到的解决方案(2)至于“完美解决方案”,我会说不是这样,但我不想实例化一个序列化程序(JSON, XML、YAML 等)用于我的 API 的每个路由。 (3) 对于 OOP 设计,我并不为 \Closure::bind 感到自豪。通过它是 php 的核心,我认为它很像 PHP 设计泄漏。 (4) 最后,我仍然会选择这个解决方案而不是你的选择,因为 Symfony 将自动处理 JSON、YAML 或 XML 格式。
    【解决方案2】:

    我发现的唯一方法是创建自己的对象规范化器来添加循环引用处理程序。

    一个最小的工作可以是:

    <?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)略高的服务:

    <service
        id="app.serializer.normalizer.object"
        class="AppBundle\Serializer\Normalizer\AppObjectNormalizer"
        public="false"
        parent="serializer.normalizer.object">
    
        <tag name="serializer.normalizer" priority="-500" />
    </service>
    

    默认情况下,此规范器将在您项目的任何地方使用。

    【讨论】:

    • 自 Symfony 4.2 起,\Symfony\Component\Serializer\Normalizer\AbstractNormalizer::setCircularReferenceHandler 方法已被弃用。您应该使用上下文的circular_reference_handler 键:$this-&gt;defaultContext['circular_reference_handler'] = function ($object) { return $object-&gt;getName(); };
    • @Konpaka,在配置文件中应该是什么。
    猜你喜欢
    • 2021-11-22
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-26
    • 1970-01-01
    相关资源
    最近更新 更多