|
29 | 29 | use ApiPlatform\Serializer\TagCollectorInterface;
|
30 | 30 | use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
31 | 31 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
| 32 | +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; |
32 | 33 | use Symfony\Component\Serializer\Exception\LogicException;
|
33 | 34 | use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
|
34 | 35 | use Symfony\Component\Serializer\Exception\RuntimeException;
|
35 | 36 | use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
36 | 37 | use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
|
37 | 38 | use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
|
38 | 39 | use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
| 40 | +use Symfony\Component\TypeInfo\Type; |
| 41 | +use Symfony\Component\TypeInfo\Type\CollectionType; |
| 42 | +use Symfony\Component\TypeInfo\Type\CompositeTypeInterface; |
| 43 | +use Symfony\Component\TypeInfo\Type\ObjectType; |
| 44 | +use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; |
39 | 45 |
|
40 | 46 | /**
|
41 | 47 | * Converts between objects and array.
|
@@ -319,50 +325,115 @@ private function getComponents(object $object, ?string $format, array $context):
|
319 | 325 | ->propertyMetadataFactory
|
320 | 326 | ->create($context['resource_class'], $attribute, $options);
|
321 | 327 |
|
322 |
| - $types = $propertyMetadata->getBuiltinTypes() ?? []; |
323 |
| - |
324 | 328 | // prevent declaring $attribute as attribute if it's already declared as relationship
|
325 | 329 | $isRelationship = false;
|
326 | 330 |
|
327 |
| - foreach ($types as $type) { |
328 |
| - $isOne = $isMany = false; |
| 331 | + if (!method_exists(PropertyInfoExtractor::class, 'getType')) { |
| 332 | + $types = $propertyMetadata->getBuiltinTypes() ?? []; |
329 | 333 |
|
330 |
| - if ($type->isCollection()) { |
331 |
| - $collectionValueType = $type->getCollectionValueTypes()[0] ?? null; |
332 |
| - $isMany = $collectionValueType && ($className = $collectionValueType->getClassName()) && $this->resourceClassResolver->isResourceClass($className); |
333 |
| - } else { |
334 |
| - $isOne = ($className = $type->getClassName()) && $this->resourceClassResolver->isResourceClass($className); |
335 |
| - } |
| 334 | + foreach ($types as $type) { |
| 335 | + $isOne = $isMany = false; |
336 | 336 |
|
337 |
| - if (!isset($className) || !$isOne && !$isMany) { |
338 |
| - // don't declare it as an attribute too quick: maybe the next type is a valid resource |
339 |
| - continue; |
340 |
| - } |
| 337 | + if ($type->isCollection()) { |
| 338 | + $collectionValueType = $type->getCollectionValueTypes()[0] ?? null; |
| 339 | + $isMany = $collectionValueType && ($className = $collectionValueType->getClassName()) && $this->resourceClassResolver->isResourceClass($className); |
| 340 | + } else { |
| 341 | + $isOne = ($className = $type->getClassName()) && $this->resourceClassResolver->isResourceClass($className); |
| 342 | + } |
| 343 | + |
| 344 | + if (!isset($className) || !$isOne && !$isMany) { |
| 345 | + // don't declare it as an attribute too quick: maybe the next type is a valid resource |
| 346 | + continue; |
| 347 | + } |
| 348 | + |
| 349 | + $relation = [ |
| 350 | + 'name' => $attribute, |
| 351 | + 'type' => $this->getResourceShortName($className), |
| 352 | + 'cardinality' => $isOne ? 'one' : 'many', |
| 353 | + ]; |
| 354 | + |
| 355 | + // if we specify the uriTemplate, generates its value for link definition |
| 356 | + // @see ApiPlatform\Serializer\AbstractItemNormalizer:getAttributeValue logic for intentional duplicate content |
| 357 | + if ($itemUriTemplate = $propertyMetadata->getUriTemplate()) { |
| 358 | + $attributeValue = $this->propertyAccessor->getValue($object, $attribute); |
| 359 | + $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); |
| 360 | + $childContext = $this->createChildContext($context, $attribute, $format); |
| 361 | + unset($childContext['iri'], $childContext['uri_variables'], $childContext['resource_class'], $childContext['operation']); |
| 362 | + |
| 363 | + $operation = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation( |
| 364 | + operationName: $itemUriTemplate, |
| 365 | + httpOperation: true |
| 366 | + ); |
| 367 | + |
| 368 | + $components['links'][$attribute] = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $operation, $childContext); |
| 369 | + } |
341 | 370 |
|
342 |
| - $relation = [ |
343 |
| - 'name' => $attribute, |
344 |
| - 'type' => $this->getResourceShortName($className), |
345 |
| - 'cardinality' => $isOne ? 'one' : 'many', |
346 |
| - ]; |
347 |
| - |
348 |
| - // if we specify the uriTemplate, generates its value for link definition |
349 |
| - // @see ApiPlatform\Serializer\AbstractItemNormalizer:getAttributeValue logic for intentional duplicate content |
350 |
| - if ($itemUriTemplate = $propertyMetadata->getUriTemplate()) { |
351 |
| - $attributeValue = $this->propertyAccessor->getValue($object, $attribute); |
352 |
| - $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); |
353 |
| - $childContext = $this->createChildContext($context, $attribute, $format); |
354 |
| - unset($childContext['iri'], $childContext['uri_variables'], $childContext['resource_class'], $childContext['operation']); |
355 |
| - |
356 |
| - $operation = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation( |
357 |
| - operationName: $itemUriTemplate, |
358 |
| - httpOperation: true |
359 |
| - ); |
360 |
| - |
361 |
| - $components['links'][$attribute] = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $operation, $childContext); |
| 371 | + $components['relationships'][] = $relation; |
| 372 | + $isRelationship = true; |
362 | 373 | }
|
| 374 | + } else { |
| 375 | + if ($type = $propertyMetadata->getNativeType()) { |
| 376 | + /** @var class-string|null $className */ |
| 377 | + $className = null; |
| 378 | + |
| 379 | + $typeIsResourceClass = function (Type $type) use (&$typeIsResourceClass, &$className): bool { |
| 380 | + return match (true) { |
| 381 | + $type instanceof ObjectType => $this->resourceClassResolver->isResourceClass($className = $type->getClassName()), |
| 382 | + $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($typeIsResourceClass), |
| 383 | + $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($typeIsResourceClass), |
| 384 | + default => false, |
| 385 | + }; |
| 386 | + }; |
| 387 | + |
| 388 | + $collectionValueIsResourceClass = function (Type $type) use ($typeIsResourceClass, &$collectionValueIsResourceClass): bool { |
| 389 | + return match (true) { |
| 390 | + $type instanceof CollectionType => $type->getCollectionValueType()->isSatisfiedBy($typeIsResourceClass), |
| 391 | + $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($collectionValueIsResourceClass), |
| 392 | + $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($collectionValueIsResourceClass), |
| 393 | + default => false, |
| 394 | + }; |
| 395 | + }; |
| 396 | + |
| 397 | + foreach ($type instanceof CompositeTypeInterface ? $type->getTypes() : [$type] as $t) { |
| 398 | + $isOne = $isMany = false; |
| 399 | + |
| 400 | + if ($t->isSatisfiedBy($collectionValueIsResourceClass)) { |
| 401 | + $isMany = true; |
| 402 | + } elseif ($t->isSatisfiedBy($typeIsResourceClass)) { |
| 403 | + $isOne = true; |
| 404 | + } |
| 405 | + |
| 406 | + if (!$className || (!$isOne && !$isMany)) { |
| 407 | + // don't declare it as an attribute too quick: maybe the next type is a valid resource |
| 408 | + continue; |
| 409 | + } |
363 | 410 |
|
364 |
| - $components['relationships'][] = $relation; |
365 |
| - $isRelationship = true; |
| 411 | + $relation = [ |
| 412 | + 'name' => $attribute, |
| 413 | + 'type' => $this->getResourceShortName($className), |
| 414 | + 'cardinality' => $isOne ? 'one' : 'many', |
| 415 | + ]; |
| 416 | + |
| 417 | + // if we specify the uriTemplate, generates its value for link definition |
| 418 | + // @see ApiPlatform\Serializer\AbstractItemNormalizer:getAttributeValue logic for intentional duplicate content |
| 419 | + if ($itemUriTemplate = $propertyMetadata->getUriTemplate()) { |
| 420 | + $attributeValue = $this->propertyAccessor->getValue($object, $attribute); |
| 421 | + $resourceClass = $this->resourceClassResolver->getResourceClass($attributeValue, $className); |
| 422 | + $childContext = $this->createChildContext($context, $attribute, $format); |
| 423 | + unset($childContext['iri'], $childContext['uri_variables'], $childContext['resource_class'], $childContext['operation']); |
| 424 | + |
| 425 | + $operation = $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation( |
| 426 | + operationName: $itemUriTemplate, |
| 427 | + httpOperation: true |
| 428 | + ); |
| 429 | + |
| 430 | + $components['links'][$attribute] = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $operation, $childContext); |
| 431 | + } |
| 432 | + |
| 433 | + $components['relationships'][] = $relation; |
| 434 | + $isRelationship = true; |
| 435 | + } |
| 436 | + } |
366 | 437 | }
|
367 | 438 |
|
368 | 439 | // if all types are not relationships, declare it as an attribute
|
|
0 commit comments