vendor/api-platform/core/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php line 49

Open in your IDE?
  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\Core\Metadata\Property\Factory;
  12. use ApiPlatform\Core\Api\ResourceClassResolverInterface;
  13. use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
  14. use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
  15. use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
  16. use ApiPlatform\Core\Util\ResourceClassInfoTrait;
  17. use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface as SerializerClassMetadataFactoryInterface;
  18. /**
  19.  * Populates read/write and link status using serialization groups.
  20.  *
  21.  * @author Kévin Dunglas <dunglas@gmail.com>
  22.  * @author Teoh Han Hui <teohhanhui@gmail.com>
  23.  */
  24. final class SerializerPropertyMetadataFactory implements PropertyMetadataFactoryInterface
  25. {
  26.     use ResourceClassInfoTrait;
  27.     private $serializerClassMetadataFactory;
  28.     private $decorated;
  29.     public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactorySerializerClassMetadataFactoryInterface $serializerClassMetadataFactoryPropertyMetadataFactoryInterface $decoratedResourceClassResolverInterface $resourceClassResolver null)
  30.     {
  31.         $this->resourceMetadataFactory $resourceMetadataFactory;
  32.         $this->serializerClassMetadataFactory $serializerClassMetadataFactory;
  33.         $this->decorated $decorated;
  34.         $this->resourceClassResolver $resourceClassResolver;
  35.     }
  36.     /**
  37.      * {@inheritdoc}
  38.      */
  39.     public function create(string $resourceClassstring $property, array $options = []): PropertyMetadata
  40.     {
  41.         $propertyMetadata $this->decorated->create($resourceClass$property$options);
  42.         // in case of a property inherited (in a child class), we need it's properties
  43.         // to be mapped against serialization groups instead of the parent ones.
  44.         if (null !== ($childResourceClass $propertyMetadata->getChildInherited())) {
  45.             $resourceClass $childResourceClass;
  46.         }
  47.         try {
  48.             [$normalizationGroups$denormalizationGroups] = $this->getEffectiveSerializerGroups($options$resourceClass);
  49.         } catch (ResourceClassNotFoundException $e) {
  50.             // TODO: for input/output classes, the serializer groups must be read from the actual resource class
  51.             return $propertyMetadata;
  52.         }
  53.         $propertyMetadata $this->transformReadWrite($propertyMetadata$resourceClass$property$normalizationGroups$denormalizationGroups);
  54.         return $this->transformLinkStatus($propertyMetadata$normalizationGroups$denormalizationGroups);
  55.     }
  56.     /**
  57.      * Sets readable/writable based on matching normalization/denormalization groups.
  58.      *
  59.      * A false value is never reset as it could be unreadable/unwritable for other reasons.
  60.      * If normalization/denormalization groups are not specified, the property is implicitly readable/writable.
  61.      *
  62.      * @param string[]|null $normalizationGroups
  63.      * @param string[]|null $denormalizationGroups
  64.      */
  65.     private function transformReadWrite(PropertyMetadata $propertyMetadatastring $resourceClassstring $propertyName, array $normalizationGroups null, array $denormalizationGroups null): PropertyMetadata
  66.     {
  67.         $groups $this->getPropertySerializerGroups($resourceClass$propertyName);
  68.         if (false !== $propertyMetadata->isReadable()) {
  69.             $propertyMetadata $propertyMetadata->withReadable(null === $normalizationGroups || !empty(array_intersect($normalizationGroups$groups)));
  70.         }
  71.         if (false !== $propertyMetadata->isWritable()) {
  72.             $propertyMetadata $propertyMetadata->withWritable(null === $denormalizationGroups || !empty(array_intersect($denormalizationGroups$groups)));
  73.         }
  74.         return $propertyMetadata;
  75.     }
  76.     /**
  77.      * Sets readableLink/writableLink based on matching normalization/denormalization groups.
  78.      *
  79.      * If normalization/denormalization groups are not specified,
  80.      * set link status to false since embedding of resource must be explicitly enabled
  81.      *
  82.      * @param string[]|null $normalizationGroups
  83.      * @param string[]|null $denormalizationGroups
  84.      */
  85.     private function transformLinkStatus(PropertyMetadata $propertyMetadata, array $normalizationGroups null, array $denormalizationGroups null): PropertyMetadata
  86.     {
  87.         // No need to check link status if property is not readable and not writable
  88.         if (false === $propertyMetadata->isReadable() && false === $propertyMetadata->isWritable()) {
  89.             return $propertyMetadata;
  90.         }
  91.         $type $propertyMetadata->getType();
  92.         if (null === $type) {
  93.             return $propertyMetadata;
  94.         }
  95.         $relatedClass $type->isCollection() && ($collectionValueType $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName();
  96.         // if property is not a resource relation, don't set link status (as it would have no meaning)
  97.         if (null === $relatedClass || !$this->isResourceClass($relatedClass)) {
  98.             return $propertyMetadata;
  99.         }
  100.         // find the resource class
  101.         // this prevents serializer groups on non-resource child class from incorrectly influencing the decision
  102.         if (null !== $this->resourceClassResolver) {
  103.             $relatedClass $this->resourceClassResolver->getResourceClass(null$relatedClass);
  104.         }
  105.         $relatedGroups $this->getClassSerializerGroups($relatedClass);
  106.         if (null === $propertyMetadata->isReadableLink()) {
  107.             $propertyMetadata $propertyMetadata->withReadableLink(null !== $normalizationGroups && !empty(array_intersect($normalizationGroups$relatedGroups)));
  108.         }
  109.         if (null === $propertyMetadata->isWritableLink()) {
  110.             $propertyMetadata $propertyMetadata->withWritableLink(null !== $denormalizationGroups && !empty(array_intersect($denormalizationGroups$relatedGroups)));
  111.         }
  112.         return $propertyMetadata;
  113.     }
  114.     /**
  115.      * Gets the effective serializer groups used in normalization/denormalization.
  116.      *
  117.      * Groups are extracted in the following order:
  118.      *
  119.      * - From the "serializer_groups" key of the $options array.
  120.      * - From metadata of the given operation ("collection_operation_name" and "item_operation_name" keys).
  121.      * - From metadata of the current resource.
  122.      *
  123.      * @throws ResourceClassNotFoundException
  124.      *
  125.      * @return (string[]|null)[]
  126.      */
  127.     private function getEffectiveSerializerGroups(array $optionsstring $resourceClass): array
  128.     {
  129.         if (isset($options['serializer_groups'])) {
  130.             $groups = (array) $options['serializer_groups'];
  131.             return [$groups$groups];
  132.         }
  133.         $resourceMetadata $this->resourceMetadataFactory->create($resourceClass);
  134.         if (isset($options['collection_operation_name'])) {
  135.             $normalizationContext $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], 'normalization_context'nulltrue);
  136.             $denormalizationContext $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], 'denormalization_context'nulltrue);
  137.         } elseif (isset($options['item_operation_name'])) {
  138.             $normalizationContext $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], 'normalization_context'nulltrue);
  139.             $denormalizationContext $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], 'denormalization_context'nulltrue);
  140.         } elseif (isset($options['graphql_operation_name'])) {
  141.             $normalizationContext $resourceMetadata->getGraphqlAttribute($options['graphql_operation_name'], 'normalization_context'nulltrue);
  142.             $denormalizationContext $resourceMetadata->getGraphqlAttribute($options['graphql_operation_name'], 'denormalization_context'nulltrue);
  143.         } else {
  144.             $normalizationContext $resourceMetadata->getAttribute('normalization_context');
  145.             $denormalizationContext $resourceMetadata->getAttribute('denormalization_context');
  146.         }
  147.         return [
  148.             isset($normalizationContext['groups']) ? (array) $normalizationContext['groups'] : null,
  149.             isset($denormalizationContext['groups']) ? (array) $denormalizationContext['groups'] : null,
  150.         ];
  151.     }
  152.     /**
  153.      * Gets the serializer groups defined on a property.
  154.      *
  155.      * @return string[]
  156.      */
  157.     private function getPropertySerializerGroups(string $classstring $property): array
  158.     {
  159.         $serializerClassMetadata $this->serializerClassMetadataFactory->getMetadataFor($class);
  160.         foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
  161.             if ($property === $serializerAttributeMetadata->getName()) {
  162.                 return $serializerAttributeMetadata->getGroups();
  163.             }
  164.         }
  165.         return [];
  166.     }
  167.     /**
  168.      * Gets all serializer groups used in a class.
  169.      *
  170.      * @return string[]
  171.      */
  172.     private function getClassSerializerGroups(string $class): array
  173.     {
  174.         $serializerClassMetadata $this->serializerClassMetadataFactory->getMetadataFor($class);
  175.         $groups = [];
  176.         foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
  177.             $groups array_merge($groups$serializerAttributeMetadata->getGroups());
  178.         }
  179.         return array_unique($groups);
  180.     }
  181. }