|
29 | 29 | use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
|
30 | 30 | use ApiPlatform\Metadata\ResourceClassResolverInterface;
|
31 | 31 | use ApiPlatform\Metadata\UrlGeneratorInterface;
|
32 |
| -use Symfony\Component\PropertyInfo\Type; |
| 32 | +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; |
| 33 | +use Symfony\Component\PropertyInfo\Type as LegacyType; |
33 | 34 | use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
|
34 | 35 | use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
35 | 36 | use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
| 37 | +use Symfony\Component\TypeInfo\Type; |
| 38 | +use Symfony\Component\TypeInfo\Type\CollectionType; |
| 39 | +use Symfony\Component\TypeInfo\Type\CompositeTypeInterface; |
| 40 | +use Symfony\Component\TypeInfo\Type\ObjectType; |
| 41 | +use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; |
| 42 | +use Symfony\Component\TypeInfo\TypeIdentifier; |
36 | 43 |
|
37 | 44 | use const ApiPlatform\JsonLd\HYDRA_CONTEXT;
|
38 | 45 |
|
@@ -356,73 +363,171 @@ private function getRange(ApiProperty $propertyMetadata): array|string|null
|
356 | 363 | return $jsonldContext['@type'];
|
357 | 364 | }
|
358 | 365 |
|
359 |
| - $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? []; |
360 | 366 | $types = [];
|
361 | 367 |
|
362 |
| - foreach ($builtInTypes as $type) { |
363 |
| - if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) { |
364 |
| - $type = $collectionType; |
| 368 | + if (method_exists(PropertyInfoExtractor::class, 'getType')) { |
| 369 | + $nativeType = $propertyMetadata->getNativeType(); |
| 370 | + if (null === $nativeType) { |
| 371 | + return null; |
365 | 372 | }
|
366 | 373 |
|
367 |
| - switch ($type->getBuiltinType()) { |
368 |
| - case Type::BUILTIN_TYPE_STRING: |
369 |
| - if (!\in_array('xmls:string', $types, true)) { |
370 |
| - $types[] = 'xmls:string'; |
371 |
| - } |
372 |
| - break; |
373 |
| - case Type::BUILTIN_TYPE_INT: |
374 |
| - if (!\in_array('xmls:integer', $types, true)) { |
375 |
| - $types[] = 'xmls:integer'; |
376 |
| - } |
377 |
| - break; |
378 |
| - case Type::BUILTIN_TYPE_FLOAT: |
379 |
| - if (!\in_array('xmls:decimal', $types, true)) { |
380 |
| - $types[] = 'xmls:decimal'; |
381 |
| - } |
382 |
| - break; |
383 |
| - case Type::BUILTIN_TYPE_BOOL: |
384 |
| - if (!\in_array('xmls:boolean', $types, true)) { |
385 |
| - $types[] = 'xmls:boolean'; |
386 |
| - } |
387 |
| - break; |
388 |
| - case Type::BUILTIN_TYPE_OBJECT: |
389 |
| - if (null === $className = $type->getClassName()) { |
390 |
| - continue 2; |
| 374 | + /** @var Type|null $collectionValueType */ |
| 375 | + $collectionValueType = null; |
| 376 | + $typeIsCollection = static function (Type $type) use (&$typeIsCollection, &$collectionValueType): bool { |
| 377 | + return match (true) { |
| 378 | + $type instanceof CollectionType => null !== $collectionValueType = $type->getCollectionValueType(), |
| 379 | + $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($typeIsCollection), |
| 380 | + $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($typeIsCollection), |
| 381 | + default => false, |
| 382 | + }; |
| 383 | + }; |
| 384 | + |
| 385 | + if ($nativeType->isSatisfiedBy($typeIsCollection)) { |
| 386 | + $nativeType = $collectionValueType; |
| 387 | + } |
| 388 | + |
| 389 | + // Check for specific types after potentially unwrapping the collection |
| 390 | + if (null === $nativeType) { |
| 391 | + return null; // Should not happen if collection had a value type, but safety check |
| 392 | + } |
| 393 | + |
| 394 | + if ($nativeType->isIdentifiedBy(TypeIdentifier::STRING)) { |
| 395 | + $types[] = 'xmls:string'; |
| 396 | + } |
| 397 | + |
| 398 | + if ($nativeType->isIdentifiedBy(TypeIdentifier::INT)) { |
| 399 | + $types[] = 'xmls:integer'; |
| 400 | + } |
| 401 | + |
| 402 | + if ($nativeType->isIdentifiedBy(TypeIdentifier::FLOAT)) { |
| 403 | + $types[] = 'xmls:decimal'; |
| 404 | + } |
| 405 | + |
| 406 | + if ($nativeType->isIdentifiedBy(TypeIdentifier::BOOL)) { |
| 407 | + $types[] = 'xmls:boolean'; |
| 408 | + } |
| 409 | + |
| 410 | + if ($nativeType->isIdentifiedBy(\DateTimeInterface::class)) { |
| 411 | + $types[] = 'xmls:dateTime'; |
| 412 | + } |
| 413 | + |
| 414 | + /** @var class-string|null $className */ |
| 415 | + $className = null; |
| 416 | + |
| 417 | + $typeIsResourceClass = function (Type $type) use (&$typeIsResourceClass, &$className): bool { |
| 418 | + return match (true) { |
| 419 | + $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($typeIsResourceClass), |
| 420 | + $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($typeIsResourceClass), |
| 421 | + default => $type instanceof ObjectType && $this->resourceClassResolver->isResourceClass($className = $type->getClassName()), |
| 422 | + }; |
| 423 | + }; |
| 424 | + |
| 425 | + if ($nativeType->isSatisfiedBy($typeIsResourceClass) && $className) { |
| 426 | + $resourceMetadata = $this->resourceMetadataFactory->create($className); |
| 427 | + $operation = $resourceMetadata->getOperation(); |
| 428 | + |
| 429 | + if (!$operation instanceof HttpOperation || !$operation->getTypes()) { |
| 430 | + if (!\in_array("#{$operation->getShortName()}", $types, true)) { |
| 431 | + $types[] = "#{$operation->getShortName()}"; |
391 | 432 | }
|
| 433 | + } else { |
| 434 | + $types = array_unique(array_merge($types, $operation->getTypes())); |
| 435 | + } |
| 436 | + } |
| 437 | + // TODO: remove in 5.x |
| 438 | + } else { |
| 439 | + $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? []; |
| 440 | + |
| 441 | + foreach ($builtInTypes as $type) { |
| 442 | + if ($type->isCollection() && null !== $collectionType = $type->getCollectionValueTypes()[0] ?? null) { |
| 443 | + $type = $collectionType; |
| 444 | + } |
392 | 445 |
|
393 |
| - if (is_a($className, \DateTimeInterface::class, true)) { |
394 |
| - if (!\in_array('xmls:dateTime', $types, true)) { |
395 |
| - $types[] = 'xmls:dateTime'; |
| 446 | + switch ($type->getBuiltinType()) { |
| 447 | + case LegacyType::BUILTIN_TYPE_STRING: |
| 448 | + if (!\in_array('xmls:string', $types, true)) { |
| 449 | + $types[] = 'xmls:string'; |
396 | 450 | }
|
397 | 451 | break;
|
398 |
| - } |
399 |
| - |
400 |
| - if ($this->resourceClassResolver->isResourceClass($className)) { |
401 |
| - $resourceMetadata = $this->resourceMetadataFactory->create($className); |
402 |
| - $operation = $resourceMetadata->getOperation(); |
| 452 | + case LegacyType::BUILTIN_TYPE_INT: |
| 453 | + if (!\in_array('xmls:integer', $types, true)) { |
| 454 | + $types[] = 'xmls:integer'; |
| 455 | + } |
| 456 | + break; |
| 457 | + case LegacyType::BUILTIN_TYPE_FLOAT: |
| 458 | + if (!\in_array('xmls:decimal', $types, true)) { |
| 459 | + $types[] = 'xmls:decimal'; |
| 460 | + } |
| 461 | + break; |
| 462 | + case LegacyType::BUILTIN_TYPE_BOOL: |
| 463 | + if (!\in_array('xmls:boolean', $types, true)) { |
| 464 | + $types[] = 'xmls:boolean'; |
| 465 | + } |
| 466 | + break; |
| 467 | + case LegacyType::BUILTIN_TYPE_OBJECT: |
| 468 | + if (null === $className = $type->getClassName()) { |
| 469 | + continue 2; |
| 470 | + } |
403 | 471 |
|
404 |
| - if (!$operation instanceof HttpOperation || !$operation->getTypes()) { |
405 |
| - if (!\in_array("#{$operation->getShortName()}", $types, true)) { |
406 |
| - $types[] = "#{$operation->getShortName()}"; |
| 472 | + if (is_a($className, \DateTimeInterface::class, true)) { |
| 473 | + if (!\in_array('xmls:dateTime', $types, true)) { |
| 474 | + $types[] = 'xmls:dateTime'; |
407 | 475 | }
|
408 | 476 | break;
|
409 | 477 | }
|
410 | 478 |
|
411 |
| - $types = array_unique(array_merge($types, $operation->getTypes())); |
412 |
| - break; |
413 |
| - } |
| 479 | + if ($this->resourceClassResolver->isResourceClass($className)) { |
| 480 | + $resourceMetadata = $this->resourceMetadataFactory->create($className); |
| 481 | + $operation = $resourceMetadata->getOperation(); |
| 482 | + |
| 483 | + if (!$operation instanceof HttpOperation || !$operation->getTypes()) { |
| 484 | + if (!\in_array("#{$operation->getShortName()}", $types, true)) { |
| 485 | + $types[] = "#{$operation->getShortName()}"; |
| 486 | + } |
| 487 | + break; |
| 488 | + } |
| 489 | + |
| 490 | + $types = array_unique(array_merge($types, $operation->getTypes())); |
| 491 | + break; |
| 492 | + } |
| 493 | + } |
414 | 494 | }
|
415 | 495 | }
|
416 | 496 |
|
417 | 497 | if ([] === $types) {
|
418 | 498 | return null;
|
419 | 499 | }
|
420 | 500 |
|
| 501 | + $types = array_unique($types); |
| 502 | + |
421 | 503 | return 1 === \count($types) ? $types[0] : $types;
|
422 | 504 | }
|
423 | 505 |
|
424 | 506 | private function isSingleRelation(ApiProperty $propertyMetadata): bool
|
425 | 507 | {
|
| 508 | + if (method_exists(PropertyInfoExtractor::class, 'getType')) { |
| 509 | + $nativeType = $propertyMetadata->getNativeType(); |
| 510 | + if (null === $nativeType) { |
| 511 | + return false; |
| 512 | + } |
| 513 | + |
| 514 | + if ($nativeType instanceof CollectionType) { |
| 515 | + return false; |
| 516 | + } |
| 517 | + |
| 518 | + $typeIsResourceClass = function (Type $type) use (&$typeIsResourceClass): bool { |
| 519 | + return match (true) { |
| 520 | + $type instanceof CollectionType => false, |
| 521 | + $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($typeIsResourceClass), |
| 522 | + $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($typeIsResourceClass), |
| 523 | + default => $type instanceof ObjectType && $this->resourceClassResolver->isResourceClass($type->getClassName()), |
| 524 | + }; |
| 525 | + }; |
| 526 | + |
| 527 | + return $nativeType->isSatisfiedBy($typeIsResourceClass); |
| 528 | + } |
| 529 | + |
| 530 | + // TODO: remove in 5.x |
426 | 531 | $builtInTypes = $propertyMetadata->getBuiltinTypes() ?? [];
|
427 | 532 |
|
428 | 533 | foreach ($builtInTypes as $type) {
|
|
0 commit comments