Skip to content

Commit a0b9d35

Browse files
committed
handle mixed, object and raw array
1 parent d88381d commit a0b9d35

File tree

11 files changed

+158
-62
lines changed

11 files changed

+158
-62
lines changed

src/Symfony/Component/SerDes/Internal/Deserialize/DeserializerFactory.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ private function __construct()
3232

3333
/**
3434
* @param array<string, mixed> $context
35+
*
36+
* @return Deserializer<mixed>
3537
*/
3638
public static function create(string $format, array $context): Deserializer
3739
{
@@ -44,6 +46,9 @@ public static function create(string $format, array $context): Deserializer
4446
};
4547
}
4648

49+
/**
50+
* @return Deserializer<mixed>
51+
*/
4752
private static function json(bool $lazy, bool $validate): Deserializer
4853
{
4954
if ($lazy) {

src/Symfony/Component/SerDes/Internal/Deserialize/EagerDeserializer.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ protected function deserializeDict(mixed $data, Type $type, array $context): ?\I
6060
return $this->deserializeCollectionItems($data, $type->collectionValueType(), $context);
6161
}
6262

63+
/**
64+
* @return array<string, mixed>|null
65+
*/
6366
protected function deserializeObjectProperties(mixed $data, Type $type, array $context): ?array
6467
{
6568
if (null === $data) {

src/Symfony/Component/SerDes/Internal/Serialize/TemplateGenerator/JsonTemplateGenerator.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ protected function objectNodes(Type $type, array $propertiesInfo, array $context
130130
return $nodes;
131131
}
132132

133+
protected function mixedNodes(NodeInterface $accessor, array $context): array
134+
{
135+
return [
136+
new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), $this->encodeValueNode($accessor)])),
137+
];
138+
}
139+
133140
private function encodeValueNode(NodeInterface $node): NodeInterface
134141
{
135142
return new FunctionNode('\json_encode', [

src/Symfony/Component/SerDes/Internal/Serialize/TemplateGenerator/TemplateGenerator.php

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use Symfony\Component\SerDes\Exception\CircularReferenceException;
1515
use Symfony\Component\SerDes\Exception\LogicException;
16-
use Symfony\Component\SerDes\Exception\UnsupportedTypeException;
1716
use Symfony\Component\SerDes\Internal\Serialize\Compiler;
1817
use Symfony\Component\SerDes\Internal\Serialize\Node\AssignNode;
1918
use Symfony\Component\SerDes\Internal\Serialize\Node\BinaryNode;
@@ -91,6 +90,13 @@ abstract protected function dictNodes(Type $type, NodeInterface $accessor, array
9190
*/
9291
abstract protected function objectNodes(Type $type, array $propertiesInfo, array $context): array;
9392

93+
/**
94+
* @param array<string, mixed> $context
95+
*
96+
* @return list<NodeInterface>
97+
*/
98+
abstract protected function mixedNodes(NodeInterface $accessor, array $context): array;
99+
94100
/**
95101
* @param array<string, mixed> $context
96102
*
@@ -174,7 +180,13 @@ private function nodes(Type|UnionType $type, NodeInterface $accessor, array $con
174180
}
175181

176182
if ($type->isObject()) {
177-
if (null !== $hook = $context['hooks']['serialize'][$className = $type->className()] ?? $context['hooks']['serialize']['object'] ?? null) {
183+
try {
184+
$className = $type->className();
185+
} catch (LogicException) {
186+
return $this->mixedNodes($accessor, $context);
187+
}
188+
189+
if (null !== $hook = $context['hooks']['serialize'][$className] ?? $context['hooks']['serialize']['object'] ?? null) {
178190
$hookResult = $hook((string) $type, (new Compiler())->compile($accessor)->source(), $context);
179191

180192
/** @var Type $type */
@@ -183,7 +195,7 @@ private function nodes(Type|UnionType $type, NodeInterface $accessor, array $con
183195
$context = $hookResult['context'] ?? $context;
184196
}
185197

186-
if (isset($context['generated_classes'][$className = $type->className()])) {
198+
if (isset($context['generated_classes'][$className])) {
187199
throw new CircularReferenceException($className);
188200
}
189201

@@ -199,7 +211,7 @@ private function nodes(Type|UnionType $type, NodeInterface $accessor, array $con
199211
];
200212
}
201213

202-
throw new UnsupportedTypeException((string) $type);
214+
return $this->mixedNodes($accessor, $context);
203215
}
204216

205217
/**
@@ -261,6 +273,14 @@ private function typeValidatorNode(Type $type, NodeInterface $accessor): NodeInt
261273
return new BinaryNode('instanceof', $accessor, new ScalarNode($type->className()));
262274
}
263275

276+
if ('array' === $type->name()) {
277+
return new FunctionNode('\is_array', [$accessor]);
278+
}
279+
280+
if ('mixed' === $type->name()) {
281+
return new ScalarNode(true);
282+
}
283+
264284
throw new LogicException(sprintf('Cannot find validator for "%s".', (string) $type));
265285
}
266286
}

src/Symfony/Component/SerDes/Internal/Type.php

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,10 @@ public function __construct(
3434
private readonly bool $isGeneric = false,
3535
private readonly array $genericParameterTypes = [],
3636
) {
37-
if ($this->isObject() && null === $this->className) {
38-
throw new InvalidArgumentException('Missing className of "object" type.');
39-
}
40-
4137
if ($this->isGeneric && !$this->genericParameterTypes) {
4238
throw new InvalidArgumentException(sprintf('Missing generic parameter types of "%s" type.', $this->name));
4339
}
4440

45-
if ($this->isCollection() && 2 !== \count($this->genericParameterTypes)) {
46-
throw new InvalidArgumentException(sprintf('Invalid generic parameter types of "%s" type.', $this->name));
47-
}
48-
4941
$this->stringValue = $this->computeStringValue();
5042
}
5143

@@ -68,10 +60,11 @@ public function className(): string
6860
throw new LogicException(sprintf('Cannot get class on "%s" type as it\'s not an object nor an enum.', $this->name));
6961
}
7062

71-
/** @var class-string $className */
72-
$className = $this->className;
63+
if (null === $this->className) {
64+
throw new LogicException(sprintf('No class has been defined for "%s".', $this->name));
65+
}
7366

74-
return $className;
67+
return $this->className;
7568
}
7669

7770
/**
@@ -133,7 +126,16 @@ public function isList(): bool
133126

134127
public function isDict(): bool
135128
{
136-
return $this->isCollection() && !$this->isList();
129+
if (!$this->isCollection()) {
130+
return false;
131+
}
132+
133+
$collectionKeyType = $this->collectionKeyType();
134+
if (!$collectionKeyType instanceof self) {
135+
return false;
136+
}
137+
138+
return 'string' === $collectionKeyType->name();
137139
}
138140

139141
public function collectionKeyType(): self|UnionType
@@ -142,7 +144,7 @@ public function collectionKeyType(): self|UnionType
142144
throw new LogicException(sprintf('Cannot get collection key type on "%s" type as it\'s not a collection.', $this->name));
143145
}
144146

145-
return $this->genericParameterTypes[0];
147+
return $this->genericParameterTypes[0] ?? new self('mixed');
146148
}
147149

148150
public function collectionValueType(): self|UnionType
@@ -151,7 +153,7 @@ public function collectionValueType(): self|UnionType
151153
throw new LogicException(sprintf('Cannot get collection value type on "%s" type as it\'s not a collection.', $this->name));
152154
}
153155

154-
return $this->genericParameterTypes[1];
156+
return $this->genericParameterTypes[1] ?? new self('mixed');
155157
}
156158

157159
private function computeStringValue(): string
@@ -162,9 +164,10 @@ private function computeStringValue(): string
162164

163165
$nullablePrefix = $this->isNullable() ? '?' : '';
164166

165-
$name = $this->name();
166-
if ($this->isObject() || $this->isEnum()) {
167+
try {
167168
$name = $this->className();
169+
} catch (LogicException) {
170+
$name = $this->name();
168171
}
169172

170173
if ($this->isGeneric()) {

src/Symfony/Component/SerDes/Tests/Internal/Serialize/Json/SerializeGenerateTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,40 @@ public static function serializeGenerateDataProvider(): iterable
102102
[],
103103
];
104104

105+
yield [
106+
<<<PHP
107+
<?php
108+
109+
/**
110+
* @param mixed \$data
111+
* @param resource \$resource
112+
*/
113+
return static function (mixed \$data, mixed \$resource, array \$context): void {
114+
\\fwrite(\$resource, \json_encode(\$data, \$context["json_encode_flags"] ?? 0));
115+
};
116+
117+
PHP,
118+
'mixed',
119+
[],
120+
];
121+
122+
yield [
123+
<<<PHP
124+
<?php
125+
126+
/**
127+
* @param array \$data
128+
* @param resource \$resource
129+
*/
130+
return static function (mixed \$data, mixed \$resource, array \$context): void {
131+
\\fwrite(\$resource, \json_encode(\$data, \$context["json_encode_flags"] ?? 0));
132+
};
133+
134+
PHP,
135+
'array',
136+
[],
137+
];
138+
105139
yield [
106140
<<<PHP
107141
<?php
@@ -200,6 +234,23 @@ public static function serializeGenerateDataProvider(): iterable
200234
[],
201235
];
202236

237+
yield [
238+
<<<PHP
239+
<?php
240+
241+
/**
242+
* @param object \$data
243+
* @param resource \$resource
244+
*/
245+
return static function (mixed \$data, mixed \$resource, array \$context): void {
246+
\\fwrite(\$resource, \json_encode(\$data, \$context["json_encode_flags"] ?? 0));
247+
};
248+
249+
PHP,
250+
'object',
251+
[],
252+
];
253+
203254
yield [
204255
<<<PHP
205256
<?php

src/Symfony/Component/SerDes/Tests/Internal/Serialize/Json/SerializeTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ public static function serializeDataProvider(): iterable
5454
yield [.01];
5555
yield [false];
5656
yield [new ClassicDummy()];
57+
yield [new ClassicDummy(), 'object'];
5758
yield [DummyBackedEnum::ONE];
5859
yield [new DummyWithQuotes()];
60+
yield [['foo', true]];
5961
yield [[1, 2, 3], 'array<int, int>'];
6062
yield [[1, 2, 3.12], 'array<int, int|float>'];
6163
yield [[true, false, true], 'iterable<int, bool>'];
@@ -65,6 +67,7 @@ public static function serializeDataProvider(): iterable
6567
yield [['"a"' => '"b"'], 'array<string, string>'];
6668
yield [['a' => 1, 'b' => null], 'iterable<string, ?string>'];
6769
yield [[1, 2.12, new ClassicDummy()], sprintf('array<int, int|float|%s>', ClassicDummy::class)];
70+
yield [true, 'mixed'];
6871
}
6972

7073
public function testSerializeWithJsonEncodeFlags()

src/Symfony/Component/SerDes/Tests/Internal/Serialize/TemplateGenerator/JsonTemplateGeneratorTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,14 @@ public function testGenerateObject()
130130
new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('}')])),
131131
], $this->templateGenerator->generate(TypeFactory::createFromString(ClassicDummy::class), new VariableNode('accessor'), []));
132132
}
133+
134+
public function testGenerateMixed()
135+
{
136+
$this->assertEquals([
137+
new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('\json_encode', [
138+
new VariableNode('accessor'),
139+
new BinaryNode('??', new ArrayAccessNode(new VariableNode('context'), new ScalarNode('json_encode_flags')), new ScalarNode(0)),
140+
])])),
141+
], $this->templateGenerator->generate(TypeFactory::createFromString('mixed'), new VariableNode('accessor'), []));
142+
}
133143
}

0 commit comments

Comments
 (0)