Skip to content

Commit 1d66f68

Browse files
committed
Merge branch 'discriminator-support' into 3.x
2 parents 684f973 + 6c6ad30 commit 1d66f68

18 files changed

+630
-25
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44

55
# 3.0.0 (unreleased)
66

7+
* Update to liip/metadata-parser 2.x. Most notable changes:
8+
* Switched from annotations to attributes. Replace `LiipMetadataAnnotationParser` with
9+
`LiipMetadataAttributeParser` and change `@Preferred` annotations to `#[Preferred]` attributes.
10+
* Setting a (different) naming strategy via the `PropertyCollection::useIdenticalNamingStrategy`
11+
is no longer possible. Instead, pass the naming strategy as second argument to the constructor
12+
of the `Parser`.
13+
* For further changes in this library, take a look at the
14+
[changelog from metadata-parser](https://github.com/liip/metadata-parser/blob/2.x/CHANGELOG.md)
15+
* Add discriminator support
16+
717
# 2.x
818

919
# 2.6.2

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use Liip\MetadataParser\Builder;
3636
use Liip\MetadataParser\Parser;
3737
use Liip\MetadataParser\RecursionChecker;
3838
use Liip\MetadataParser\ModelParser\JMSParser;
39-
use Liip\MetadataParser\ModelParser\LiipMetadataAnnotationParser;
39+
use Liip\MetadataParser\ModelParser\LiipMetadataAttributeParser;
4040
use Liip\MetadataParser\ModelParser\PhpDocParser;
4141
use Liip\MetadataParser\ModelParser\ReflectionParser;
4242
use Liip\Serializer\DeserializerGenerator;
@@ -75,7 +75,7 @@ $parsers = [
7575
new ReflectionParser(),
7676
new PhpDocParser(),
7777
new JMSParser(new AnnotationReader()),
78-
new LiipMetadataAnnotationParser(new AnnotationReader()),
78+
new LiipMetadataAttributeParser(),
7979
];
8080
$builder = new Builder(new Parser($parsers), new RecursionChecker(null, []));
8181

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"require": {
1717
"php": "^8.0",
1818
"ext-json": "*",
19-
"liip/metadata-parser": "^1.2",
19+
"liip/metadata-parser": "^2.0",
2020
"pnz/json-exception": "^1.0",
2121
"symfony/filesystem": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0",
2222
"symfony/finder": "^4.4 || ^5.0 || ^6.0 || ^7.0 || ^8.0",
@@ -25,7 +25,7 @@
2525
},
2626
"require-dev": {
2727
"doctrine/collections": "^1.6",
28-
"friendsofphp/php-cs-fixer": "^3.23",
28+
"friendsofphp/php-cs-fixer": "^3.90.0",
2929
"jms/serializer": "^1.13 || ^2 || ^3",
3030
"phpstan/phpstan": "^1.0",
3131
"phpstan/phpstan-phpunit": "^1.3",

src/DeserializerGenerator.php

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
use Liip\MetadataParser\Builder;
88
use Liip\MetadataParser\Metadata\ClassMetadata;
99
use Liip\MetadataParser\Metadata\PropertyMetadata;
10-
use Liip\MetadataParser\Metadata\PropertyTypeArray;
10+
use Liip\MetadataParser\Metadata\PropertyType;
1111
use Liip\MetadataParser\Metadata\PropertyTypeClass;
1212
use Liip\MetadataParser\Metadata\PropertyTypeDateTime;
13+
use Liip\MetadataParser\Metadata\PropertyTypeIterable;
1314
use Liip\MetadataParser\Metadata\PropertyTypePrimitive;
15+
use Liip\MetadataParser\Metadata\PropertyTypeUnion;
1416
use Liip\MetadataParser\Metadata\PropertyTypeUnknown;
1517
use Liip\MetadataParser\Reducer\TakeBestReducer;
1618
use Liip\Serializer\Configuration\ClassToGenerate;
@@ -89,6 +91,11 @@ private function generateCodeForClass(
8991
ModelPath $modelPath,
9092
array $stack = [],
9193
): string {
94+
$discriminatorMetadata = $classMetadata->getDiscriminatorMetadata();
95+
if (null !== $discriminatorMetadata && $discriminatorMetadata->baseClass == $classMetadata->getClassName()) {
96+
return $this->generateCodeForDiscriminatorClass($classMetadata, $arrayPath, $modelPath, $stack);
97+
}
98+
9299
$stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1;
93100

94101
$constructorArgumentNames = [];
@@ -143,6 +150,29 @@ private function generateCodeForClass(
143150
return $this->templating->renderClass((string) $modelPath, $classMetadata->getClassName(), $constructorArguments, $code, $initCode);
144151
}
145152

153+
/**
154+
* @param array<string, positive-int> $stack
155+
*/
156+
private function generateCodeForDiscriminatorClass(
157+
ClassMetadata $classMetadata,
158+
ArrayPath $arrayPath,
159+
ModelPath $modelPath,
160+
array $stack = [],
161+
): string {
162+
$code = '';
163+
$discriminatorMetadata = $classMetadata->getDiscriminatorMetadata();
164+
$discriminatorFieldPath = $arrayPath->withFieldName($discriminatorMetadata->propertyName);
165+
foreach ($discriminatorMetadata->classMap as $typeValue => $class) {
166+
$code .= $this->templating->renderDiscriminatorConditional(
167+
(string) $discriminatorFieldPath,
168+
$typeValue,
169+
$this->generateCodeForClass($discriminatorMetadata->getMetadataForClass($class), $arrayPath, $modelPath, $stack)
170+
);
171+
}
172+
173+
return $code;
174+
}
175+
146176
/**
147177
* @param array<string, positive-int> $stack
148178
*/
@@ -204,7 +234,7 @@ private function generateInnerCodeForFieldType(
204234
$type = $propertyMetadata->getType();
205235

206236
switch ($type) {
207-
case $type instanceof PropertyTypeArray:
237+
case $type instanceof PropertyTypeIterable:
208238
if ($type->isTraversable()) {
209239
return $this->generateCodeForArrayCollection($propertyMetadata, $type, $arrayPath, $modelPropertyPath, $stack);
210240
}
@@ -229,16 +259,74 @@ private function generateInnerCodeForFieldType(
229259
case $type instanceof PropertyTypeClass:
230260
return $this->generateCodeForClass($type->getClassMetadata(), $arrayPath, $modelPropertyPath, $stack);
231261

262+
case $type instanceof PropertyTypeUnion:
263+
return $this->generateCodeForUnion($type, $arrayPath, $modelPropertyPath, $stack);
264+
232265
default:
233266
throw new \Exception('Unexpected type '.$type::class.' at '.$modelPropertyPath);
234267
}
235268
}
236269

270+
/**
271+
* @param array<string, positive-int> $stack
272+
*/
273+
private function generateCodeForUnion(
274+
PropertyTypeUnion $type,
275+
ArrayPath $arrayPath,
276+
ModelPath $modelPath,
277+
array $stack,
278+
): string {
279+
$code = '';
280+
281+
$types = $type->getTypes();
282+
$typesWithoutPrimitives = array_filter($types, static function (PropertyType $subType): bool {
283+
return !($subType instanceof PropertyTypePrimitive || $subType instanceof PropertyTypeIterable);
284+
});
285+
286+
$fieldName = $type->getFieldName();
287+
if (null !== $fieldName) {
288+
$discriminatorFieldPath = $arrayPath->withFieldName($fieldName);
289+
290+
foreach ($type->getTypeMap() as $typeValue => $class) {
291+
$classType = $type->getTypeByClassName($class);
292+
$code .= $this->templating->renderDiscriminatorConditional(
293+
(string) $discriminatorFieldPath,
294+
$typeValue,
295+
$this->generateCodeForClass($classType->getClassMetadata(), $arrayPath, $modelPath, $stack)
296+
);
297+
}
298+
299+
return $code;
300+
}
301+
302+
if (0 !== \count($typesWithoutPrimitives)) {
303+
throw new \Exception('Found union type that contains primitives and non primitives, which is currently not supported.');
304+
}
305+
306+
$amountOfTypes = \count($types);
307+
foreach ($types as $key => $subType) {
308+
$phpType = 'array';
309+
if ($subType instanceof PropertyTypePrimitive) {
310+
$phpType = $subType->getTypeName();
311+
}
312+
313+
$withElseBlock = $key !== ($amountOfTypes - 1);
314+
$code .= $this->templating->renderPrimitiveConditional(
315+
$phpType,
316+
(string) $arrayPath,
317+
$this->templating->renderAssignJsonDataToFieldWithCast($phpType, (string) $modelPath, (string) $arrayPath),
318+
$withElseBlock
319+
);
320+
}
321+
322+
return $code;
323+
}
324+
237325
/**
238326
* @param array<string, positive-int> $stack
239327
*/
240328
private function generateCodeForArray(
241-
PropertyTypeArray $type,
329+
PropertyTypeIterable $type,
242330
ArrayPath $arrayPath,
243331
ModelPath $modelPath,
244332
array $stack,
@@ -254,7 +342,7 @@ private function generateCodeForArray(
254342
$subType = $type->getSubType();
255343

256344
switch ($subType) {
257-
case $subType instanceof PropertyTypeArray:
345+
case $subType instanceof PropertyTypeIterable:
258346
$innerCode = $this->generateCodeForArray($subType, $arrayPropertyPath, $modelPropertyPath, $stack);
259347
break;
260348

@@ -284,7 +372,7 @@ private function generateCodeForArray(
284372
*/
285373
private function generateCodeForArrayCollection(
286374
PropertyMetadata $propertyMetadata,
287-
PropertyTypeArray $type,
375+
PropertyTypeIterable $type,
288376
ArrayPath $arrayPath,
289377
ModelPath $modelPath,
290378
array $stack,

src/Recursion.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
namespace Liip\Serializer;
66

77
use Liip\MetadataParser\Metadata\PropertyMetadata;
8-
use Liip\MetadataParser\Metadata\PropertyTypeArray;
98
use Liip\MetadataParser\Metadata\PropertyTypeClass;
9+
use Liip\MetadataParser\Metadata\PropertyTypeIterable;
1010

1111
abstract class Recursion
1212
{
@@ -44,7 +44,7 @@ public static function hasMaxDepthReached(PropertyMetadata $propertyMetadata, ar
4444
private static function getClassNameFromProperty(PropertyMetadata $propertyMetadata): ?string
4545
{
4646
$type = $propertyMetadata->getType();
47-
if ($type instanceof PropertyTypeArray) {
47+
if ($type instanceof PropertyTypeIterable) {
4848
$type = $type->getLeafType();
4949
}
5050

src/SerializerGenerator.php

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
use Liip\MetadataParser\Metadata\ClassMetadata;
99
use Liip\MetadataParser\Metadata\PropertyMetadata;
1010
use Liip\MetadataParser\Metadata\PropertyType;
11-
use Liip\MetadataParser\Metadata\PropertyTypeArray;
1211
use Liip\MetadataParser\Metadata\PropertyTypeClass;
1312
use Liip\MetadataParser\Metadata\PropertyTypeDateTime;
13+
use Liip\MetadataParser\Metadata\PropertyTypeIterable;
1414
use Liip\MetadataParser\Metadata\PropertyTypePrimitive;
15+
use Liip\MetadataParser\Metadata\PropertyTypeUnion;
1516
use Liip\MetadataParser\Metadata\PropertyTypeUnknown;
1617
use Liip\MetadataParser\Reducer\GroupReducer;
1718
use Liip\MetadataParser\Reducer\PreferredReducer;
@@ -120,16 +121,51 @@ private function generateCodeForClass(
120121
string $modelPath,
121122
array $stack = [],
122123
): string {
124+
$discriminatorMetadata = $classMetadata->getDiscriminatorMetadata();
125+
if (null !== $discriminatorMetadata && $discriminatorMetadata->baseClass == $classMetadata->getClassName()) {
126+
return $this->generateCodeForDiscriminatorClass($classMetadata, $apiVersion, $serializerGroups, $arrayPath, $modelPath, $stack);
127+
}
128+
123129
$stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1;
124130

125131
$code = '';
126132
foreach ($classMetadata->getProperties() as $propertyMetadata) {
127133
$code .= $this->generateCodeForField($propertyMetadata, $apiVersion, $serializerGroups, $arrayPath, $modelPath, $stack);
128134
}
129135

136+
if (null !== $discriminatorMetadata) {
137+
$discriminatorFieldPath = $arrayPath.'["'.$discriminatorMetadata->propertyName.'"]';
138+
$code .= $this->templating->renderAssign($discriminatorFieldPath, \sprintf("'%s'", $discriminatorMetadata->value));
139+
}
140+
130141
return $this->templating->renderClass($arrayPath, $code);
131142
}
132143

144+
/**
145+
* @param list<string> $serializerGroups
146+
* @param array<string, positive-int> $stack
147+
*/
148+
private function generateCodeForDiscriminatorClass(
149+
ClassMetadata $classMetadata,
150+
?string $apiVersion,
151+
array $serializerGroups,
152+
string $arrayPath,
153+
string $modelPath,
154+
array $stack = [],
155+
): string {
156+
$code = '';
157+
$discriminatorMetadata = $classMetadata->getDiscriminatorMetadata();
158+
foreach ($discriminatorMetadata->classMap as $class) {
159+
$code .= $this->templating->renderInstanceOfConditional(
160+
$modelPath,
161+
$class,
162+
$this->generateCodeForClass($discriminatorMetadata->getMetadataForClass($class), $apiVersion, $serializerGroups, $arrayPath, $modelPath, $stack)
163+
);
164+
}
165+
166+
return $code;
167+
}
168+
133169
/**
134170
* @param list<string> $serializerGroups
135171
* @param array<string, positive-int> $stack
@@ -196,9 +232,12 @@ private function generateCodeForFieldType(
196232
case $type instanceof PropertyTypeClass:
197233
return $this->generateCodeForClass($type->getClassMetadata(), $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack);
198234

199-
case $type instanceof PropertyTypeArray:
235+
case $type instanceof PropertyTypeIterable:
200236
return $this->generateCodeForArray($type, $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack);
201237

238+
case $type instanceof PropertyTypeUnion:
239+
return $this->generateCodeForUnion($type, $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack);
240+
202241
default:
203242
throw new \Exception('Unexpected type '.$type::class.' at '.$modelPropertyPath);
204243
}
@@ -209,7 +248,7 @@ private function generateCodeForFieldType(
209248
* @param array<string, positive-int> $stack
210249
*/
211250
private function generateCodeForArray(
212-
PropertyTypeArray $type,
251+
PropertyTypeIterable $type,
213252
?string $apiVersion,
214253
array $serializerGroups,
215254
string $arrayPath,
@@ -221,11 +260,11 @@ private function generateCodeForArray(
221260

222261
switch ($subType) {
223262
case $subType instanceof PropertyTypePrimitive:
224-
case $subType instanceof PropertyTypeArray && self::isArrayForPrimitive($subType):
263+
case $subType instanceof PropertyTypeIterable && self::isArrayForPrimitive($subType):
225264
case $subType instanceof PropertyTypeUnknown && $this->configuration->shouldAllowGenericArrays():
226265
return $this->templating->renderArrayAssign($arrayPath, $modelPath);
227266

228-
case $subType instanceof PropertyTypeArray:
267+
case $subType instanceof PropertyTypeIterable:
229268
$innerCode = $this->generateCodeForArray($subType, $apiVersion, $serializerGroups, $arrayPath.'['.$index.']', $modelPath.'['.$index.']', $stack);
230269
break;
231270

@@ -252,14 +291,62 @@ private function generateCodeForArray(
252291
return $this->templating->renderLoopArray($arrayPath, $modelPath, $index, $innerCode);
253292
}
254293

255-
private static function isArrayForPrimitive(PropertyTypeArray $type): bool
294+
/**
295+
* @param list<string> $serializerGroups
296+
* @param array<string, positive-int> $stack
297+
*/
298+
private function generateCodeForUnion(
299+
PropertyTypeUnion $subType,
300+
?string $apiVersion,
301+
array $serializerGroups,
302+
string $arrayPath,
303+
string $modelPath,
304+
array $stack,
305+
): string {
306+
$code = '';
307+
308+
$types = $subType->getTypes();
309+
$typesWithoutPrimitives = array_filter($types, static function (PropertyType $subType): bool {
310+
return !($subType instanceof PropertyTypePrimitive || $subType instanceof PropertyTypeUnknown);
311+
});
312+
313+
$hasPrimitives = \count($types) !== \count($typesWithoutPrimitives);
314+
if ($hasPrimitives) {
315+
$code .= $this->templating->renderPrimitiveConditional(
316+
$modelPath,
317+
$this->templating->renderAssign($arrayPath, $modelPath)
318+
);
319+
}
320+
321+
foreach ($typesWithoutPrimitives as $subType) {
322+
switch ($subType::class) {
323+
case PropertyTypeClass::class:
324+
$code .= $this->templating->renderInstanceOfConditional(
325+
$modelPath,
326+
$subType->getClassName(),
327+
$this->generateCodeForFieldType($subType, $apiVersion, $serializerGroups, $arrayPath, $modelPath, $stack)
328+
);
329+
break;
330+
case PropertyTypeIterable::class:
331+
$code .= $this->templating->renderArrayConditional(
332+
$modelPath,
333+
$this->generateCodeForArray($subType, $apiVersion, $serializerGroups, $arrayPath, $modelPath, $stack)
334+
);
335+
break;
336+
}
337+
}
338+
339+
return $code;
340+
}
341+
342+
private static function isArrayForPrimitive(PropertyTypeIterable $type): bool
256343
{
257344
do {
258345
$type = $type->getSubType();
259346
if ($type instanceof PropertyTypePrimitive) {
260347
return true;
261348
}
262-
} while ($type instanceof PropertyTypeArray);
349+
} while ($type instanceof PropertyTypeIterable);
263350

264351
return false;
265352
}

0 commit comments

Comments
 (0)