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

时间:2021-02-27 06:46:50

Is there any way to set the circular reference limit in the serializer component of Symfony (not JMSSerializer) with any config or something like that?

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

I have a REST Application with FOSRestBundle and some Entities that contain other entities which should be serialized too. But I'm running into circular reference errors.

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

I know how to set it like this:

我知道如何设置它:

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

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

But this has to be done in more than one controller (overhead for me). I want to set it globally in the config (.yml) e.g. like this:

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

framework: 
    serializer:
        enabled: true
        circular_limit: 5

Found no serializer API reference for this so I wonder is it possible or not?

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

2 个解决方案

#1


5  

The only way I've found is to create your own object normalizer to add the circular reference handler.

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

A minimal working one can be:

最小的工作可以是:

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

Then declare as a service with a slithly higher priority than the default one (which is -1000):

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

This normalizer will be used by default everywhere in your project.

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

#2


7  

For a week have I been reading Symfony source and trying some tricks to get it work (on my project and without installing a third party bundle: not for that functionality) and I finally got one. I used CompilerPass (https://symfony.com/doc/current/service_container/compiler_passes.html)... Which works in three steps:

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

1. Define build method in bundle

I choosed AppBundle because it is my first bundle to load in app/AppKernel.php.

我选择了AppBundle,因为它是我在app / AppKernel.php中加载的第一个包。

src/AppBundle/AppBundle.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. Write your custom CompilerPass

Symfony serializers are all under the serializer service. So I just fetched it and added to it a configurator option, in order to catch its instanciation.

Symfony序列化器都在串行器服务之下。所以我只是取了它并添加了一个配置器选项,以便捕获它的instanciation。

src/AppBundle/AppCompilerPass.php

的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. Write your configurer...

Here, you create a class following what you wrote in the custom CompilerPass (I choosed AppConfigurer)... A class with an instance method named after what you choosed in the custom compiler pass (I choosed configureNormalizer).

在这里,您根据自己在自定义CompilerPass(我选择的AppConfigurer)中编写的内容创建了一个类...一个具有以您在自定义编译器传递中选择的实例方法命名的类(我选择了configureNormalizer)。

This method will be called when the symfony internal serializer will be created.

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

The symfony serializer contains normalizers and decoders and such things as private/protected properties. That is why I used PHP's \Closure::bind method to scope the symfony serializer as $this into my lambda-like function (PHP Closure).

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

Then a loop through the nomalizers ($this->normalizers) help customize their behaviours. Actually, not all of those nomalizers need circular reference handlers (like DateTimeNormalizer): the reason of the condition there.

然后循环通过最佳化器($ this-> normalizers)帮助定制他们的行为。实际上,并非所有这些正规化器都需要循环引用处理程序(如DateTimeNormalizer):那里条件的原因。

src/AppBundle/AppConfigurer.php

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

Conclusion

As said earlier, I did it for my project since I dind't wanted FOSRestBundle nor any third party bundle as I've seen over Internet as a solution: not for that part (may be for security). My controllers now stand as...

如前所述,我为我的项目做了这件事,因为我不想要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');
    }

}

#1


5  

The only way I've found is to create your own object normalizer to add the circular reference handler.

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

A minimal working one can be:

最小的工作可以是:

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

Then declare as a service with a slithly higher priority than the default one (which is -1000):

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

This normalizer will be used by default everywhere in your project.

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

#2


7  

For a week have I been reading Symfony source and trying some tricks to get it work (on my project and without installing a third party bundle: not for that functionality) and I finally got one. I used CompilerPass (https://symfony.com/doc/current/service_container/compiler_passes.html)... Which works in three steps:

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

1. Define build method in bundle

I choosed AppBundle because it is my first bundle to load in app/AppKernel.php.

我选择了AppBundle,因为它是我在app / AppKernel.php中加载的第一个包。

src/AppBundle/AppBundle.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. Write your custom CompilerPass

Symfony serializers are all under the serializer service. So I just fetched it and added to it a configurator option, in order to catch its instanciation.

Symfony序列化器都在串行器服务之下。所以我只是取了它并添加了一个配置器选项,以便捕获它的instanciation。

src/AppBundle/AppCompilerPass.php

的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. Write your configurer...

Here, you create a class following what you wrote in the custom CompilerPass (I choosed AppConfigurer)... A class with an instance method named after what you choosed in the custom compiler pass (I choosed configureNormalizer).

在这里,您根据自己在自定义CompilerPass(我选择的AppConfigurer)中编写的内容创建了一个类...一个具有以您在自定义编译器传递中选择的实例方法命名的类(我选择了configureNormalizer)。

This method will be called when the symfony internal serializer will be created.

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

The symfony serializer contains normalizers and decoders and such things as private/protected properties. That is why I used PHP's \Closure::bind method to scope the symfony serializer as $this into my lambda-like function (PHP Closure).

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

Then a loop through the nomalizers ($this->normalizers) help customize their behaviours. Actually, not all of those nomalizers need circular reference handlers (like DateTimeNormalizer): the reason of the condition there.

然后循环通过最佳化器($ this-> normalizers)帮助定制他们的行为。实际上,并非所有这些正规化器都需要循环引用处理程序(如DateTimeNormalizer):那里条件的原因。

src/AppBundle/AppConfigurer.php

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

Conclusion

As said earlier, I did it for my project since I dind't wanted FOSRestBundle nor any third party bundle as I've seen over Internet as a solution: not for that part (may be for security). My controllers now stand as...

如前所述,我为我的项目做了这件事,因为我不想要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');
    }

}