feature #26108 [Serializer] Add a MaxDepth handler (dunglas)
This PR was squashed before being merged into the 4.1-dev branch (closes #26108).
Discussion
----------
[Serializer] Add a MaxDepth handler
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks? | no
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md files -->
| Tests pass? | yes
| Fixed tickets | api-platform/core#1130, api-platform/core#1528, api-platform/core#1528 <!-- #-prefixed issue number(s), if any -->
| License | MIT
| Doc PR | todo <!--highly recommended for new features-->
Sometimes, instead of just stopping the serialization process when the configured max depth is reached, it can be interesting to let the user return something (like returning the identifier of the entity to stop manually the serialization process).
This PR also makes the max depth handling more similar to circular references handling (that already has the possibility to set a handler).
Commits
-------
ed975c764b
[Serializer] Add a MaxDepth handler
This commit is contained in:
commit
3cb5619216
@ -731,6 +731,7 @@ class Configuration implements ConfigurationInterface
|
||||
->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end()
|
||||
->scalarNode('name_converter')->end()
|
||||
->scalarNode('circular_reference_handler')->end()
|
||||
->scalarNode('max_depth_handler')->end()
|
||||
->arrayNode('mapping')
|
||||
->addDefaultsIfNotSet()
|
||||
->fixXmlConfig('path')
|
||||
|
@ -1275,6 +1275,10 @@ class FrameworkExtension extends Extension
|
||||
if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) {
|
||||
$container->getDefinition('serializer.normalizer.object')->addMethodCall('setCircularReferenceHandler', array(new Reference($config['circular_reference_handler'])));
|
||||
}
|
||||
|
||||
if ($config['max_depth_handler'] ?? false) {
|
||||
$container->getDefinition('serializer.normalizer.object')->addMethodCall('setMaxDepthHandler', array(new Reference($config['max_depth_handler'])));
|
||||
}
|
||||
}
|
||||
|
||||
private function registerPropertyInfoConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
|
||||
|
@ -239,6 +239,7 @@
|
||||
<xsd:attribute name="cache" type="xsd:string" />
|
||||
<xsd:attribute name="enable-annotations" type="xsd:boolean" />
|
||||
<xsd:attribute name="name-converter" type="xsd:string" />
|
||||
<xsd:attribute name="max-depth-handler" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="property_info">
|
||||
|
@ -68,6 +68,7 @@ $container->loadFromExtension('framework', array(
|
||||
'enabled' => true,
|
||||
'enable_annotations' => true,
|
||||
'name_converter' => 'serializer.name_converter.camel_case_to_snake_case',
|
||||
'max_depth_handler' => 'my.max.depth.handler',
|
||||
),
|
||||
'property_info' => true,
|
||||
'ide' => 'file%%link%%format',
|
||||
|
@ -41,7 +41,7 @@
|
||||
</framework:translator>
|
||||
<framework:validation enabled="true" />
|
||||
<framework:annotations cache="file" debug="true" file-cache-dir="%kernel.cache_dir%/annotations" />
|
||||
<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" />
|
||||
<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" max-depth-handler="my.max.depth.handler" />
|
||||
<framework:property-info />
|
||||
</framework:config>
|
||||
</container>
|
||||
|
@ -54,6 +54,7 @@ framework:
|
||||
enabled: true
|
||||
enable_annotations: true
|
||||
name_converter: serializer.name_converter.camel_case_to_snake_case
|
||||
max_depth_handler: my.max.depth.handler
|
||||
property_info: ~
|
||||
ide: file%%link%%format
|
||||
request:
|
||||
|
@ -807,6 +807,7 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||
$this->assertNull($container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1));
|
||||
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.normalizer.object')->getArgument(1));
|
||||
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
|
||||
$this->assertEquals(array('setMaxDepthHandler', array(new Reference('my.max.depth.handler'))), $container->getDefinition('serializer.normalizer.object')->getMethodCalls()[0]);
|
||||
}
|
||||
|
||||
public function testRegisterSerializerExtractor()
|
||||
|
@ -9,6 +9,8 @@ CHANGELOG
|
||||
* added an optional `default_constructor_arguments` option of context to specify a default data in
|
||||
case the object is not initializable by its constructor because of data missing
|
||||
* added optional `bool $escapeFormulas = false` argument to `CsvEncoder::__construct`
|
||||
* added `AbstractObjectNormalizer::setMaxDepthHandler` to set a handler to call when the configured
|
||||
maximum depth is reached
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
@ -41,6 +41,11 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||
private $attributesCache = array();
|
||||
private $cache = array();
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $maxDepthHandler;
|
||||
|
||||
/**
|
||||
* @var ClassDiscriminatorResolverInterface|null
|
||||
*/
|
||||
@ -86,11 +91,15 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||
$attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($class)->getAttributesMetadata() : null;
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if (null !== $attributesMetadata && $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) {
|
||||
$maxDepthReached = false;
|
||||
if (null !== $attributesMetadata && ($maxDepthReached = $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) && !$this->maxDepthHandler) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $context);
|
||||
if ($maxDepthReached) {
|
||||
$attributeValue = \call_user_func($this->maxDepthHandler, $attributeValue);
|
||||
}
|
||||
|
||||
if (isset($this->callbacks[$attribute])) {
|
||||
$attributeValue = call_user_func($this->callbacks[$attribute], $attributeValue);
|
||||
@ -204,6 +213,14 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
||||
*/
|
||||
abstract protected function getAttributeValue($object, $attribute, $format = null, array $context = array());
|
||||
|
||||
/**
|
||||
* Sets an handler function that will be called when the max depth is reached.
|
||||
*/
|
||||
public function setMaxDepthHandler(?callable $handler): void
|
||||
{
|
||||
$this->maxDepthHandler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -613,6 +613,27 @@ class ObjectNormalizerTest extends TestCase
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, $result);
|
||||
|
||||
$expected = array(
|
||||
'bar' => null,
|
||||
'foo' => 'level1',
|
||||
'child' => array(
|
||||
'bar' => null,
|
||||
'foo' => 'level2',
|
||||
'child' => array(
|
||||
'bar' => null,
|
||||
'child' => null,
|
||||
'foo' => 'handler',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->normalizer->setMaxDepthHandler(function ($obj) {
|
||||
return 'handler';
|
||||
});
|
||||
|
||||
$result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true));
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user