vendor/api-platform/core/src/JsonLd/Serializer/ItemNormalizer.php line 66

  1. <?php
  2. /*
  3.  * This file is part of the API Platform project.
  4.  *
  5.  * (c) Kévin Dunglas <dunglas@gmail.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. declare(strict_types=1);
  11. namespace ApiPlatform\JsonLd\Serializer;
  12. use ApiPlatform\Api\IriConverterInterface;
  13. use ApiPlatform\Api\ResourceClassResolverInterface;
  14. use ApiPlatform\Api\UrlGeneratorInterface;
  15. use ApiPlatform\JsonLd\AnonymousContextBuilderInterface;
  16. use ApiPlatform\JsonLd\ContextBuilderInterface;
  17. use ApiPlatform\Metadata\HttpOperation;
  18. use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
  19. use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
  20. use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
  21. use ApiPlatform\Metadata\Util\ClassInfoTrait;
  22. use ApiPlatform\Serializer\AbstractItemNormalizer;
  23. use ApiPlatform\Serializer\ContextTrait;
  24. use ApiPlatform\Symfony\Security\ResourceAccessCheckerInterface;
  25. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  26. use Symfony\Component\Serializer\Exception\LogicException;
  27. use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
  28. use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
  29. use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
  30. /**
  31.  * Converts between objects and array including JSON-LD and Hydra metadata.
  32.  *
  33.  * @author Kévin Dunglas <dunglas@gmail.com>
  34.  */
  35. final class ItemNormalizer extends AbstractItemNormalizer
  36. {
  37.     use ClassInfoTrait;
  38.     use ContextTrait;
  39.     use JsonLdContextTrait;
  40.     public const FORMAT 'jsonld';
  41.     public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactoryPropertyNameCollectionFactoryInterface $propertyNameCollectionFactoryPropertyMetadataFactoryInterface $propertyMetadataFactoryIriConverterInterface $iriConverterResourceClassResolverInterface $resourceClassResolver, private readonly ContextBuilderInterface $contextBuilderPropertyAccessorInterface $propertyAccessor nullNameConverterInterface $nameConverter nullClassMetadataFactoryInterface $classMetadataFactory null, array $defaultContext = [], ResourceAccessCheckerInterface $resourceAccessChecker null)
  42.     {
  43.         parent::__construct($propertyNameCollectionFactory$propertyMetadataFactory$iriConverter$resourceClassResolver$propertyAccessor$nameConverter$classMetadataFactory$defaultContext$resourceMetadataCollectionFactory$resourceAccessChecker);
  44.     }
  45.     /**
  46.      * {@inheritdoc}
  47.      */
  48.     public function supportsNormalization(mixed $datastring $format null, array $context = []): bool
  49.     {
  50.         return self::FORMAT === $format && parent::supportsNormalization($data$format$context);
  51.     }
  52.     /**
  53.      * {@inheritdoc}
  54.      *
  55.      * @throws LogicException
  56.      */
  57.     public function normalize(mixed $objectstring $format null, array $context = []): array|string|int|float|bool|\ArrayObject|null
  58.     {
  59.         $resourceClass $this->getObjectClass($object);
  60.         if ($this->getOutputClass($context)) {
  61.             return parent::normalize($object$format$context);
  62.         }
  63.         // TODO: we should not remove the resource_class in the normalizeRawCollection as we would find out anyway that it's not the same as the requested one
  64.         $previousResourceClass $context['resource_class'] ?? null;
  65.         $metadata = [];
  66.         if ($isResourceClass $this->resourceClassResolver->isResourceClass($resourceClass)) {
  67.             $resourceClass $this->resourceClassResolver->getResourceClass($object$context['resource_class'] ?? null);
  68.             $context $this->initContext($resourceClass$context);
  69.             $metadata $this->addJsonLdContext($this->contextBuilder$resourceClass$context);
  70.         } elseif ($this->contextBuilder instanceof AnonymousContextBuilderInterface) {
  71.             if ($context['api_collection_sub_level'] ?? false) {
  72.                 unset($context['api_collection_sub_level']);
  73.                 $context['output']['genid'] = true;
  74.                 $context['output']['iri'] = null;
  75.             }
  76.             // We should improve what's behind the context creation, its probably more complicated then it should
  77.             $metadata $this->createJsonLdContext($this->contextBuilder$object$context);
  78.         }
  79.         // maybe not needed anymore
  80.         if (isset($context['operation']) && $previousResourceClass !== $resourceClass) {
  81.             unset($context['operation'], $context['operation_name']);
  82.         }
  83.         if (($operation $context['operation'] ?? null) && method_exists($operation'getItemUriTemplate') && ($itemUriTemplate $operation->getItemUriTemplate())) {
  84.             $context['item_uri_template'] = $itemUriTemplate;
  85.         }
  86.         if ($iri $this->iriConverter->getIriFromResource($objectUrlGeneratorInterface::ABS_PATH$context['operation'] ?? null$context)) {
  87.             $context['iri'] = $iri;
  88.             $metadata['@id'] = $iri;
  89.         }
  90.         $context['api_normalize'] = true;
  91.         $data parent::normalize($object$format$context);
  92.         if (!\is_array($data)) {
  93.             return $data;
  94.         }
  95.         if (!isset($metadata['@type']) && $isResourceClass) {
  96.             $operation $context['operation'] ?? $this->resourceMetadataCollectionFactory->create($resourceClass)->getOperation();
  97.             $types $operation instanceof HttpOperation $operation->getTypes() : null;
  98.             if (null === $types) {
  99.                 $types = [$operation->getShortName()];
  100.             }
  101.             $metadata['@type'] = === \count($types) ? $types[0] : $types;
  102.         }
  103.         return $metadata $data;
  104.     }
  105.     /**
  106.      * {@inheritdoc}
  107.      */
  108.     public function supportsDenormalization(mixed $datastring $typestring $format null, array $context = []): bool
  109.     {
  110.         return self::FORMAT === $format && parent::supportsDenormalization($data$type$format$context);
  111.     }
  112.     /**
  113.      * {@inheritdoc}
  114.      *
  115.      * @throws NotNormalizableValueException
  116.      */
  117.     public function denormalize(mixed $datastring $classstring $format null, array $context = []): mixed
  118.     {
  119.         // Avoid issues with proxies if we populated the object
  120.         if (isset($data['@id']) && !isset($context[self::OBJECT_TO_POPULATE])) {
  121.             if (true !== ($context['api_allow_update'] ?? true)) {
  122.                 throw new NotNormalizableValueException('Update is not allowed for this operation.');
  123.             }
  124.             $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getResourceFromIri($data['@id'], $context + ['fetch_data' => true]);
  125.         }
  126.         return parent::denormalize($data$class$format$context);
  127.     }
  128. }