Skip to content

Commit 2a9e91c

Browse files
committed
deserialize hooks
1 parent 55d3491 commit 2a9e91c

26 files changed

+721
-723
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/ser_des.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,6 @@
139139
service('ser_des.type_extractor'),
140140
])
141141

142-
->set('ser_des.hook.deserialize.property', DeserializeHook\PropertyHook::class)
143-
->args([
144-
service('ser_des.type_extractor'),
145-
])
146-
147142
// Serializable resolvers
148143
->set('ser_des.serializable_resolver', PathSerializableResolver::class)
149144
->args([

src/Symfony/Component/SerDes/Context/DeserializeContext.php

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\SerDes\Context;
1313

1414
use Symfony\Component\SerDes\Hook\Deserialize\ObjectHookInterface;
15-
use Symfony\Component\SerDes\Hook\Deserialize\PropertyHookInterface;
1615

1716
/**
1817
* @author Mathias Arlaud <[email protected]>
@@ -61,8 +60,7 @@ public function withUnionSelector(array $unionSelector): self
6160
}
6261

6362
/**
64-
* @param ObjectHookInterface|callable(string, array<string, mixed>): array{type: string, context: array<string, mixed>} $hook
65-
* @param class-string|null $className
63+
* @param class-string|null $className
6664
*/
6765
public function withObjectHook(ObjectHookInterface|callable $hook, string $className = null): self
6866
{
@@ -71,17 +69,6 @@ public function withObjectHook(ObjectHookInterface|callable $hook, string $class
7169
return $this->withHook($hookName, $hook);
7270
}
7371

74-
/**
75-
* @param PropertyHookInterface|callable(\ReflectionClass<object>, string, callable(): mixed, array<string, mixed>): array{name?: string, value?: callable(): mixed, context?: array<string, mixed>} $hook
76-
* @param class-string|null $className
77-
*/
78-
public function withPropertyHook(PropertyHookInterface|callable $hook, string $className = null, string $propertyName = null): self
79-
{
80-
$hookName = null !== $className && null !== $propertyName ? sprintf('%s::$%s', $className, $propertyName) : 'property';
81-
82-
return $this->withHook($hookName, $hook);
83-
}
84-
8572
public function withEagerReading(): self
8673
{
8774
return new self(['lazy_reading' => false] + $this->options);

src/Symfony/Component/SerDes/Context/SerializeContext.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\SerDes\Context;
1313

1414
use Symfony\Component\SerDes\Hook\Serialize\ObjectHookInterface;
15-
use Symfony\Component\SerDes\Hook\Serialize\PropertyHookInterface;
1615

1716
/**
1817
* @author Mathias Arlaud <[email protected]>
@@ -58,8 +57,7 @@ public function withJsonEncodeFlags(int $flags): self
5857
}
5958

6059
/**
61-
* @param ObjectHookInterface|callable(string, string, array<string, mixed>): array{type: string, accessor: string, context: array<string, mixed>} $hook
62-
* @param class-string|null $className
60+
* @param class-string|null $className
6361
*/
6462
public function withObjectHook(ObjectHookInterface|callable $hook, string $className = null): self
6563
{

src/Symfony/Component/SerDes/Hook/Deserialize/ObjectHook.php

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
namespace Symfony\Component\SerDes\Hook\Deserialize;
1313

1414
use Symfony\Component\SerDes\Exception\InvalidArgumentException;
15+
use Symfony\Component\SerDes\Type\Type;
1516
use Symfony\Component\SerDes\Type\TypeExtractorInterface;
1617
use Symfony\Component\SerDes\Type\TypeGenericsHelper;
18+
use Symfony\Component\SerDes\Type\UnionType;
1719

1820
/**
1921
* @author Mathias Arlaud <[email protected]>
@@ -23,10 +25,10 @@
2325
final class ObjectHook implements ObjectHookInterface
2426
{
2527
/**
26-
* @var array{class_reflection: array<string, \ReflectionClass<object>>}
28+
* @var array{type: array<string, Type|UnionType>}
2729
*/
2830
private static array $cache = [
29-
'class_reflection' => [],
31+
'type' => [],
3032
];
3133

3234
private readonly TypeGenericsHelper $typeGenericsHelper;
@@ -37,10 +39,62 @@ public function __construct(
3739
$this->typeGenericsHelper = new TypeGenericsHelper();
3840
}
3941

40-
public function __invoke(string $type, array $context): array
42+
public function __invoke(Type $type, array $properties, array $context): array
4143
{
44+
$className = $type->className();
45+
$context = $this->addGenericParameterTypes($type, $context);
46+
47+
foreach ($properties as $name => &$property) {
48+
if (isset($context['groups'])) {
49+
$matchingGroup = false;
50+
foreach ($context['groups'] as $group) {
51+
if (isset($context['_symfony']['deserialize']['property_groups'][$className][$name][$group])) {
52+
$matchingGroup = true;
53+
54+
break;
55+
}
56+
}
57+
58+
if (!$matchingGroup) {
59+
unset($properties[$name]);
60+
61+
continue;
62+
}
63+
}
64+
65+
$cacheKey = $className.$name;
66+
67+
$property['name'] = $context['_symfony']['deserialize']['property_name'][$className][$name] ?? $name;
68+
69+
if (null === $formatter = $context['_symfony']['deserialize']['property_formatter'][$className][$name] ?? null) {
70+
$type = (self::$cache['type'][$cacheKey] ??= $this->typeExtractor->extractFromProperty(new \ReflectionProperty($className, $property['name'])));
71+
72+
if (isset($context['_symfony']['generic_parameter_types'][$className])) {
73+
$type = $this->typeGenericsHelper->replaceGenericTypes($type, $context['_symfony']['generic_parameter_types'][$className]);
74+
}
75+
76+
$property['value_provider'] = fn (Type|UnionType $initialType) => $property['value_provider']($type);
77+
78+
continue;
79+
}
80+
81+
$cacheKey .= ($propertyFormatterHash = json_encode($context['_symfony']['deserialize']['property_formatter'][$className][$property['name']]));
82+
83+
$propertyFormatter = \Closure::fromCallable($context['_symfony']['deserialize']['property_formatter'][$className][$property['name']]);
84+
$propertyFormatterReflection = new \ReflectionFunction($propertyFormatter);
85+
86+
$type = (self::$cache['type'][$cacheKey] ??= $this->typeExtractor->extractFromFunctionParameter($propertyFormatterReflection->getParameters()[0]));
87+
88+
if (isset($context['_symfony']['generic_parameter_types'][$className]) && $propertyFormatterReflection->getClosureScopeClass()?->getName() === $className) {
89+
$type = $this->typeGenericsHelper->replaceGenericTypes($type, $context['_symfony']['generic_parameter_types'][$className]);
90+
}
91+
92+
$property['value_provider'] = fn (Type|UnionType $initialType) => $propertyFormatter($property['value_provider']($type), $context);
93+
}
94+
4295
return [
43-
'context' => $this->addGenericParameterTypes($type, $context),
96+
'properties' => $properties,
97+
'context' => $context,
4498
];
4599
}
46100

@@ -49,29 +103,19 @@ public function __invoke(string $type, array $context): array
49103
*
50104
* @return array<string, mixed>
51105
*/
52-
private function addGenericParameterTypes(string $type, array $context): array
106+
private function addGenericParameterTypes(Type $type, array $context): array
53107
{
54-
$generics = $this->typeGenericsHelper->extractGenerics($type);
55-
56-
$genericType = $generics['genericType'];
57-
$genericParameters = $generics['genericParameters'];
58-
59-
if (!class_exists($genericType)) {
60-
return $context;
61-
}
62-
63-
if (!isset(self::$cache['class_reflection'][$type])) {
64-
self::$cache['class_reflection'][$type] = new \ReflectionClass($genericType);
65-
}
108+
$className = $type->className();
109+
$genericParameterTypes = $type->genericParameterTypes();
66110

67-
$templates = $this->typeExtractor->extractTemplateFromClass(self::$cache['class_reflection'][$type]);
111+
$templates = $this->typeExtractor->extractTemplateFromClass(new \ReflectionClass($className));
68112

69-
if (\count($templates) !== \count($genericParameters)) {
70-
throw new InvalidArgumentException(sprintf('Given %d generic parameters in "%s", but %d templates are defined in "%s".', \count($genericParameters), $type, \count($templates), $genericType));
113+
if (\count($templates) !== \count($genericParameterTypes)) {
114+
throw new InvalidArgumentException(sprintf('Given %d generic parameters in "%s", but %d templates are defined in "%s".', \count($genericParameterTypes), (string) $type, \count($templates), $className));
71115
}
72116

73-
foreach ($genericParameters as $i => $genericParameter) {
74-
$context['_symfony']['generic_parameter_types'][$genericType][$templates[$i]] = $genericParameter;
117+
foreach ($genericParameterTypes as $i => $genericParameterType) {
118+
$context['_symfony']['generic_parameter_types'][$className][$templates[$i]] = $genericParameterType;
75119
}
76120

77121
return $context;

src/Symfony/Component/SerDes/Hook/Deserialize/ObjectHookInterface.php

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

1212
namespace Symfony\Component\SerDes\Hook\Deserialize;
1313

14+
use Symfony\Component\SerDes\Type\Type;
15+
use Symfony\Component\SerDes\Type\UnionType;
16+
1417
/**
1518
* @author Mathias Arlaud <[email protected]>
1619
*
@@ -19,9 +22,10 @@
1922
interface ObjectHookInterface
2023
{
2124
/**
22-
* @param array<string, mixed> $context
25+
* @param array<string, array{name: string, value_provider: callable(Type|UnionType): mixed}> $properties
26+
* @param array<string, mixed> $context
2327
*
24-
* @return array{type?: string, context?: array<string, mixed>}
28+
* @return array{properties?: array<string, array{name: string, value_provider: callable(Type|UnionType): mixed}>, context?: array<string, mixed>}
2529
*/
26-
public function __invoke(string $type, array $context): array;
30+
public function __invoke(Type $type, array $properties, array $context): array;
2731
}

src/Symfony/Component/SerDes/Hook/Deserialize/PropertyHook.php

Lines changed: 0 additions & 99 deletions
This file was deleted.

src/Symfony/Component/SerDes/Hook/Deserialize/PropertyHookInterface.php

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/Symfony/Component/SerDes/Hook/Serialize/ObjectHookInterface.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ interface ObjectHookInterface
2323
{
2424
/**
2525
* @param array<string, array{name: string, type: Type|UnionType, accessor: string}> $properties
26-
* @param array<string, mixed> $context
26+
* @param array<string, mixed> $context
2727
*
28-
* @return array{properties?: array<string, array{name?: string, type?: Type|UnionType, accessor?: string}>, context?: array<string, mixed>}
28+
* @return array{properties?: array<string, array{name: string, type: Type|UnionType, accessor: string}>, context?: array<string, mixed>}
2929
*/
3030
public function __invoke(Type $type, string $accessor, array $properties, array $context): array;
3131
}

src/Symfony/Component/SerDes/Internal/Deserialize/Csv/CsvDeserializer.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,24 @@ protected function deserializeDict(mixed $data, Type $type, array $context): \It
9797
}
9898
}
9999

100-
protected function deserializeObjectProperties(mixed $data, Type $type, array $context): \Iterator
100+
protected function deserializeObjectProperties(mixed $data, Type $type, array $context): ?array
101101
{
102102
if (!\is_array($data)) {
103103
throw $this->tooDeepException();
104104
}
105105

106+
$properties = [];
106107
foreach ($data as $index => $value) {
107108
if ('' === $value) {
108109
continue;
109110
}
110111

111-
yield $context['csv_headers'][$index] => $value;
112+
/** @var string $key */
113+
$key = $context['csv_headers'][$index];
114+
$properties[$key] = $value;
112115
}
116+
117+
return $properties;
113118
}
114119

115120
protected function deserializeMixed(mixed $data, array $context): mixed
@@ -121,11 +126,11 @@ protected function deserializeMixed(mixed $data, array $context): mixed
121126
return $data;
122127
}
123128

124-
protected function propertyValueCallable(Type|UnionType $type, mixed $data, mixed $value, array $context): callable
129+
protected function deserializeObjectPropertyValue(Type|UnionType $type, mixed $resource, mixed $value, array $context): mixed
125130
{
126131
++$context['csv_depth'];
127132

128-
return fn () => $this->doDeserialize($value, $type, $context);
133+
return $this->doDeserialize($value, $type, $context);
129134
}
130135

131136
/**

0 commit comments

Comments
 (0)