Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a572179

Browse files
committed
[Serializer] Instantiator - Add an interface and default implementation to instantiate objects #30956
1 parent de1c216 commit a572179

13 files changed

+440
-162
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Context;
13+
14+
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
15+
16+
/**
17+
* Create a child context during serialization/deserialization process.
18+
*
19+
* @author Baptiste Leduc <[email protected]>
20+
*
21+
* @internal
22+
*/
23+
trait ChildContextTrait
24+
{
25+
public function createChildContext(array $parentContext, string $attribute, ?string $format = null, array $defaultContext = []): array
26+
{
27+
if (isset($parentContext[AbstractObjectNormalizer::ATTRIBUTES][$attribute])) {
28+
$parentContext[AbstractObjectNormalizer::ATTRIBUTES] = $parentContext[AbstractObjectNormalizer::ATTRIBUTES][$attribute];
29+
} else {
30+
unset($parentContext[AbstractObjectNormalizer::ATTRIBUTES]);
31+
}
32+
33+
return $parentContext;
34+
}
35+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Context;
13+
14+
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
15+
16+
/**
17+
* Create a child context with cache_key during serialization/deserialization or instantiation process.
18+
*
19+
* @author Baptiste Leduc <[email protected]>
20+
*
21+
* @internal
22+
*/
23+
trait ObjectChildContextTrait
24+
{
25+
use ChildContextTrait {
26+
createChildContext as parentCreateChildContext;
27+
}
28+
29+
public function createChildContext(array $parentContext, string $attribute, ?string $format = null, array $defaultContext = []): array
30+
{
31+
$parentContext = $this->parentCreateChildContext($parentContext, $attribute, $format, $defaultContext);
32+
$parentContext['cache_key'] = $this->getAttributesCacheKey($parentContext, $format, $defaultContext);
33+
34+
return $parentContext;
35+
}
36+
37+
/**
38+
* Builds the cache key for the attributes cache.
39+
*
40+
* The key must be different for every option in the context that could change which attributes should be handled.
41+
*
42+
* @return bool|string
43+
*/
44+
private function getAttributesCacheKey(array $context, ?string $format = null, array $defaultContext = [])
45+
{
46+
foreach ($context[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY] ?? $defaultContext[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY] ?? [] as $key) {
47+
unset($context[$key]);
48+
}
49+
unset($context[AbstractObjectNormalizer::EXCLUDE_FROM_CACHE_KEY]);
50+
unset($context['cache_key']); // avoid artificially different keys
51+
52+
try {
53+
return md5($format.serialize([
54+
'context' => $context,
55+
'ignored' => $context[AbstractObjectNormalizer::IGNORED_ATTRIBUTES] ?? $defaultContext[AbstractObjectNormalizer::IGNORED_ATTRIBUTES] ?? [],
56+
]));
57+
} catch (\Exception $exception) {
58+
// The context cannot be serialized, skip the cache
59+
return false;
60+
}
61+
}
62+
}

src/Symfony/Component/Serializer/Instantiator/Instantiator.php

Lines changed: 189 additions & 21 deletions
Large diffs are not rendered by default.

src/Symfony/Component/Serializer/Instantiator/InstantiatorInterface.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
1515

1616
/**
17+
* Describes the interface to instantiate an object using constructor parameters when needed.
18+
*
1719
* @author Jérôme Desjardins <[email protected]>
20+
* @author Baptiste Leduc <[email protected]>
1821
*/
1922
interface InstantiatorInterface
2023
{
2124
/**
22-
* Instantiate a new object.
25+
* Instantiates a new object.
2326
*
2427
* @throws MissingConstructorArgumentsException When some arguments are missing to use the constructor
25-
*
26-
* @return mixed
2728
*/
28-
public function instantiate(string $class, $data, $format = null, array $context = []);
29+
public function instantiate(string $class, array $data, array $context, string $format = null): InstantiatorResult;
2930
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Instantiator;
13+
14+
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
15+
16+
/**
17+
* Contains the result of an instantiation process.
18+
*
19+
* @author Baptiste Leduc <[email protected]>
20+
*/
21+
final class InstantiatorResult
22+
{
23+
private $object;
24+
private $data;
25+
private $context;
26+
private $error;
27+
28+
public function __construct(?object $object, array $data, array $context, string $error = null)
29+
{
30+
$this->object = $object;
31+
$this->data = $data;
32+
$this->context = $context;
33+
$this->error = $error;
34+
}
35+
36+
public function getObject(): ?object
37+
{
38+
return $this->object;
39+
}
40+
41+
public function getUnusedData(): array
42+
{
43+
return $this->data;
44+
}
45+
46+
public function getUnusedContext(): array
47+
{
48+
return $this->context;
49+
}
50+
51+
public function getError(): ?\Exception
52+
{
53+
if (null === $this->error) {
54+
return null;
55+
}
56+
57+
return new MissingConstructorArgumentsException($this->error);
58+
}
59+
60+
public function hasFailed(): bool
61+
{
62+
return null === $this->object;
63+
}
64+
}

src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14+
use Symfony\Component\Serializer\Context\ObjectChildContextTrait;
1415
use Symfony\Component\Serializer\Exception\CircularReferenceException;
1516
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1617
use Symfony\Component\Serializer\Exception\LogicException;
@@ -31,6 +32,7 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
3132
{
3233
use ObjectToPopulateTrait;
3334
use SerializerAwareTrait;
35+
use ObjectChildContextTrait;
3436

3537
/* constants to configure the context */
3638

@@ -433,18 +435,4 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
433435

434436
return $parameterData;
435437
}
436-
437-
/**
438-
* @internal
439-
*/
440-
protected function createChildContext(array $parentContext, string $attribute, ?string $format): array
441-
{
442-
if (isset($parentContext[self::ATTRIBUTES][$attribute])) {
443-
$parentContext[self::ATTRIBUTES] = $parentContext[self::ATTRIBUTES][$attribute];
444-
} else {
445-
unset($parentContext[self::ATTRIBUTES]);
446-
}
447-
448-
return $parentContext;
449-
}
450438
}

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
1616
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1717
use Symfony\Component\PropertyInfo\Type;
18+
use Symfony\Component\Serializer\Context\ObjectChildContextTrait;
1819
use Symfony\Component\Serializer\Encoder\CsvEncoder;
1920
use Symfony\Component\Serializer\Encoder\JsonEncoder;
2021
use Symfony\Component\Serializer\Encoder\XmlEncoder;
2122
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
2223
use Symfony\Component\Serializer\Exception\LogicException;
24+
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
2325
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
24-
use Symfony\Component\Serializer\Exception\RuntimeException;
26+
use Symfony\Component\Serializer\Instantiator\Instantiator;
27+
use Symfony\Component\Serializer\Instantiator\InstantiatorInterface;
2528
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
2629
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
2730
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
@@ -33,8 +36,11 @@
3336
*
3437
* @author Kévin Dunglas <[email protected]>
3538
*/
36-
abstract class AbstractObjectNormalizer extends AbstractNormalizer
39+
abstract class AbstractObjectNormalizer extends AbstractNormalizer implements DenormalizerAwareInterface
3740
{
41+
use DenormalizerAwareTrait;
42+
use ObjectChildContextTrait;
43+
3844
/**
3945
* Set to true to respect the max depth metadata on fields.
4046
*/
@@ -93,6 +99,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
9399
public const PRESERVE_EMPTY_OBJECTS = 'preserve_empty_objects';
94100

95101
private $propertyTypeExtractor;
102+
private $instantiator;
96103
private $typesCache = [];
97104
private $attributesCache = [];
98105

@@ -103,7 +110,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
103110
*/
104111
protected $classDiscriminatorResolver;
105112

106-
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [])
113+
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [], InstantiatorInterface $instantiator = null)
107114
{
108115
parent::__construct($classMetadataFactory, $nameConverter, $defaultContext);
109116

@@ -120,6 +127,24 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory
120127
}
121128
$this->classDiscriminatorResolver = $classDiscriminatorResolver;
122129
$this->objectClassResolver = $objectClassResolver;
130+
131+
if (null === $instantiator) {
132+
$instantiator = new Instantiator($classMetadataFactory, $this->classDiscriminatorResolver, $propertyTypeExtractor, null, $nameConverter, null);
133+
134+
if ($this->denormalizer instanceof DenormalizerInterface) {
135+
$instantiator->setDenormalizer($this->denormalizer);
136+
}
137+
}
138+
$this->instantiator = $instantiator;
139+
}
140+
141+
public function setDenormalizer(DenormalizerInterface $denormalizer)
142+
{
143+
$this->denormalizer = $denormalizer;
144+
145+
// Because we need a denormalizer in the Instantiator and we create it in the construct method, it won't get it.
146+
// So we are obliged to overwrite this method in order to give the denormalizer to the Instantiator.
147+
$this->instantiator->setDenormalizer($denormalizer);
123148
}
124149

125150
/**
@@ -210,29 +235,6 @@ public function normalize($object, string $format = null, array $context = [])
210235
return $data;
211236
}
212237

213-
/**
214-
* {@inheritdoc}
215-
*/
216-
protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null)
217-
{
218-
if ($this->classDiscriminatorResolver && $mapping = $this->classDiscriminatorResolver->getMappingForClass($class)) {
219-
if (!isset($data[$mapping->getTypeProperty()])) {
220-
throw new RuntimeException(sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class));
221-
}
222-
223-
$type = $data[$mapping->getTypeProperty()];
224-
if (null === ($mappedClass = $mapping->getClassForType($type))) {
225-
throw new RuntimeException(sprintf('The type "%s" has no mapped class for the abstract object "%s".', $type, $class));
226-
}
227-
228-
if ($mappedClass !== $class) {
229-
return $this->instantiateObject($data, $mappedClass, $context, new \ReflectionClass($mappedClass), $allowedAttributes, $format);
230-
}
231-
}
232-
233-
return parent::instantiateObject($data, $class, $context, $reflectionClass, $allowedAttributes, $format);
234-
}
235-
236238
/**
237239
* Gets and caches attributes for the given object, format and context.
238240
*
@@ -307,8 +309,14 @@ public function denormalize($data, string $type, string $format = null, array $c
307309
$normalizedData = $this->prepareForDenormalization($data);
308310
$extraAttributes = [];
309311

310-
$reflectionClass = new \ReflectionClass($type);
311-
$object = $this->instantiateObject($normalizedData, $type, $context, $reflectionClass, $allowedAttributes, $format);
312+
$instantiatorResult = $this->instantiator->instantiate($type, $normalizedData, $context, $format);
313+
if ($instantiatorResult->hasFailed()) {
314+
throw new MissingConstructorArgumentsException($instantiatorResult->getError());
315+
}
316+
$object = $instantiatorResult->getObject();
317+
$normalizedData = $instantiatorResult->getUnusedData();
318+
$context = $instantiatorResult->getUnusedContext();
319+
312320
$resolvedClass = $this->objectClassResolver ? ($this->objectClassResolver)($object) : \get_class($object);
313321

314322
foreach ($normalizedData as $attribute => $value) {
@@ -595,23 +603,6 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str
595603
return false;
596604
}
597605

598-
/**
599-
* Overwritten to update the cache key for the child.
600-
*
601-
* We must not mix up the attribute cache between parent and children.
602-
*
603-
* {@inheritdoc}
604-
*
605-
* @internal
606-
*/
607-
protected function createChildContext(array $parentContext, string $attribute, ?string $format): array
608-
{
609-
$context = parent::createChildContext($parentContext, $attribute, $format);
610-
$context['cache_key'] = $this->getCacheKey($format, $context);
611-
612-
return $context;
613-
}
614-
615606
/**
616607
* Builds the cache key for the attributes cache.
617608
*

src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1717
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1818
use Symfony\Component\Serializer\Exception\LogicException;
19+
use Symfony\Component\Serializer\Instantiator\InstantiatorInterface;
1920
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
2021
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
2122
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
@@ -34,13 +35,13 @@ class ObjectNormalizer extends AbstractObjectNormalizer
3435

3536
private $objectClassResolver;
3637

37-
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [])
38+
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = [], InstantiatorInterface $instantiator = null)
3839
{
3940
if (!class_exists(PropertyAccess::class)) {
4041
throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.');
4142
}
4243

43-
parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext);
44+
parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext, $instantiator);
4445

4546
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
4647

0 commit comments

Comments
 (0)