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

Skip to content

Commit f6fc79d

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

11 files changed

+492
-181
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: 203 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: 46 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@
1111

1212
namespace Symfony\Component\Serializer\Normalizer;
1313

14+
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
15+
use Symfony\Component\Serializer\Context\ChildContextTrait;
1416
use Symfony\Component\Serializer\Exception\CircularReferenceException;
1517
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1618
use Symfony\Component\Serializer\Exception\LogicException;
1719
use Symfony\Component\Serializer\Exception\MissingConstructorArgumentsException;
1820
use Symfony\Component\Serializer\Exception\RuntimeException;
21+
use Symfony\Component\Serializer\Instantiator\Instantiator;
22+
use Symfony\Component\Serializer\Instantiator\InstantiatorInterface;
1923
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
24+
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
2025
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
2126
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
2227
use Symfony\Component\Serializer\SerializerAwareInterface;
@@ -27,10 +32,12 @@
2732
*
2833
* @author Kévin Dunglas <[email protected]>
2934
*/
30-
abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface
35+
abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface, DenormalizerAwareInterface
3136
{
3237
use ObjectToPopulateTrait;
3338
use SerializerAwareTrait;
39+
use DenormalizerAwareTrait;
40+
use ChildContextTrait;
3441

3542
/* constants to configure the context */
3643

@@ -133,10 +140,15 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
133140
*/
134141
protected $nameConverter;
135142

143+
/**
144+
* @var Instantiator|null
145+
*/
146+
protected $instantiator;
147+
136148
/**
137149
* Sets the {@link ClassMetadataFactoryInterface} to use.
138150
*/
139-
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, array $defaultContext = [])
151+
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, array $defaultContext = [], InstantiatorInterface $instantiator = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null)
140152
{
141153
$this->classMetadataFactory = $classMetadataFactory;
142154
$this->nameConverter = $nameConverter;
@@ -157,6 +169,27 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory
157169
if (isset($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER]) && !\is_callable($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER])) {
158170
throw new InvalidArgumentException(sprintf('Invalid callback found in the "%s" default context option.', self::CIRCULAR_REFERENCE_HANDLER));
159171
}
172+
173+
if (null === $instantiator) {
174+
$instantiator = new Instantiator($classMetadataFactory, $classDiscriminatorResolver, $propertyTypeExtractor, null, $nameConverter, null);
175+
176+
if ($this->denormalizer instanceof DenormalizerInterface) {
177+
$instantiator->setDenormalizer($this->denormalizer);
178+
}
179+
}
180+
$this->instantiator = $instantiator;
181+
}
182+
183+
/**
184+
* @internal
185+
*/
186+
public function setDenormalizer(DenormalizerInterface $denormalizer)
187+
{
188+
$this->denormalizer = $denormalizer;
189+
190+
// Because we need a denormalizer in the Instantiator and we create it in the construct method, it won't get it.
191+
// So we are obliged to overwrite this method in order to give the denormalizer to the Instantiator.
192+
$this->instantiator->setDenormalizer($denormalizer);
160193
}
161194

162195
/**
@@ -312,9 +345,13 @@ protected function prepareForDenormalization($data)
312345
* @param array|bool $allowedAttributes
313346
*
314347
* @return \ReflectionMethod|null
348+
*
349+
* @deprecated since Symfony 5.3, use "instantiator_constructor" field in context array instead.
315350
*/
316351
protected function getConstructor(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes)
317352
{
353+
trigger_deprecation('symfony/serializer', '5.3', 'The "%s()" method is deprecated. Use "%s" field in context array instead.', __METHOD__, Instantiator::INSTANTIATOR_CONSTRUCTOR);
354+
318355
return $reflectionClass->getConstructor();
319356
}
320357

@@ -332,77 +369,20 @@ protected function getConstructor(array &$data, string $class, array &$context,
332369
*
333370
* @throws RuntimeException
334371
* @throws MissingConstructorArgumentsException
372+
*
373+
* @deprecated since Symfony 5.3, Use "Symfony\Component\Serializer\Instantiator\Instantiator::instantiate()" instead.
335374
*/
336375
protected function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null)
337376
{
338-
if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) {
339-
unset($context[self::OBJECT_TO_POPULATE]);
377+
trigger_deprecation('symfony/serializer', '5.3', 'The "%s()" method is deprecated. Use "%s::instantiate()" instead.', __METHOD__, Instantiator::class);
340378

341-
return $object;
379+
if (!\array_key_exists(Instantiator::INSTANTIATOR_CONSTRUCTOR, $context)) {
380+
$context[Instantiator::INSTANTIATOR_CONSTRUCTOR] = \Closure::fromCallable([$this, 'getConstructor']);
342381
}
343-
// clean up even if no match
344-
unset($context[static::OBJECT_TO_POPULATE]);
345382

346-
$constructor = $this->getConstructor($data, $class, $context, $reflectionClass, $allowedAttributes);
347-
if ($constructor) {
348-
if (true !== $constructor->isPublic()) {
349-
return $reflectionClass->newInstanceWithoutConstructor();
350-
}
351-
352-
$constructorParameters = $constructor->getParameters();
353-
354-
$params = [];
355-
foreach ($constructorParameters as $constructorParameter) {
356-
$paramName = $constructorParameter->name;
357-
$key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName;
358-
359-
$allowed = false === $allowedAttributes || \in_array($paramName, $allowedAttributes);
360-
$ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
361-
if ($constructorParameter->isVariadic()) {
362-
if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
363-
if (!\is_array($data[$paramName])) {
364-
throw new RuntimeException(sprintf('Cannot create an instance of "%s" from serialized data because the variadic parameter "%s" can only accept an array.', $class, $constructorParameter->name));
365-
}
366-
367-
$variadicParameters = [];
368-
foreach ($data[$paramName] as $parameterData) {
369-
$variadicParameters[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
370-
}
371-
372-
$params = array_merge($params, $variadicParameters);
373-
unset($data[$key]);
374-
}
375-
} elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
376-
$parameterData = $data[$key];
377-
if (null === $parameterData && $constructorParameter->allowsNull()) {
378-
$params[] = null;
379-
// Don't run set for a parameter passed to the constructor
380-
unset($data[$key]);
381-
continue;
382-
}
383-
384-
// Don't run set for a parameter passed to the constructor
385-
$params[] = $this->denormalizeParameter($reflectionClass, $constructorParameter, $paramName, $parameterData, $context, $format);
386-
unset($data[$key]);
387-
} elseif (\array_key_exists($key, $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
388-
$params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
389-
} elseif (\array_key_exists($key, $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class] ?? [])) {
390-
$params[] = $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key];
391-
} elseif ($constructorParameter->isDefaultValueAvailable()) {
392-
$params[] = $constructorParameter->getDefaultValue();
393-
} else {
394-
throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires parameter "%s" to be present.', $class, $constructorParameter->name));
395-
}
396-
}
383+
$result = $this->instantiator->instantiate($class, $data, $context, $format);
397384

398-
if ($constructor->isConstructor()) {
399-
return $reflectionClass->newInstanceArgs($params);
400-
} else {
401-
return $constructor->invokeArgs(null, $params);
402-
}
403-
}
404-
405-
return new $class();
385+
return $result->getObject();
406386
}
407387

408388
/**
@@ -433,18 +413,4 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
433413

434414
return $parameterData;
435415
}
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-
}
450416
}

0 commit comments

Comments
 (0)