vendor/api-platform/core/src/Hydra/Serializer/DocumentationNormalizer.php line 68

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\Hydra\Serializer;
  12. use ApiPlatform\Api\ResourceClassResolverInterface;
  13. use ApiPlatform\Api\UrlGeneratorInterface;
  14. use ApiPlatform\Core\Api\OperationMethodResolverInterface;
  15. use ApiPlatform\Core\Api\OperationType;
  16. use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface as LegacyPropertyMetadataFactoryInterface;
  17. use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
  18. use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
  19. use ApiPlatform\Core\Metadata\Property\SubresourceMetadata;
  20. use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
  21. use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
  22. use ApiPlatform\Core\Operation\Factory\SubresourceOperationFactoryInterface;
  23. use ApiPlatform\Documentation\Documentation;
  24. use ApiPlatform\JsonLd\ContextBuilderInterface;
  25. use ApiPlatform\Metadata\ApiProperty;
  26. use ApiPlatform\Metadata\ApiResource;
  27. use ApiPlatform\Metadata\CollectionOperationInterface;
  28. use ApiPlatform\Metadata\HttpOperation;
  29. use ApiPlatform\Metadata\Operation;
  30. use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
  31. use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
  32. use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
  33. use Symfony\Component\PropertyInfo\Type;
  34. use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
  35. use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
  36. use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
  37. use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
  38. /**
  39.  * Creates a machine readable Hydra API documentation.
  40.  *
  41.  * @author Kévin Dunglas <dunglas@gmail.com>
  42.  */
  43. final class DocumentationNormalizer implements NormalizerInterfaceCacheableSupportsMethodInterface
  44. {
  45.     public const FORMAT 'jsonld';
  46.     /**
  47.      * @var ResourceMetadataFactoryInterface|ResourceMetadataCollectionFactoryInterface
  48.      */
  49.     private $resourceMetadataFactory;
  50.     private $propertyNameCollectionFactory;
  51.     /**
  52.      * @var PropertyMetadataFactoryInterface|LegacyPropertyMetadataFactoryInterface
  53.      */
  54.     private $propertyMetadataFactory;
  55.     private $resourceClassResolver;
  56.     private $operationMethodResolver;
  57.     private $urlGenerator;
  58.     private $subresourceOperationFactory;
  59.     private $nameConverter;
  60.     public function __construct($resourceMetadataFactoryPropertyNameCollectionFactoryInterface $propertyNameCollectionFactory$propertyMetadataFactoryResourceClassResolverInterface $resourceClassResolverOperationMethodResolverInterface $operationMethodResolver nullUrlGeneratorInterface $urlGeneratorSubresourceOperationFactoryInterface $subresourceOperationFactory nullNameConverterInterface $nameConverter null)
  61.     {
  62.         if ($operationMethodResolver) {
  63.             @trigger_error(sprintf('Passing an instance of %s to %s() is deprecated since version 2.5 and will be removed in 3.0.'OperationMethodResolverInterface::class, __METHOD__), \E_USER_DEPRECATED);
  64.         }
  65.         $this->resourceMetadataFactory $resourceMetadataFactory;
  66.         if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
  67.             trigger_deprecation('api-platform/core''2.7'sprintf('Use "%s" instead of "%s".'ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class));
  68.         }
  69.         if ($subresourceOperationFactory) {
  70.             trigger_deprecation('api-platform/core''2.7'sprintf('Using "%s" is deprecated and will be removed.'SubresourceOperationFactoryInterface::class));
  71.         }
  72.         $this->propertyNameCollectionFactory $propertyNameCollectionFactory;
  73.         $this->propertyMetadataFactory $propertyMetadataFactory;
  74.         $this->resourceClassResolver $resourceClassResolver;
  75.         $this->operationMethodResolver $operationMethodResolver;
  76.         $this->urlGenerator $urlGenerator;
  77.         $this->subresourceOperationFactory $subresourceOperationFactory;
  78.         $this->nameConverter $nameConverter;
  79.     }
  80.     /**
  81.      * @param mixed|null $format
  82.      *
  83.      * @return array|string|int|float|bool|\ArrayObject|null
  84.      */
  85.     public function normalize($object$format null, array $context = [])
  86.     {
  87.         $classes = [];
  88.         $entrypointProperties = [];
  89.         foreach ($object->getResourceNameCollection() as $resourceClass) {
  90.             $resourceMetadataCollection $this->resourceMetadataFactory->create($resourceClass);
  91.             if ($resourceMetadataCollection instanceof ResourceMetadata) {
  92.                 $shortName $resourceMetadataCollection->getShortName();
  93.                 $prefixedShortName $resourceMetadataCollection->getIri() ?? "#$shortName";
  94.                 $this->populateEntrypointProperties($resourceClass$resourceMetadataCollection$shortName$prefixedShortName$entrypointProperties);
  95.                 $classes[] = $this->getClass($resourceClass$resourceMetadataCollection$shortName$prefixedShortName$context);
  96.                 continue;
  97.             }
  98.             $resourceMetadata $resourceMetadataCollection[0];
  99.             $shortName $resourceMetadata->getShortName();
  100.             $prefixedShortName $resourceMetadata->getTypes()[0] ?? "#$shortName";
  101.             $this->populateEntrypointProperties($resourceClass$resourceMetadata$shortName$prefixedShortName$entrypointProperties$resourceMetadataCollection);
  102.             $classes[] = $this->getClass($resourceClass$resourceMetadata$shortName$prefixedShortName$context$resourceMetadataCollection);
  103.         }
  104.         return $this->computeDoc($object$this->getClasses($entrypointProperties$classes));
  105.     }
  106.     /**
  107.      * Populates entrypoint properties.
  108.      *
  109.      * @param ResourceMetadata|ApiResource $resourceMetadata
  110.      */
  111.     private function populateEntrypointProperties(string $resourceClass$resourceMetadatastring $shortNamestring $prefixedShortName, array &$entrypointPropertiesResourceMetadataCollection $resourceMetadataCollection null)
  112.     {
  113.         $hydraCollectionOperations $this->getHydraOperations($resourceClass$resourceMetadata$prefixedShortNametrue$resourceMetadataCollection);
  114.         if (empty($hydraCollectionOperations)) {
  115.             return;
  116.         }
  117.         $entrypointProperty = [
  118.             '@type' => 'hydra:SupportedProperty',
  119.             'hydra:property' => [
  120.                 '@id' => sprintf('#Entrypoint/%s'lcfirst($shortName)),
  121.                 '@type' => 'hydra:Link',
  122.                 'domain' => '#Entrypoint',
  123.                 'rdfs:label' => "The collection of $shortName resources",
  124.                 'rdfs:range' => [
  125.                     ['@id' => 'hydra:Collection'],
  126.                     [
  127.                         'owl:equivalentClass' => [
  128.                             'owl:onProperty' => ['@id' => 'hydra:member'],
  129.                             'owl:allValuesFrom' => ['@id' => $prefixedShortName],
  130.                         ],
  131.                     ],
  132.                 ],
  133.                 'hydra:supportedOperation' => $hydraCollectionOperations,
  134.             ],
  135.             'hydra:title' => "The collection of $shortName resources",
  136.             'hydra:readable' => true,
  137.             'hydra:writeable' => false,
  138.         ];
  139.         if ($resourceMetadata instanceof ResourceMetadata $resourceMetadata->getCollectionOperationAttribute('GET''deprecation_reason'nulltrue) : $resourceMetadata->getDeprecationReason()) {
  140.             $entrypointProperty['owl:deprecated'] = true;
  141.         }
  142.         $entrypointProperties[] = $entrypointProperty;
  143.     }
  144.     /**
  145.      * Gets a Hydra class.
  146.      *
  147.      * @param ResourceMetadata|ApiResource $resourceMetadata
  148.      */
  149.     private function getClass(string $resourceClass$resourceMetadatastring $shortNamestring $prefixedShortName, array $contextResourceMetadataCollection $resourceMetadataCollection null): array
  150.     {
  151.         if ($resourceMetadata instanceof ApiResource) {
  152.             $description $resourceMetadata->getDescription();
  153.             $isDeprecated $resourceMetadata->getDeprecationReason();
  154.         } else {
  155.             $description $resourceMetadata->getDescription();
  156.             $isDeprecated $resourceMetadata->getAttribute('deprecation_reason');
  157.         }
  158.         $class = [
  159.             '@id' => $prefixedShortName,
  160.             '@type' => 'hydra:Class',
  161.             'rdfs:label' => $shortName,
  162.             'hydra:title' => $shortName,
  163.             'hydra:supportedProperty' => $this->getHydraProperties($resourceClass$resourceMetadata$shortName$prefixedShortName$context),
  164.             'hydra:supportedOperation' => $this->getHydraOperations($resourceClass$resourceMetadata$prefixedShortNamefalse$resourceMetadataCollection),
  165.         ];
  166.         if (null !== $description) {
  167.             $class['hydra:description'] = $description;
  168.         }
  169.         if ($isDeprecated) {
  170.             $class['owl:deprecated'] = true;
  171.         }
  172.         return $class;
  173.     }
  174.     /**
  175.      * Gets the context for the property name factory.
  176.      */
  177.     private function getPropertyNameCollectionFactoryContext(ResourceMetadata $resourceMetadata): array
  178.     {
  179.         $attributes $resourceMetadata->getAttributes();
  180.         $context = [];
  181.         if (isset($attributes['normalization_context'][AbstractNormalizer::GROUPS])) {
  182.             $context['serializer_groups'] = (array) $attributes['normalization_context'][AbstractNormalizer::GROUPS];
  183.         }
  184.         if (!isset($attributes['denormalization_context'][AbstractNormalizer::GROUPS])) {
  185.             return $context;
  186.         }
  187.         if (isset($context['serializer_groups'])) {
  188.             foreach ((array) $attributes['denormalization_context'][AbstractNormalizer::GROUPS] as $groupName) {
  189.                 $context['serializer_groups'][] = $groupName;
  190.             }
  191.             return $context;
  192.         }
  193.         $context['serializer_groups'] = (array) $attributes['denormalization_context'][AbstractNormalizer::GROUPS];
  194.         return $context;
  195.     }
  196.     /**
  197.      * Creates context for property metatata factories.
  198.      */
  199.     private function getPropertyMetadataFactoryContext(ApiResource $resourceMetadata): array
  200.     {
  201.         $normalizationGroups $resourceMetadata->getNormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
  202.         $denormalizationGroups $resourceMetadata->getDenormalizationContext()[AbstractNormalizer::GROUPS] ?? null;
  203.         $propertyContext = [
  204.             'normalization_groups' => $normalizationGroups,
  205.             'denormalization_groups' => $denormalizationGroups,
  206.         ];
  207.         $propertyNameContext = [];
  208.         if ($normalizationGroups) {
  209.             $propertyNameContext['serializer_groups'] = $normalizationGroups;
  210.         }
  211.         if (!$denormalizationGroups) {
  212.             return [$propertyNameContext$propertyContext];
  213.         }
  214.         if (!isset($propertyNameContext['serializer_groups'])) {
  215.             $propertyNameContext['serializer_groups'] = $denormalizationGroups;
  216.             return [$propertyNameContext$propertyContext];
  217.         }
  218.         foreach ($denormalizationGroups as $group) {
  219.             $propertyNameContext['serializer_groups'][] = $group;
  220.         }
  221.         return [$propertyNameContext$propertyContext];
  222.     }
  223.     /**
  224.      * Gets Hydra properties.
  225.      *
  226.      * @param ResourceMetadata|ApiResource $resourceMetadata
  227.      */
  228.     private function getHydraProperties(string $resourceClass$resourceMetadatastring $shortNamestring $prefixedShortName, array $context): array
  229.     {
  230.         $classes = [];
  231.         if ($resourceMetadata instanceof ResourceMetadata) {
  232.             foreach ($resourceMetadata->getCollectionOperations() as $operationName => $operation) {
  233.                 $inputMetadata $resourceMetadata->getTypedOperationAttribute(OperationType::COLLECTION$operationName'input', ['class' => $resourceClass], true);
  234.                 if (null !== $inputClass $inputMetadata['class'] ?? null) {
  235.                     $classes[$inputClass] = true;
  236.                 }
  237.                 $outputMetadata $resourceMetadata->getTypedOperationAttribute(OperationType::COLLECTION$operationName'output', ['class' => $resourceClass], true);
  238.                 if (null !== $outputClass $outputMetadata['class'] ?? null) {
  239.                     $classes[$outputClass] = true;
  240.                 }
  241.             }
  242.         } else {
  243.             $classes[$resourceClass] = true;
  244.             foreach ($resourceMetadata->getOperations() as $operation) {
  245.                 /** @var Operation $operation */
  246.                 if (!$operation instanceof CollectionOperationInterface) {
  247.                     continue;
  248.                 }
  249.                 $inputMetadata $operation->getInput();
  250.                 if (null !== $inputClass $inputMetadata['class'] ?? null) {
  251.                     $classes[$inputClass] = true;
  252.                 }
  253.                 $outputMetadata $operation->getOutput();
  254.                 if (null !== $outputClass $outputMetadata['class'] ?? null) {
  255.                     $classes[$outputClass] = true;
  256.                 }
  257.             }
  258.         }
  259.         /** @var string[] $classes */
  260.         $classes array_keys($classes);
  261.         $properties = [];
  262.         if ($resourceMetadata instanceof ResourceMetadata) {
  263.             $propertyNameContext $this->getPropertyNameCollectionFactoryContext($resourceMetadata);
  264.             $propertyContext = [];
  265.         } else {
  266.             [$propertyNameContext$propertyContext] = $this->getPropertyMetadataFactoryContext($resourceMetadata);
  267.         }
  268.         foreach ($classes as $class) {
  269.             foreach ($this->propertyNameCollectionFactory->create($class$propertyNameContext) as $propertyName) {
  270.                 $propertyMetadata $this->propertyMetadataFactory->create($class$propertyName$propertyContext);
  271.                 if (true === $propertyMetadata->isIdentifier() && false === $propertyMetadata->isWritable()) {
  272.                     continue;
  273.                 }
  274.                 if ($this->nameConverter) {
  275.                     $propertyName $this->nameConverter->normalize($propertyName$classself::FORMAT$context);
  276.                 }
  277.                 $properties[] = $this->getProperty($propertyMetadata$propertyName$prefixedShortName$shortName);
  278.             }
  279.         }
  280.         return $properties;
  281.     }
  282.     /**
  283.      * Gets Hydra operations.
  284.      *
  285.      * @param ResourceMetadata|ApiResource $resourceMetadata
  286.      */
  287.     private function getHydraOperations(string $resourceClass$resourceMetadatastring $prefixedShortNamebool $collectionResourceMetadataCollection $resourceMetadataCollection null): array
  288.     {
  289.         if ($resourceMetadata instanceof ResourceMetadata) {
  290.             if (null === $operations $collection $resourceMetadata->getCollectionOperations() : $resourceMetadata->getItemOperations()) {
  291.                 return [];
  292.             }
  293.             $hydraOperations = [];
  294.             foreach ($operations as $operationName => $operation) {
  295.                 $hydraOperations[] = $this->getHydraOperation($resourceClass$resourceMetadata$operationName$operation$prefixedShortName$collection OperationType::COLLECTION OperationType::ITEM);
  296.             }
  297.         } else {
  298.             $hydraOperations = [];
  299.             foreach ($resourceMetadataCollection as $resourceMetadata) {
  300.                 foreach ($resourceMetadata->getOperations() as $operationName => $operation) {
  301.                     if ((HttpOperation::METHOD_POST === $operation->getMethod() || $operation instanceof CollectionOperationInterface) !== $collection) {
  302.                         continue;
  303.                     }
  304.                     $hydraOperations[] = $this->getHydraOperation($resourceClass$resourceMetadata$operationName$operation$operation->getTypes()[0] ?? "#{$operation->getShortName()}"null);
  305.                 }
  306.             }
  307.         }
  308.         if (null !== $this->subresourceOperationFactory && !$this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
  309.             foreach ($this->subresourceOperationFactory->create($resourceClass) as $operationId => $operation) {
  310.                 $subresourceMetadata $this->resourceMetadataFactory->create($operation['resource_class']);
  311.                 $propertyMetadata $this->propertyMetadataFactory->create(end($operation['identifiers'])[0], $operation['property']);
  312.                 $hydraOperations[] = $this->getHydraOperation($resourceClass$subresourceMetadata$operation['route_name'], $operation"#{$subresourceMetadata->getShortName()}"OperationType::SUBRESOURCE$propertyMetadata->getSubresource());
  313.             }
  314.         }
  315.         return $hydraOperations;
  316.     }
  317.     /**
  318.      * Gets and populates if applicable a Hydra operation.
  319.      *
  320.      * @param ResourceMetadata|ApiResource $resourceMetadata
  321.      * @param array|HttpOperation          $operation
  322.      */
  323.     private function getHydraOperation(string $resourceClass$resourceMetadatastring $operationName$operationstring $prefixedShortNamestring $operationType nullSubresourceMetadata $subresourceMetadata null): array
  324.     {
  325.         if ($operation instanceof HttpOperation) {
  326.             $method $operation->getMethod() ?: HttpOperation::METHOD_GET;
  327.         } elseif ($this->operationMethodResolver) {
  328.             if (OperationType::COLLECTION === $operationType) {
  329.                 $method $this->operationMethodResolver->getCollectionOperationMethod($resourceClass$operationName);
  330.             } elseif (OperationType::ITEM === $operationType) {
  331.                 $method $this->operationMethodResolver->getItemOperationMethod($resourceClass$operationName);
  332.             } else {
  333.                 $method 'GET';
  334.             }
  335.         } else {
  336.             $method $resourceMetadata->getTypedOperationAttribute($operationType$operationName'method''GET');
  337.         }
  338.         $hydraOperation $operation instanceof HttpOperation ? ($operation->getHydraContext() ?? []) : ($operation['hydra_context'] ?? []);
  339.         if ($operation instanceof HttpOperation $operation->getDeprecationReason() : $resourceMetadata->getTypedOperationAttribute($operationType$operationName'deprecation_reason'nulltrue)) {
  340.             $hydraOperation['owl:deprecated'] = true;
  341.         }
  342.         if ($operation instanceof HttpOperation) {
  343.             $shortName $operation->getShortName();
  344.             $inputMetadata $operation->getInput() ?? [];
  345.             $outputMetadata $operation->getOutput() ?? [];
  346.             $operationType $operation instanceof CollectionOperationInterface OperationType::COLLECTION OperationType::ITEM;
  347.         } else {
  348.             $shortName $resourceMetadata->getShortName();
  349.             $inputMetadata $resourceMetadata->getTypedOperationAttribute($operationType$operationName'input', ['class' => false]);
  350.             $outputMetadata $resourceMetadata->getTypedOperationAttribute($operationType$operationName'output', ['class' => false]);
  351.         }
  352.         $inputClass \array_key_exists('class'$inputMetadata) ? $inputMetadata['class'] : false;
  353.         $outputClass \array_key_exists('class'$outputMetadata) ? $outputMetadata['class'] : false;
  354.         if ('GET' === $method && OperationType::COLLECTION === $operationType) {
  355.             $hydraOperation += [
  356.                 '@type' => ['hydra:Operation''schema:FindAction'],
  357.                 'hydra:title' => "Retrieves the collection of $shortName resources.",
  358.                 'returns' => 'hydra:Collection',
  359.             ];
  360.         } elseif ('GET' === $method && OperationType::SUBRESOURCE === $operationType) {
  361.             $hydraOperation += [
  362.                 '@type' => ['hydra:Operation''schema:FindAction'],
  363.                 'hydra:title' => $subresourceMetadata && $subresourceMetadata->isCollection() ? "Retrieves the collection of $shortName resources." "Retrieves a $shortName resource.",
  364.                 'returns' => null === $outputClass 'owl:Nothing' "#$shortName",
  365.             ];
  366.         } elseif ('GET' === $method) {
  367.             $hydraOperation += [
  368.                 '@type' => ['hydra:Operation''schema:FindAction'],
  369.                 'hydra:title' => "Retrieves a $shortName resource.",
  370.                 'returns' => null === $outputClass 'owl:Nothing' $prefixedShortName,
  371.             ];
  372.         } elseif ('PATCH' === $method) {
  373.             $hydraOperation += [
  374.                 '@type' => 'hydra:Operation',
  375.                 'hydra:title' => "Updates the $shortName resource.",
  376.                 'returns' => null === $outputClass 'owl:Nothing' $prefixedShortName,
  377.                 'expects' => null === $inputClass 'owl:Nothing' $prefixedShortName,
  378.             ];
  379.         } elseif ('POST' === $method) {
  380.             $hydraOperation += [
  381.                 '@type' => ['hydra:Operation''schema:CreateAction'],
  382.                 'hydra:title' => "Creates a $shortName resource.",
  383.                 'returns' => null === $outputClass 'owl:Nothing' $prefixedShortName,
  384.                 'expects' => null === $inputClass 'owl:Nothing' $prefixedShortName,
  385.             ];
  386.         } elseif ('PUT' === $method) {
  387.             $hydraOperation += [
  388.                 '@type' => ['hydra:Operation''schema:ReplaceAction'],
  389.                 'hydra:title' => "Replaces the $shortName resource.",
  390.                 'returns' => null === $outputClass 'owl:Nothing' $prefixedShortName,
  391.                 'expects' => null === $inputClass 'owl:Nothing' $prefixedShortName,
  392.             ];
  393.         } elseif ('DELETE' === $method) {
  394.             $hydraOperation += [
  395.                 '@type' => ['hydra:Operation''schema:DeleteAction'],
  396.                 'hydra:title' => "Deletes the $shortName resource.",
  397.                 'returns' => 'owl:Nothing',
  398.             ];
  399.         }
  400.         $hydraOperation['hydra:method'] ?? $hydraOperation['hydra:method'] = $method;
  401.         if (!isset($hydraOperation['rdfs:label']) && isset($hydraOperation['hydra:title'])) {
  402.             $hydraOperation['rdfs:label'] = $hydraOperation['hydra:title'];
  403.         }
  404.         ksort($hydraOperation);
  405.         return $hydraOperation;
  406.     }
  407.     /**
  408.      * Gets the range of the property.
  409.      *
  410.      * @param ApiProperty|PropertyMetadata $propertyMetadata
  411.      */
  412.     private function getRange($propertyMetadata): ?string
  413.     {
  414.         $jsonldContext $propertyMetadata instanceof PropertyMetadata $propertyMetadata->getAttributes()['jsonld_context'] ?? [] : $propertyMetadata->getJsonldContext();
  415.         if (isset($jsonldContext['@type'])) {
  416.             return $jsonldContext['@type'];
  417.         }
  418.         // TODO: 3.0 support multiple types, default value of types will be [] instead of null
  419.         $type $propertyMetadata instanceof PropertyMetadata $propertyMetadata->getType() : $propertyMetadata->getBuiltinTypes()[0] ?? null;
  420.         if (null === $type) {
  421.             return null;
  422.         }
  423.         if ($type->isCollection() && null !== $collectionType method_exists(Type::class, 'getCollectionValueTypes') ? ($type->getCollectionValueTypes()[0] ?? null) : $type->getCollectionValueType()) {
  424.             $type $collectionType;
  425.         }
  426.         switch ($type->getBuiltinType()) {
  427.             case Type::BUILTIN_TYPE_STRING:
  428.                 return 'xmls:string';
  429.             case Type::BUILTIN_TYPE_INT:
  430.                 return 'xmls:integer';
  431.             case Type::BUILTIN_TYPE_FLOAT:
  432.                 return 'xmls:decimal';
  433.             case Type::BUILTIN_TYPE_BOOL:
  434.                 return 'xmls:boolean';
  435.             case Type::BUILTIN_TYPE_OBJECT:
  436.                 if (null === $className $type->getClassName()) {
  437.                     return null;
  438.                 }
  439.                 if (is_a($className\DateTimeInterface::class, true)) {
  440.                     return 'xmls:dateTime';
  441.                 }
  442.                 if ($this->resourceClassResolver->isResourceClass($className)) {
  443.                     $resourceMetadata $this->resourceMetadataFactory->create($className);
  444.                     if ($resourceMetadata instanceof ResourceMetadataCollection) {
  445.                         $operation $resourceMetadata->getOperation();
  446.                         if (!$operation instanceof HttpOperation) {
  447.                             return "#{$operation->getShortName()}";
  448.                         }
  449.                         return $operation->getTypes()[0] ?? "#{$operation->getShortName()}";
  450.                     }
  451.                     return $resourceMetadata->getIri() ?? "#{$resourceMetadata->getShortName()}";
  452.                 }
  453.         }
  454.         return null;
  455.     }
  456.     /**
  457.      * Builds the classes array.
  458.      */
  459.     private function getClasses(array $entrypointProperties, array $classes): array
  460.     {
  461.         $classes[] = [
  462.             '@id' => '#Entrypoint',
  463.             '@type' => 'hydra:Class',
  464.             'hydra:title' => 'The API entrypoint',
  465.             'hydra:supportedProperty' => $entrypointProperties,
  466.             'hydra:supportedOperation' => [
  467.                 '@type' => 'hydra:Operation',
  468.                 'hydra:method' => 'GET',
  469.                 'rdfs:label' => 'The API entrypoint.',
  470.                 'returns' => '#EntryPoint',
  471.             ],
  472.         ];
  473.         // Constraint violation
  474.         $classes[] = [
  475.             '@id' => '#ConstraintViolation',
  476.             '@type' => 'hydra:Class',
  477.             'hydra:title' => 'A constraint violation',
  478.             'hydra:supportedProperty' => [
  479.                 [
  480.                     '@type' => 'hydra:SupportedProperty',
  481.                     'hydra:property' => [
  482.                         '@id' => '#ConstraintViolation/propertyPath',
  483.                         '@type' => 'rdf:Property',
  484.                         'rdfs:label' => 'propertyPath',
  485.                         'domain' => '#ConstraintViolation',
  486.                         'range' => 'xmls:string',
  487.                     ],
  488.                     'hydra:title' => 'propertyPath',
  489.                     'hydra:description' => 'The property path of the violation',
  490.                     'hydra:readable' => true,
  491.                     'hydra:writeable' => false,
  492.                 ],
  493.                 [
  494.                     '@type' => 'hydra:SupportedProperty',
  495.                     'hydra:property' => [
  496.                         '@id' => '#ConstraintViolation/message',
  497.                         '@type' => 'rdf:Property',
  498.                         'rdfs:label' => 'message',
  499.                         'domain' => '#ConstraintViolation',
  500.                         'range' => 'xmls:string',
  501.                     ],
  502.                     'hydra:title' => 'message',
  503.                     'hydra:description' => 'The message associated with the violation',
  504.                     'hydra:readable' => true,
  505.                     'hydra:writeable' => false,
  506.                 ],
  507.             ],
  508.         ];
  509.         // Constraint violation list
  510.         $classes[] = [
  511.             '@id' => '#ConstraintViolationList',
  512.             '@type' => 'hydra:Class',
  513.             'subClassOf' => 'hydra:Error',
  514.             'hydra:title' => 'A constraint violation list',
  515.             'hydra:supportedProperty' => [
  516.                 [
  517.                     '@type' => 'hydra:SupportedProperty',
  518.                     'hydra:property' => [
  519.                         '@id' => '#ConstraintViolationList/violations',
  520.                         '@type' => 'rdf:Property',
  521.                         'rdfs:label' => 'violations',
  522.                         'domain' => '#ConstraintViolationList',
  523.                         'range' => '#ConstraintViolation',
  524.                     ],
  525.                     'hydra:title' => 'violations',
  526.                     'hydra:description' => 'The violations',
  527.                     'hydra:readable' => true,
  528.                     'hydra:writeable' => false,
  529.                 ],
  530.             ],
  531.         ];
  532.         return $classes;
  533.     }
  534.     /**
  535.      * Gets a property definition.
  536.      *
  537.      * @param ApiProperty|PropertyMetadata $propertyMetadata
  538.      */
  539.     private function getProperty($propertyMetadatastring $propertyNamestring $prefixedShortNamestring $shortName): array
  540.     {
  541.         if ($propertyMetadata instanceof PropertyMetadata) {
  542.             $iri $propertyMetadata->getIri();
  543.         } else {
  544.             if ($iri $propertyMetadata->getIris()) {
  545.                 $iri === \count($iri) ? $iri[0] : $iri;
  546.             }
  547.         }
  548.         if (!isset($iri)) {
  549.             $iri "#$shortName/$propertyName";
  550.         }
  551.         $propertyData = [
  552.             '@id' => $iri,
  553.             '@type' => false === $propertyMetadata->isReadableLink() ? 'hydra:Link' 'rdf:Property',
  554.             'rdfs:label' => $propertyName,
  555.             'domain' => $prefixedShortName,
  556.         ];
  557.         // TODO: 3.0 support multiple types, default value of types will be [] instead of null
  558.         $type $propertyMetadata instanceof PropertyMetadata $propertyMetadata->getType() : $propertyMetadata->getBuiltinTypes()[0] ?? null;
  559.         if (null !== $type && !$type->isCollection() && (null !== $className $type->getClassName()) && $this->resourceClassResolver->isResourceClass($className)) {
  560.             $propertyData['owl:maxCardinality'] = 1;
  561.         }
  562.         $property = [
  563.             '@type' => 'hydra:SupportedProperty',
  564.             'hydra:property' => $propertyData,
  565.             'hydra:title' => $propertyName,
  566.             'hydra:required' => $propertyMetadata->isRequired(),
  567.             'hydra:readable' => $propertyMetadata->isReadable(),
  568.             'hydra:writeable' => $propertyMetadata->isWritable() || $propertyMetadata->isInitializable(),
  569.         ];
  570.         if (null !== $range $this->getRange($propertyMetadata)) {
  571.             $property['hydra:property']['range'] = $range;
  572.         }
  573.         if (null !== $description $propertyMetadata->getDescription()) {
  574.             $property['hydra:description'] = $description;
  575.         }
  576.         if ($deprecationReason $propertyMetadata instanceof PropertyMetadata $propertyMetadata->getAttribute('deprecation_reason') : $propertyMetadata->getDeprecationReason()) {
  577.             $property['owl:deprecated'] = true;
  578.         }
  579.         return $property;
  580.     }
  581.     /**
  582.      * Computes the documentation.
  583.      */
  584.     private function computeDoc(Documentation $object, array $classes): array
  585.     {
  586.         $doc = ['@context' => $this->getContext(), '@id' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT]), '@type' => 'hydra:ApiDocumentation'];
  587.         if ('' !== $object->getTitle()) {
  588.             $doc['hydra:title'] = $object->getTitle();
  589.         }
  590.         if ('' !== $object->getDescription()) {
  591.             $doc['hydra:description'] = $object->getDescription();
  592.         }
  593.         $doc['hydra:entrypoint'] = $this->urlGenerator->generate('api_entrypoint');
  594.         $doc['hydra:supportedClass'] = $classes;
  595.         return $doc;
  596.     }
  597.     /**
  598.      * Builds the JSON-LD context for the API documentation.
  599.      */
  600.     private function getContext(): array
  601.     {
  602.         return [
  603.             '@vocab' => $this->urlGenerator->generate('api_doc', ['_format' => self::FORMAT], UrlGeneratorInterface::ABS_URL).'#',
  604.             'hydra' => ContextBuilderInterface::HYDRA_NS,
  605.             'rdf' => ContextBuilderInterface::RDF_NS,
  606.             'rdfs' => ContextBuilderInterface::RDFS_NS,
  607.             'xmls' => ContextBuilderInterface::XML_NS,
  608.             'owl' => ContextBuilderInterface::OWL_NS,
  609.             'schema' => ContextBuilderInterface::SCHEMA_ORG_NS,
  610.             'domain' => ['@id' => 'rdfs:domain''@type' => '@id'],
  611.             'range' => ['@id' => 'rdfs:range''@type' => '@id'],
  612.             'subClassOf' => ['@id' => 'rdfs:subClassOf''@type' => '@id'],
  613.             'expects' => ['@id' => 'hydra:expects''@type' => '@id'],
  614.             'returns' => ['@id' => 'hydra:returns''@type' => '@id'],
  615.         ];
  616.     }
  617.     public function supportsNormalization($data$format null, array $context = []): bool
  618.     {
  619.         return self::FORMAT === $format && $data instanceof Documentation;
  620.     }
  621.     public function hasCacheableSupportsMethod(): bool
  622.     {
  623.         return true;
  624.     }
  625. }
  626. class_alias(DocumentationNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\DocumentationNormalizer::class);