Symfony序列化程序 - 设置循环引用全局

kin*_*ske 10 rest serialization circular-reference symfony fosrestbundle

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

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

我知道如何设置它:

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

$normalizer->setCircularReferenceHandler(function ($object) {
     return $object->getName();
});
Run Code Online (Sandbox Code Playgroud)

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

framework: 
    serializer:
        enabled: true
        circular_limit: 5
Run Code Online (Sandbox Code Playgroud)

找不到序列化程序API参考,所以我想知道它是否可能?

Sal*_*èse 10

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

1. 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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'
            ]);
    }
}
Run Code Online (Sandbox Code Playgroud)

3.编写你的配置器......

在这里,你按照你在自定义CompilerPass(我选择AppConfigurer)中编写的内容创建一个类...一个带有一个实例方法的类,以你在自定义编译器传递(我选择configureNormalizer)中选择的方式命名.

将在创建symfony内部序列化程序时调用此方法.

symfony序列化程序包含规范化器和解码器以及私有/受保护属性等.这就是为什么我使用PHP的\Closure::bind方法将symfony序列化程序作为$this我的lambda类函数(PHP Closure)的范围.

然后循环通过nomalizers($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)();
    }
}
Run Code Online (Sandbox Code Playgroud)

结论

如前所述,我为我的项目做了这件事,因为我不想要FOSRestBundle或任何第三方捆绑,因为我已经通过互联网看到了解决方案:不是那部分(可能是为了安全).我的控制器现在是......

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

}
Run Code Online (Sandbox Code Playgroud)


mag*_*tik 6

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

最小的工作可以是:

<?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();
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

然后声明为一个服务,其优先级比默认值(-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>
Run Code Online (Sandbox Code Playgroud)

默认情况下,此规范化程序将在项目的任何位置使用.

  • 自 Symfony 4.2 起,方法 `\Symfony\Component\Serializer\Normalizer\AbstractNormalizer::setCircularReferenceHandler` 已被弃用。您应该使用上下文的 `circular_reference_handler` 键: `$this-&gt;defaultContext['circular_reference_handler'] = function ($object) { return $object-&gt;getName(); };` (2认同)