diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index b98f7914be3aa..b3d67cabbade9 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -796,6 +796,7 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode)
->info('serializer configuration')
->{!class_exists(FullStack::class) && class_exists(Serializer::class) ? 'canBeDisabled' : 'canBeEnabled'}()
->children()
+ ->booleanNode('enable_normalizer_generation')->defaultFalse()->end()
->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end()
->scalarNode('name_converter')->end()
->scalarNode('circular_reference_handler')->end()
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index ab0e271a12dce..8ff5d99652de4 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -1374,6 +1374,12 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) {
$container->getDefinition('serializer.normalizer.object')->addMethodCall('setCircularReferenceHandler', array(new Reference($config['circular_reference_handler'])));
+ $container->getDefinition('serializer.normalizer.object.generated')->addMethodCall('setCircularReferenceHandler', array(new Reference($config['circular_reference_handler'])));
+ }
+
+ if (!$config['enable_normalizer_generation']) {
+ $container->removeDefinition('serializer.normalizer.object.generated');
+ $container->removeDefinition('serializer.normalizer.object.dumper');
}
if ($config['max_depth_handler'] ?? false) {
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
index 2cd70ff083894..818ea92433bff 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
@@ -68,6 +68,19 @@
+
+
+ %kernel.cache_dir%
+ %kernel.debug%
+
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
index 5ef3cbf13585b..2075e6a35b024 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
@@ -210,6 +210,7 @@ protected static function getBundleDefaultConfig()
),
'serializer' => array(
'enabled' => !class_exists(FullStack::class),
+ 'enable_normalizer_generation' => false,
'enable_annotations' => !class_exists(FullStack::class),
'mapping' => array('paths' => array()),
),
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php
index bdcf462baea22..9be7a0a9e929a 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SerializerTest.php
@@ -32,6 +32,32 @@ public function testDeserializeArrayOfObject()
$this->assertEquals($expected, $result);
}
+
+ /**
+ * @dataProvider caseProvider
+ */
+ public function testSerializeArrayOfObject($testCase)
+ {
+ static::bootKernel(array('test_case' => $testCase));
+ $container = static::$kernel->getContainer();
+
+ $bar1 = new Bar();
+ $bar1->id = 1;
+ $bar2 = new Bar();
+ $bar2->id = 2;
+
+ $foo = new Foo();
+ $foo->bars = array($bar1, $bar2);
+
+ $result = $container->get('serializer')->normalize($foo);
+
+ $this->assertEquals(array('bars' => array(array('id' => 1), array('id' => 2))), $result);
+ }
+
+ public function caseProvider()
+ {
+ return array(array('Serializer'), array('GeneratedSerializer'));
+ }
}
class Foo
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/GeneratedSerializer/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/GeneratedSerializer/bundles.php
new file mode 100644
index 0000000000000..144db90236034
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/GeneratedSerializer/bundles.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
+
+return array(
+ new FrameworkBundle(),
+);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/GeneratedSerializer/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/GeneratedSerializer/config.yml
new file mode 100644
index 0000000000000..9272078c574ef
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/GeneratedSerializer/config.yml
@@ -0,0 +1,7 @@
+imports:
+ - { resource: ../config/default.yml }
+
+framework:
+ serializer:
+ enable_normalizer_generation: true
+ enable_annotations: true # required to detect properties
diff --git a/src/Symfony/Component/Serializer/Dumper/NormalizerDumper.php b/src/Symfony/Component/Serializer/Dumper/NormalizerDumper.php
new file mode 100644
index 0000000000000..04e27df8890d1
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Dumper/NormalizerDumper.php
@@ -0,0 +1,211 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Dumper;
+
+use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
+use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
+use Symfony\Component\Serializer\NormalizerInterface;
+
+/**
+ * @author Guilhem Niot
+ * @author Amrouche Hamza
+ *
+ * @experimental
+ */
+final class NormalizerDumper
+{
+ private $classMetadataFactory;
+
+ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory)
+ {
+ $this->classMetadataFactory = $classMetadataFactory;
+ }
+
+ public function dump(string $class, array $context = array())
+ {
+ $reflectionClass = new \ReflectionClass($class);
+ if (!isset($context['class'])) {
+ $context['class'] = $reflectionClass->getShortName().'Normalizer';
+ }
+
+ $namespaceLine = isset($context['namespace']) ? "\nnamespace {$context['namespace']};\n" : '';
+
+ return << 1,
+ );
+
+ use CircularReferenceTrait, NormalizerAwareTrait;
+
+ public function __construct(array \$defaultContext = array())
+ {
+ \$this->defaultContext = array_merge(\$this->defaultContext, \$defaultContext);
+ }
+
+{$this->generateNormalizeMethod($reflectionClass)}
+
+{$this->generateSupportsNormalizationMethod($reflectionClass)}
+}
+EOL;
+ }
+
+ /**
+ * Generates the {@see NormalizerInterface::normalize} method.
+ */
+ private function generateNormalizeMethod(\ReflectionClass $reflectionClass): string
+ {
+ return <<generateNormalizeMethodInner($reflectionClass)}
+ }
+EOL;
+ }
+
+ private function generateNormalizeMethodInner(\ReflectionClass $reflectionClass): string
+ {
+ $code = <<isCircularReference(\$object, \$context)) {
+ return \$this->handleCircularReference(\$object, \$format, \$context);
+ }
+
+ \$groups = isset(\$context[ObjectNormalizer::GROUPS]) && is_array(\$context[ObjectNormalizer::GROUPS]) ? \$context[ObjectNormalizer::GROUPS] : null;
+
+ \$output = array();
+EOL;
+
+ $attributesMetadata = $this->classMetadataFactory->getMetadataFor($reflectionClass->name)->getAttributesMetadata();
+ $maxDepthCode = '';
+ foreach ($attributesMetadata as $attributeMetadata) {
+ if (null === $maxDepth = $attributeMetadata->getMaxDepth()) {
+ continue;
+ }
+
+ $key = sprintf(ObjectNormalizer::DEPTH_KEY_PATTERN, $reflectionClass->name, $attributeMetadata->name);
+ $maxDepthCode .= <<defaultContext[ObjectNormalizer::ENABLE_MAX_DEPTH]) {{$maxDepthCode}
+ }
+
+EOL;
+ }
+
+ foreach ($attributesMetadata as $attributeMetadata) {
+ $code .= <<defaultContext[ObjectNormalizer::ATTRIBUTES] ?? null;
+ if ((null === \$groups
+EOL;
+
+ if ($attributeMetadata->groups) {
+ $code .= sprintf(" || array_intersect(\$groups, array('%s'))", implode("', '", $attributeMetadata->groups));
+ }
+ $code .= ')';
+
+ $code .= " && (null === \$attributes || isset(\$attributes['{$attributeMetadata->name}']) || (is_array(\$attributes) && in_array('{$attributeMetadata->name}', \$attributes, true)))";
+
+ if (null !== $maxDepth = $attributeMetadata->getMaxDepth()) {
+ $key = sprintf(ObjectNormalizer::DEPTH_KEY_PATTERN, $reflectionClass->name, $attributeMetadata->name);
+ $code .= " && (!isset(\$context['{$key}']) || {$maxDepth} >= \$context['{$key}'])";
+ }
+
+ $code .= ') {';
+
+ $value = $this->generateGetAttributeValueExpression($attributeMetadata->name, $reflectionClass);
+ $code .= <<name}'] = \$value;
+ } else {
+ \$subContext = \$context;
+ if (isset(\$attributes['{$attributeMetadata->name}'])) {
+ \$subContext[ObjectNormalizer::ATTRIBUTES] = \$attributes['{$attributeMetadata->name}'];
+ } else {
+ unset(\$subContext[ObjectNormalizer::ATTRIBUTES]);
+ }
+
+ \$output['{$attributeMetadata->name}'] = \$this->normalizer->normalize(\$value, \$format, \$subContext);
+ }
+ }
+EOL;
+ }
+
+ $code .= <<camelize($property);
+
+ foreach ($methods = array("get$camelProp", lcfirst($camelProp), "is$camelProp", "has$camelProp)") as $method) {
+ if ($reflectionClass->hasMethod($method) && $reflectionClass->getMethod($method)) {
+ return sprintf('$object->%s()', $method);
+ }
+ }
+
+ if ($reflectionClass->hasProperty($property) && $reflectionClass->getProperty($property)->isPublic()) {
+ return sprintf('$object->%s', $property);
+ }
+
+ if ($reflectionClass->hasMethod('__get') && $reflectionClass->getMethod('__get')) {
+ return sprintf('$object->__get(\'%s\')', $property);
+ }
+
+ throw new \DomainException(sprintf('Neither the property "%s" nor one of the methods "%s()", "__get()" exist and have public access in class "%s".', $property, implode('()", "', $methods), $reflectionClass->name));
+ }
+
+ private function generateSupportsNormalizationMethod(\ReflectionClass $reflectionClass): string
+ {
+ $instanceof = '\\'.$reflectionClass->name;
+
+ return << array(),
);
- /**
- * @deprecated since Symfony 4.2
- */
- protected $circularReferenceLimit = 1;
-
- /**
- * @deprecated since Symfony 4.2
- *
- * @var callable|null
- */
- protected $circularReferenceHandler;
-
/**
* @var ClassMetadataFactoryInterface|null
*/
@@ -193,68 +181,6 @@ public function hasCacheableSupportsMethod(): bool
return false;
}
- /**
- * Detects if the configured circular reference limit is reached.
- *
- * @param object $object
- * @param array $context
- *
- * @return bool
- *
- * @throws CircularReferenceException
- */
- protected function isCircularReference($object, &$context)
- {
- $objectHash = spl_object_hash($object);
-
- $circularReferenceLimit = $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->circularReferenceLimit;
- if (isset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash])) {
- if ($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] >= $circularReferenceLimit) {
- unset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash]);
-
- return true;
- }
-
- ++$context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash];
- } else {
- $context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] = 1;
- }
-
- return false;
- }
-
- /**
- * Handles a circular reference.
- *
- * If a circular reference handler is set, it will be called. Otherwise, a
- * {@class CircularReferenceException} will be thrown.
- *
- * @final since Symfony 4.2
- *
- * @param object $object
- * @param string|null $format
- * @param array $context
- *
- * @return mixed
- *
- * @throws CircularReferenceException
- */
- protected function handleCircularReference($object/*, string $format = null, array $context = array()*/)
- {
- if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) {
- @trigger_error(sprintf('The "%s()" method will have two new "string $format = null" and "array $context = array()" arguments in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
- }
- $format = \func_num_args() > 1 ? func_get_arg(1) : null;
- $context = \func_num_args() > 2 ? func_get_arg(2) : array();
-
- $circularReferenceHandler = $context[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->circularReferenceHandler;
- if ($circularReferenceHandler) {
- return $circularReferenceHandler($object, $format, $context);
- }
-
- throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', \get_class($object), $this->circularReferenceLimit));
- }
-
/**
* Gets attributes to normalize using groups.
*
diff --git a/src/Symfony/Component/Serializer/Normalizer/CircularReferenceTrait.php b/src/Symfony/Component/Serializer/Normalizer/CircularReferenceTrait.php
new file mode 100644
index 0000000000000..8ed8cff6875b7
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Normalizer/CircularReferenceTrait.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+use Symfony\Component\Serializer\Exception\CircularReferenceException;
+
+/**
+ * Handle circular references.
+ *
+ * @author Kévin Dunglas
+ *
+ * @internal
+ */
+trait CircularReferenceTrait
+{
+ /**
+ * @deprecated since Symfony 4.2
+ */
+ protected $circularReferenceLimit = 1;
+
+ /**
+ * @deprecated since Symfony 4.2
+ *
+ * @var callable|null
+ */
+ protected $circularReferenceHandler;
+
+ /**
+ * Detects if the configured circular reference limit is reached.
+ *
+ * @param object $object
+ * @param array $context
+ *
+ * @return bool
+ *
+ * @throws CircularReferenceException
+ */
+ protected function isCircularReference($object, &$context)
+ {
+ $objectHash = spl_object_hash($object);
+
+ $circularReferenceLimit = $context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT] ?? $this->circularReferenceLimit;
+ if (isset($context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash])) {
+ if ($context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] >= $circularReferenceLimit) {
+ unset($context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash]);
+
+ return true;
+ }
+
+ ++$context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash];
+ } else {
+ $context[AbstractNormalizer::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] = 1;
+ }
+
+ return false;
+ }
+
+ /**
+ * Handles a circular reference.
+ *
+ * If a circular reference handler is set, it will be called. Otherwise, a
+ * {@class CircularReferenceException} will be thrown.
+ *
+ * @final since Symfony 4.2
+ *
+ * @param object $object
+ * @param string|null $format
+ * @param array $context
+ *
+ * @return mixed
+ *
+ * @throws CircularReferenceException
+ */
+ protected function handleCircularReference($object/*, string $format = null, array $context = array()*/)
+ {
+ if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) {
+ @trigger_error(sprintf('The "%s()" method will have two new "string $format = null" and "array $context = array()" arguments in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
+ }
+ $format = \func_num_args() > 1 ? func_get_arg(1) : null;
+ $context = \func_num_args() > 2 ? func_get_arg(2) : array();
+
+ $circularReferenceHandler = $context[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER] ?? $this->defaultContext[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER] ?? $this->circularReferenceHandler;
+ if ($circularReferenceHandler) {
+ return $circularReferenceHandler($object, $format, $context);
+ }
+
+ throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', \get_class($object), $this->circularReferenceLimit));
+ }
+}
diff --git a/src/Symfony/Component/Serializer/Normalizer/GeneratedObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GeneratedObjectNormalizer.php
new file mode 100644
index 0000000000000..986dbed755b9d
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Normalizer/GeneratedObjectNormalizer.php
@@ -0,0 +1,132 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Normalizer;
+
+use Symfony\Component\Config\ConfigCacheFactory;
+use Symfony\Component\Config\ConfigCacheFactoryInterface;
+use Symfony\Component\Config\ConfigCacheInterface;
+use Symfony\Component\Config\Resource\ReflectionClassResource;
+use Symfony\Component\Serializer\Dumper\NormalizerDumper;
+
+/**
+ * Normalize objects using generated normalizers.
+ *
+ * @author Guilhem Niot
+ */
+class GeneratedObjectNormalizer implements NormalizerInterface, NormalizerAwareInterface
+{
+ use NormalizerAwareTrait;
+
+ private $normalizerDumper;
+ private $cacheDir;
+ private $debug;
+ protected $defaultContext;
+
+ /**
+ * @var NormalizerInterface[]
+ */
+ private $normalizers = array();
+
+ /**
+ * @var ConfigCacheFactoryInterface|null
+ */
+ private $configCacheFactory;
+
+ public function __construct(NormalizerDumper $normalizerDumper, string $cacheDir, bool $debug, array $defaultContext = array())
+ {
+ $this->normalizerDumper = $normalizerDumper;
+ $this->cacheDir = $cacheDir;
+ $this->debug = $debug;
+ $this->defaultContext = $defaultContext;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function normalize($object, $format = null, array $context = array())
+ {
+ $class = \get_class($object);
+ if (isset($this->normalizers[$class])) {
+ return $this->normalizers[$class]->normalize($object, $format, $context);
+ }
+
+ $normalizerClass = 'Symfony\Component\Serializer\Normalizer\Generated\\'.$class.'Normalizer';
+ $cache = $this->getConfigCacheFactory()->cache($this->cacheDir.'/normalizers-'.str_replace('\\', '-', $class).'.php',
+ function (ConfigCacheInterface $cache) use ($class, $normalizerClass) {
+ $pos = strrpos($normalizerClass, '\\');
+ $code = $this->normalizerDumper->dump($class, array(
+ 'class' => substr($normalizerClass, $pos + 1),
+ 'namespace' => substr($normalizerClass, 0, $pos),
+ ));
+
+ $cache->write($code, array(new ReflectionClassResource(new \ReflectionClass($class))));
+ }
+ );
+
+ require_once $cache->getPath();
+
+ $this->normalizers[$class] = $normalizer = new $normalizerClass($this->defaultContext);
+ $normalizer->setNormalizer($this->normalizer);
+
+ return $normalizer->normalize($object, $format, $context);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function supportsNormalization($data, $format = null)
+ {
+ return \is_object($data) && !$data instanceof \Traversable;
+ }
+
+ /**
+ * Set circular reference limit.
+ *
+ * @param int $circularReferenceLimit Limit of iterations for the same object
+ *
+ * @return self
+ */
+ public function setCircularReferenceLimit($circularReferenceLimit)
+ {
+ $this->circularReferenceLimit = $circularReferenceLimit;
+
+ return $this;
+ }
+
+ /**
+ * Set circular reference handler.
+ *
+ * @param callable $circularReferenceHandler
+ *
+ * @return self
+ */
+ public function setCircularReferenceHandler(callable $circularReferenceHandler)
+ {
+ $this->circularReferenceHandler = $circularReferenceHandler;
+
+ return $this;
+ }
+
+ private function generateUniqueName($class)
+ {
+ return str_replace('\\', '-', $class);
+ }
+
+ private function getConfigCacheFactory(): ConfigCacheFactoryInterface
+ {
+ if (null === $this->configCacheFactory) {
+ $this->configCacheFactory = new ConfigCacheFactory($this->debug);
+ }
+
+ return $this->configCacheFactory;
+ }
+}
diff --git a/src/Symfony/Component/Serializer/Tests/Dumper/NormalizerDumperTest.php b/src/Symfony/Component/Serializer/Tests/Dumper/NormalizerDumperTest.php
new file mode 100644
index 0000000000000..fb42a3483001e
--- /dev/null
+++ b/src/Symfony/Component/Serializer/Tests/Dumper/NormalizerDumperTest.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Serializer\Tests\Dumper;
+
+use Doctrine\Common\Annotations\AnnotationReader;
+use Symfony\Component\Serializer\Dumper\NormalizerDumper;
+use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
+use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
+use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
+use Symfony\Component\Serializer\Tests\Normalizer\ObjectNormalizerTest;
+
+class NormalizerDumperTest extends ObjectNormalizerTest
+{
+ protected function getNormalizerFor(string $class, array $defaultContext = array()): NormalizerInterface
+ {
+ $normalizerName = 'Test'.md5($class).'Normalizer';
+
+ if (!class_exists($normalizerName)) {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ $dumper = new NormalizerDumper($classMetadataFactory);
+
+ eval('?>'.$dumper->dump($class, array('class' => $normalizerName)));
+ }
+
+ $normalizer = new $normalizerName($defaultContext);
+ $normalizer->setNormalizer($this->serializer);
+
+ return $normalizer;
+ }
+}
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
index 9eff7914d1c89..017e8a305b592 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
@@ -32,6 +32,7 @@
use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
use Symfony\Component\Serializer\Tests\Fixtures\NotSerializedConstructorArgumentDummy;
+use Symfony\Component\Serializer\Tests\Fixtures\Sibling;
use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;
/**
@@ -46,7 +47,7 @@ class ObjectNormalizerTest extends TestCase
/**
* @var SerializerInterface
*/
- private $serializer;
+ protected $serializer;
protected function setUp()
{
@@ -60,6 +61,16 @@ private function createNormalizer(array $defaultContext = array(), ClassMetadata
$this->normalizer->setSerializer($this->serializer);
}
+ protected function getNormalizerFor(string $class, array $defaultContext = array()): NormalizerInterface
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+
+ $normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext);
+ $normalizer->setSerializer($this->serializer);
+
+ return $normalizer;
+ }
+
public function testNormalize()
{
$obj = new ObjectDummy();
@@ -77,6 +88,7 @@ public function testNormalize()
->will($this->returnValue('string_object'))
;
+ $normalizer = $this->getNormalizerFor(ObjectDummy::class);
$this->assertEquals(
array(
'foo' => 'foo',
@@ -86,7 +98,7 @@ public function testNormalize()
'camelCase' => 'camelcase',
'object' => 'string_object',
),
- $this->normalizer->normalize($obj, 'any')
+ $normalizer->normalize($obj, 'any')
);
}
@@ -263,9 +275,7 @@ public function testFillWithEmptyDataWhenMissingData()
public function testGroupsNormalize()
{
- $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
- $this->normalizer = new ObjectNormalizer($classMetadataFactory);
- $this->normalizer->setSerializer($this->serializer);
+ $normalizer = $this->getNormalizerFor(GroupDummy::class);
$obj = new GroupDummy();
$obj->setFoo('foo');
@@ -277,7 +287,7 @@ public function testGroupsNormalize()
$this->assertEquals(array(
'bar' => 'bar',
- ), $this->normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('c'))));
+ ), $normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('c'))));
$this->assertEquals(array(
'symfony' => 'symfony',
@@ -286,7 +296,7 @@ public function testGroupsNormalize()
'bar' => 'bar',
'kevin' => 'kevin',
'coopTilleuls' => 'coopTilleuls',
- ), $this->normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('a', 'c'))));
+ ), $normalizer->normalize($obj, null, array(ObjectNormalizer::GROUPS => array('a', 'c'))));
}
public function testGroupsDenormalize()
@@ -321,14 +331,12 @@ public function testGroupsDenormalize()
public function testNormalizeNoPropertyInGroup()
{
- $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
- $this->normalizer = new ObjectNormalizer($classMetadataFactory);
- $this->normalizer->setSerializer($this->serializer);
+ $normalizer = $this->getNormalizerFor(GroupDummy::class);
$obj = new GroupDummy();
$obj->setFoo('foo');
- $this->assertEquals(array(), $this->normalizer->normalize($obj, null, array('groups' => array('notExist'))));
+ $this->assertEquals(array(), $normalizer->normalize($obj, null, array('groups' => array('notExist'))));
}
public function testGroupsNormalizeWithNameConverter()
@@ -599,9 +607,8 @@ public function testLegacyUnableToNormalizeCircularReference()
private function doTestUnableToNormalizeCircularReference(bool $legacy = false)
{
- $legacy ? $this->normalizer->setCircularReferenceLimit(2) : $this->createNormalizer(array(ObjectNormalizer::CIRCULAR_REFERENCE_LIMIT => 2));
+ $legacy ? $this->normalizer->setCircularReferenceLimit(2) : $this->normalizer = $this->getNormalizerFor(CircularReferenceDummy::class, array(ObjectNormalizer::CIRCULAR_REFERENCE_LIMIT => 2));
$serializer = new Serializer(array($this->normalizer));
- $this->normalizer->setSerializer($serializer);
$obj = new CircularReferenceDummy();
@@ -610,8 +617,10 @@ private function doTestUnableToNormalizeCircularReference(bool $legacy = false)
public function testSiblingReference()
{
- $serializer = new Serializer(array($this->normalizer));
- $this->normalizer->setSerializer($serializer);
+ $siblingHolderNormalizer = $this->getNormalizerFor(SiblingHolder::class);
+ $siblingNormalizer = $this->getNormalizerFor(Sibling::class);
+
+ $serializer = new Serializer(array($siblingHolderNormalizer, $siblingNormalizer));
$siblingHolder = new SiblingHolder();
@@ -620,7 +629,7 @@ public function testSiblingReference()
'sibling1' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
'sibling2' => array('coopTilleuls' => 'Les-Tilleuls.coop'),
);
- $this->assertEquals($expected, $this->normalizer->normalize($siblingHolder));
+ $this->assertEquals($expected, $serializer->normalize($siblingHolder));
}
public function testCircularReferenceHandler()
@@ -655,9 +664,8 @@ private function doTestCircularReferenceHandler(bool $legacy = false)
private function createNormalizerWithCircularReferenceHandler(callable $handler, bool $legacy)
{
- $legacy ? $this->normalizer->setCircularReferenceHandler($handler) : $this->createNormalizer(array(ObjectNormalizer::CIRCULAR_REFERENCE_HANDLER => $handler));
+ $legacy ? $this->normalizer->setCircularReferenceHandler($handler) : $this->normalizer = $this->getNormalizerFor(CircularReferenceDummy::class, array(ObjectNormalizer::CIRCULAR_REFERENCE_HANDLER => $handler));
$this->serializer = new Serializer(array($this->normalizer));
- $this->normalizer->setSerializer($this->serializer);
}
public function testDenormalizeNonExistingAttribute()
@@ -695,7 +703,8 @@ public function testNormalizeNotSerializableContext()
'bar' => null,
);
- $this->assertEquals($expected, $this->normalizer->normalize($objectDummy, null, array('not_serializable' => function () {
+ $normalizer = $this->getNormalizerFor(ObjectDummy::class);
+ $this->assertEquals($expected, $normalizer->normalize($objectDummy, null, array('not_serializable' => function () {
})));
}
@@ -883,8 +892,9 @@ public function testExtractAttributesRespectsContext()
public function testAttributesContextNormalize()
{
- $normalizer = new ObjectNormalizer();
- $serializer = new Serializer(array($normalizer));
+ $innerNormalizer = $this->getNormalizerFor(ObjectInner::class);
+ $dummyNormalizer = $this->getNormalizerFor(ObjectDummy::class);
+ $serializer = new Serializer(array($innerNormalizer, $dummyNormalizer));
$objectInner = new ObjectInner();
$objectInner->foo = 'innerFoo';
@@ -895,10 +905,9 @@ public function testAttributesContextNormalize()
$objectDummy->setBaz(true);
$objectDummy->setObject($objectInner);
- $context = array('attributes' => array('foo', 'baz', 'object' => array('foo')));
+ $context = array('attributes' => array('baz', 'object' => array('foo')));
$this->assertEquals(
array(
- 'foo' => 'foo',
'baz' => true,
'object' => array('foo' => 'innerFoo'),
),