有没有办法在Symfony(而不是JMSSerializer)的序列化程序组件中使用任何配置或类似的东西设置循环引用限制?
我有一个带有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参考,所以我想知道它是否可能?
答案 0 :(得分:9)
有一个星期我一直在阅读Symfony源代码并尝试一些技巧来实现它(在我的项目上并且没有安装第三方软件包:不是为了那个功能)而且我终于得到了一个。我使用 CompilerPass (https://symfony.com/doc/current/service_container/compiler_passes.html)...分三个步骤:
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());
}
}
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'
]);
}
}
在这里,您根据自己在自定义CompilerPass中编写的内容创建了一个类(我选择AppConfigurer
)...一个类,其实例方法以您在自定义编译器传递中选择的名称命名(我选择了{{ 1}})。
将在创建symfony内部序列化程序时调用此方法。
symfony序列化程序包含规范化器和解码器以及作为私有/受保护属性等。这就是为什么我使用PHP的configureNormalizer
方法将symfony序列化程序作为\Closure::bind
范围扩展到类似lambda的函数(PHP Closure)。
然后循环通过正常化器($this
)来帮助定制他们的行为。实际上,并非所有这些正规化器都需要循环引用处理程序(如$this->normalizers
):那里条件的原因。
<强> 的src /的appbundle / AppConfigurer.php 强>
DateTimeNormalizer
如前所述,我为我的项目做了这件事,因为我不想要FOSRestBundle或任何第三方捆绑,因为我已经在互联网上看到了解决方案:不是那部分(可能是为了安全)。我的控制器现在是......
<?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)();
}
}
答案 1 :(得分: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();
});
}
}
然后声明一个服务,其优先级比默认值(-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>
默认情况下,此规范化程序将在项目的任何位置使用。