vendor/api-platform/core/src/Operation/Factory/SubresourceOperationFactory.php line 70

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\Operation\Factory;
  12. use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameGenerator;
  13. use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
  14. use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
  15. use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
  16. use ApiPlatform\Core\Operation\PathSegmentNameGeneratorInterface;
  17. /**
  18.  * @internal
  19.  */
  20. final class SubresourceOperationFactory implements SubresourceOperationFactoryInterface
  21. {
  22.     public const SUBRESOURCE_SUFFIX '_subresource';
  23.     public const FORMAT_SUFFIX '.{_format}';
  24.     public const ROUTE_OPTIONS = ['defaults' => [], 'requirements' => [], 'options' => [], 'host' => '''schemes' => [], 'condition' => '''controller' => null];
  25.     private $resourceMetadataFactory;
  26.     private $propertyNameCollectionFactory;
  27.     private $propertyMetadataFactory;
  28.     private $pathSegmentNameGenerator;
  29.     public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactoryPropertyNameCollectionFactoryInterface $propertyNameCollectionFactoryPropertyMetadataFactoryInterface $propertyMetadataFactoryPathSegmentNameGeneratorInterface $pathSegmentNameGenerator)
  30.     {
  31.         $this->resourceMetadataFactory $resourceMetadataFactory;
  32.         $this->propertyNameCollectionFactory $propertyNameCollectionFactory;
  33.         $this->propertyMetadataFactory $propertyMetadataFactory;
  34.         $this->pathSegmentNameGenerator $pathSegmentNameGenerator;
  35.     }
  36.     /**
  37.      * {@inheritdoc}
  38.      */
  39.     public function create(string $resourceClass): array
  40.     {
  41.         $tree = [];
  42.         $this->computeSubresourceOperations($resourceClass$tree);
  43.         return $tree;
  44.     }
  45.     /**
  46.      * Handles subresource operations recursively and declare their corresponding routes.
  47.      *
  48.      * @param string $rootResourceClass null on the first iteration, it then keeps track of the origin resource class
  49.      * @param array  $parentOperation   the previous call operation
  50.      * @param int    $depth             the number of visited
  51.      * @param int    $maxDepth
  52.      */
  53.     private function computeSubresourceOperations(string $resourceClass, array &$treestring $rootResourceClass null, array $parentOperation null, array $visited = [], int $depth 0int $maxDepth null): void
  54.     {
  55.         if (null === $rootResourceClass) {
  56.             $rootResourceClass $resourceClass;
  57.         }
  58.         foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
  59.             $propertyMetadata $this->propertyMetadataFactory->create($resourceClass$property);
  60.             if (!$subresource $propertyMetadata->getSubresource()) {
  61.                 continue;
  62.             }
  63.             $subresourceClass $subresource->getResourceClass();
  64.             $subresourceMetadata $this->resourceMetadataFactory->create($subresourceClass);
  65.             $isLastItem = ($parentOperation['resource_class'] ?? null) === $resourceClass && $propertyMetadata->isIdentifier();
  66.             // A subresource that is also an identifier can't be a start point
  67.             if ($isLastItem && (null === $parentOperation || false === $parentOperation['collection'])) {
  68.                 continue;
  69.             }
  70.             $visiting "$resourceClass $property $subresourceClass";
  71.             // Handle maxDepth
  72.             if (null !== $maxDepth && $depth >= $maxDepth) {
  73.                 break;
  74.             }
  75.             if (isset($visited[$visiting])) {
  76.                 continue;
  77.             }
  78.             $rootResourceMetadata $this->resourceMetadataFactory->create($rootResourceClass);
  79.             $operationName 'get';
  80.             $operation = [
  81.                 'property' => $property,
  82.                 'collection' => $subresource->isCollection(),
  83.                 'resource_class' => $subresourceClass,
  84.                 'shortNames' => [$subresourceMetadata->getShortName()],
  85.             ];
  86.             if (null === $parentOperation) {
  87.                 $rootShortname $rootResourceMetadata->getShortName();
  88.                 $operation['identifiers'] = [['id'$rootResourceClasstrue]];
  89.                 $operation['operation_name'] = sprintf(
  90.                     '%s_%s%s',
  91.                     RouteNameGenerator::inflector($operation['property'], $operation['collection'] ?? false),
  92.                     $operationName,
  93.                     self::SUBRESOURCE_SUFFIX
  94.                 );
  95.                 $subresourceOperation $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? [];
  96.                 $operation['route_name'] = sprintf(
  97.                     '%s%s_%s',
  98.                     RouteNameGenerator::ROUTE_NAME_PREFIX,
  99.                     RouteNameGenerator::inflector($rootShortname),
  100.                     $operation['operation_name']
  101.                 );
  102.                 $prefix trim(trim($rootResourceMetadata->getAttribute('route_prefix''')), '/');
  103.                 if ('' !== $prefix) {
  104.                     $prefix .= '/';
  105.                 }
  106.                 $operation['path'] = $subresourceOperation['path'] ?? sprintf(
  107.                     '/%s%s/{id}/%s%s',
  108.                     $prefix,
  109.                     $this->pathSegmentNameGenerator->getSegmentName($rootShortname),
  110.                     $this->pathSegmentNameGenerator->getSegmentName($operation['property'], $operation['collection']),
  111.                     self::FORMAT_SUFFIX
  112.                 );
  113.                 if (!\in_array($rootShortname$operation['shortNames'], true)) {
  114.                     $operation['shortNames'][] = $rootShortname;
  115.                 }
  116.             } else {
  117.                 $resourceMetadata $this->resourceMetadataFactory->create($resourceClass);
  118.                 $operation['identifiers'] = $parentOperation['identifiers'];
  119.                 $operation['identifiers'][] = [$parentOperation['property'], $resourceClass$isLastItem true $parentOperation['collection']];
  120.                 $operation['operation_name'] = str_replace(
  121.                     'get'.self::SUBRESOURCE_SUFFIX,
  122.                     RouteNameGenerator::inflector($isLastItem 'item' $property$operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX,
  123.                     $parentOperation['operation_name']
  124.                 );
  125.                 $operation['route_name'] = str_replace($parentOperation['operation_name'], $operation['operation_name'], $parentOperation['route_name']);
  126.                 if (!\in_array($resourceMetadata->getShortName(), $operation['shortNames'], true)) {
  127.                     $operation['shortNames'][] = $resourceMetadata->getShortName();
  128.                 }
  129.                 $subresourceOperation $rootResourceMetadata->getSubresourceOperations()[$operation['operation_name']] ?? [];
  130.                 if (isset($subresourceOperation['path'])) {
  131.                     $operation['path'] = $subresourceOperation['path'];
  132.                 } else {
  133.                     $operation['path'] = str_replace(self::FORMAT_SUFFIX'', (string) $parentOperation['path']);
  134.                     if ($parentOperation['collection']) {
  135.                         [$key] = end($operation['identifiers']);
  136.                         $operation['path'] .= sprintf('/{%s}'$key);
  137.                     }
  138.                     if ($isLastItem) {
  139.                         $operation['path'] .= self::FORMAT_SUFFIX;
  140.                     } else {
  141.                         $operation['path'] .= sprintf('/%s%s'$this->pathSegmentNameGenerator->getSegmentName($property$operation['collection']), self::FORMAT_SUFFIX);
  142.                     }
  143.                 }
  144.             }
  145.             foreach (self::ROUTE_OPTIONS as $routeOption => $defaultValue) {
  146.                 $operation[$routeOption] = $subresourceOperation[$routeOption] ?? $defaultValue;
  147.             }
  148.             $tree[$operation['route_name']] = $operation;
  149.             // Get the minimum maxDepth between the rootMaxDepth and the maxDepth of the to be visited Subresource
  150.             $currentMaxDepth array_filter([$maxDepth$subresource->getMaxDepth()], 'is_int');
  151.             $currentMaxDepth = empty($currentMaxDepth) ? null min($currentMaxDepth);
  152.             $this->computeSubresourceOperations($subresourceClass$tree$rootResourceClass$operation$visited + [$visiting => true], $depth 1$currentMaxDepth);
  153.         }
  154.     }
  155. }