Skip to content

Commit df776e8

Browse files
soyukaabluchet
authored andcommitted
lalala
1 parent 290ee61 commit df776e8

File tree

9 files changed

+195
-103
lines changed

9 files changed

+195
-103
lines changed

src/Bridge/Symfony/Bundle/Resources/config/api.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@
3636
<argument type="service" id="service_container" />
3737
<argument>%api_platform.formats%</argument>
3838
<argument>%api_platform.resource_class_directories%</argument>
39-
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
40-
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
39+
<argument type="service" id="api_platform.subresource_operation_factory" />
4140

4241
<tag name="routing.loader" />
4342
</service>
@@ -203,6 +202,14 @@
203202
<service id="api_platform.cache.identifiers_extractor" parent="cache.system" public="false">
204203
<tag name="cache.pool" />
205204
</service>
205+
206+
<service id="api_platform.subresource_operation_factory" class="ApiPlatform\Core\Bridge\Symfony\Routing\SubresourceOperationFactory" public="false">
207+
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
208+
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
209+
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
210+
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
211+
<argument type="service" id="api_platform.operation_path_resolver.custom" />
212+
</service>
206213
</services>
207214

208215
</container>

src/Bridge/Symfony/Bundle/Resources/config/swagger.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<argument>%api_platform.oauth.tokenUrl%</argument>
2323
<argument>%api_platform.oauth.authorizationUrl%</argument>
2424
<argument>%api_platform.oauth.scopes%</argument>
25+
<argument type="service" id="api_platform.subresource_operation_factory"></argument>
2526
<tag name="serializer.normalizer" priority="16" />
2627
</service>
2728

src/Bridge/Symfony/Routing/ApiLoader.php

Lines changed: 25 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ final class ApiLoader extends Loader
4242
*/
4343
const ROUTE_NAME_PREFIX = 'api_';
4444
const DEFAULT_ACTION_PATTERN = 'api_platform.action.';
45-
const SUBRESOURCE_SUFFIX = '_get_subresource';
4645

4746
private $fileLoader;
4847
private $propertyNameCollectionFactory;
@@ -54,7 +53,7 @@ final class ApiLoader extends Loader
5453
private $formats;
5554
private $resourceClassDirectories;
5655

57-
public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, OperationPathResolverInterface $operationPathResolver, ContainerInterface $container, array $formats, array $resourceClassDirectories = [], PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory = null, PropertyMetadataFactoryInterface $propertyMetadataFactory = null)
56+
public function __construct(KernelInterface $kernel, ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, OperationPathResolverInterface $operationPathResolver, ContainerInterface $container, array $formats, array $resourceClassDirectories = [], SubresourceOperationFactory $subresourceOperationFactory = null)
5857
{
5958
$this->fileLoader = new XmlFileLoader(new FileLocator($kernel->locateResource('@ApiPlatformBundle/Resources/config/routing')));
6059
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
@@ -63,8 +62,7 @@ public function __construct(KernelInterface $kernel, ResourceNameCollectionFacto
6362
$this->container = $container;
6463
$this->formats = $formats;
6564
$this->resourceClassDirectories = $resourceClassDirectories;
66-
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
67-
$this->propertyMetadataFactory = $propertyMetadataFactory;
65+
$this->subresourceOperationFactory = $subresourceOperationFactory;
6866
}
6967

7068
/**
@@ -99,90 +97,34 @@ public function load($data, $type = null): RouteCollection
9997
}
10098
}
10199

102-
$this->computeSubresourceOperations($routeCollection, $resourceClass);
103-
}
104-
105-
return $routeCollection;
106-
}
107-
108-
/**
109-
* Handles subresource operations recursively and declare their corresponding routes.
110-
*
111-
* @param RouteCollection $routeCollection
112-
* @param string $resourceClass
113-
* @param string $rootResourceClass null on the first iteration, it then keeps track of the origin resource class
114-
* @param array $parentOperation the previous call operation
115-
*/
116-
private function computeSubresourceOperations(RouteCollection $routeCollection, string $resourceClass, string $rootResourceClass = null, array $parentOperation = null, array $visited = [])
117-
{
118-
if (null === $this->propertyNameCollectionFactory || null === $this->propertyMetadataFactory) {
119-
return;
120-
}
121-
122-
if (null === $rootResourceClass) {
123-
$rootResourceClass = $resourceClass;
124-
}
125-
126-
foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
127-
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property);
128-
129-
if (!$propertyMetadata->hasSubresource()) {
130-
continue;
131-
}
132-
133-
$subresource = $propertyMetadata->getSubresource();
134-
135-
$operation = [
136-
'property' => $property,
137-
'collection' => $subresource->isCollection(),
138-
];
139-
140-
$visiting = "$rootResourceClass $resourceClass $property {$subresource->isCollection()} {$subresource->getResourceClass()}";
141-
142-
if (in_array($visiting, $visited, true)) {
100+
if (null === $this->subresourceOperationFactory) {
143101
continue;
144102
}
145103

146-
$visited[] = $visiting;
147-
148-
if (null === $parentOperation) {
149-
$rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
150-
$rootShortname = $rootResourceMetadata->getShortName();
151-
152-
$operation['identifiers'] = [['id', $rootResourceClass]];
153-
$operation['route_name'] = RouteNameGenerator::generate('get', $rootShortname, OperationType::SUBRESOURCE, $operation);
154-
$operation['path'] = $this->operationPathResolver->resolveOperationPath($rootShortname, $operation, OperationType::SUBRESOURCE, $operation['route_name']);
155-
} else {
156-
$operation['identifiers'] = $parentOperation['identifiers'];
157-
$operation['identifiers'][] = [$parentOperation['property'], $resourceClass];
158-
$operation['route_name'] = str_replace('get'.RouteNameGenerator::SUBRESOURCE_SUFFIX, RouteNameGenerator::routeNameResolver($property, $operation['collection']).'_get'.RouteNameGenerator::SUBRESOURCE_SUFFIX, $parentOperation['route_name']);
159-
$operation['path'] = $this->operationPathResolver->resolveOperationPath($parentOperation['path'], $operation, OperationType::SUBRESOURCE, $operation['route_name']);
160-
}
161-
162-
$route = new Route(
163-
$operation['path'],
164-
[
165-
'_controller' => self::DEFAULT_ACTION_PATTERN.'get_subresource',
166-
'_format' => null,
167-
'_api_resource_class' => $subresource->getResourceClass(),
168-
'_api_subresource_operation_name' => $operation['route_name'],
169-
'_api_subresource_context' => [
170-
'property' => $operation['property'],
171-
'identifiers' => $operation['identifiers'],
172-
'collection' => $subresource->isCollection(),
104+
foreach ($this->subresourceOperationFactory->create($resourceClass) as $operation) {
105+
$routeCollection->add($operation['route_name'], new Route(
106+
$operation['path'],
107+
[
108+
'_controller' => self::DEFAULT_ACTION_PATTERN.'get_subresource',
109+
'_format' => null,
110+
'_api_resource_class' => $operation['resource_class'],
111+
'_api_subresource_operation_name' => $operation['route_name'],
112+
'_api_subresource_context' => [
113+
'property' => $operation['property'],
114+
'identifiers' => $operation['identifiers'],
115+
'collection' => $operation['collection'],
116+
],
173117
],
174-
],
175-
[],
176-
[],
177-
'',
178-
[],
179-
['GET']
180-
);
181-
182-
$routeCollection->add($operation['route_name'], $route);
183-
184-
$this->computeSubresourceOperations($routeCollection, $subresource->getResourceClass(), $rootResourceClass, $operation, $visited);
118+
[],
119+
[],
120+
'',
121+
[],
122+
['GET']
123+
));
124+
}
185125
}
126+
127+
return $routeCollection;
186128
}
187129

188130
/**

src/Bridge/Symfony/Routing/RouteNameGenerator.php

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
class RouteNameGenerator
2929
{
3030
const ROUTE_NAME_PREFIX = 'api_';
31-
const SUBRESOURCE_SUFFIX = '_subresource';
3231

3332
private function __construct()
3433
{
@@ -49,24 +48,13 @@ private function __construct()
4948
public static function generate(string $operationName, string $resourceShortName, $operationType, array $subresourceContext = []): string
5049
{
5150
if (OperationType::SUBRESOURCE === $operationType = OperationTypeDeprecationHelper::getOperationType($operationType)) {
52-
if (!isset($subresourceContext['property'])) {
53-
throw new InvalidArgumentException('Missing "property" to generate a route name from a subresource');
54-
}
55-
56-
return sprintf(
57-
'%s%s_%s_%s%s',
58-
static::ROUTE_NAME_PREFIX,
59-
self::routeNameResolver($resourceShortName),
60-
self::routeNameResolver($subresourceContext['property'], $subresourceContext['collection'] ?? false),
61-
$operationName,
62-
self::SUBRESOURCE_SUFFIX
63-
);
51+
throw new InvalidArgumentException('Subresource operations are not supported by the RouteNameGenerator.');
6452
}
6553

6654
return sprintf(
6755
'%s%s_%s_%s',
6856
static::ROUTE_NAME_PREFIX,
69-
self::routeNameResolver($resourceShortName),
57+
self::inflector($resourceShortName),
7058
$operationName,
7159
$operationType
7260
);
@@ -79,7 +67,7 @@ public static function generate(string $operationName, string $resourceShortName
7967
*
8068
* @return string A string that is a part of the route name
8169
*/
82-
public static function routeNameResolver(string $name, bool $pluralize = true): string
70+
public static function inflector(string $name, bool $pluralize = true): string
8371
{
8472
$name = Inflector::tableize($name);
8573

src/Bridge/Symfony/Routing/RouterOperationPathResolver.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Bridge\Symfony\Routing;
1515

1616
use ApiPlatform\Core\Api\OperationTypeDeprecationHelper;
17+
use ApiPlatform\Core\Bridge\Symfony\Routing\SubresourceOperationFactory;
1718
use ApiPlatform\Core\Exception\InvalidArgumentException;
1819
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
1920
use Symfony\Component\Routing\RouterInterface;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Bridge\Symfony\Routing;
15+
16+
use ApiPlatform\Core\Api\OperationType;
17+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
18+
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
19+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
20+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
21+
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
22+
23+
/**
24+
* @internal
25+
*/
26+
final class SubresourceOperationFactory
27+
{
28+
const SUBRESOURCE_SUFFIX = '_subresource';
29+
30+
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, OperationPathResolverInterface $operationPathResolver)
31+
{
32+
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
33+
$this->resourceMetadataFactory = $resourceMetadataFactory;
34+
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
35+
$this->propertyMetadataFactory = $propertyMetadataFactory;
36+
$this->operationPathResolver = $operationPathResolver;
37+
}
38+
39+
public function create(string $resourceClass): array
40+
{
41+
$tree = [];
42+
$this->computeSubresourceOperations($resourceClass, $tree);
43+
return $tree;
44+
}
45+
46+
/**
47+
* Handles subresource operations recursively and declare their corresponding routes.
48+
*
49+
* @param string $resourceClass
50+
* @param array $tree
51+
* @param string $rootResourceClass null on the first iteration, it then keeps track of the origin resource class
52+
* @param array $parentOperation the previous call operation
53+
*/
54+
private function computeSubresourceOperations(string $resourceClass, array &$tree, string $rootResourceClass = null, array $parentOperation = null, array $visited = [])
55+
{
56+
if (null === $rootResourceClass) {
57+
$rootResourceClass = $resourceClass;
58+
}
59+
60+
foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $property) {
61+
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property);
62+
63+
if (!$propertyMetadata->hasSubresource()) {
64+
continue;
65+
}
66+
67+
$subresource = $propertyMetadata->getSubresource();
68+
$subresourceMetadata = $this->resourceMetadataFactory->create($subresource->getResourceClass());
69+
70+
$operation = [
71+
'property' => $property,
72+
'collection' => $subresource->isCollection(),
73+
'resource_class' => $subresource->getResourceClass(),
74+
'shortnames' => [$subresourceMetadata->getShortName()],
75+
];
76+
77+
$visiting = "$rootResourceClass $resourceClass $property {$subresource->getResourceClass()}";
78+
79+
if (in_array($visiting, $visited, true)) {
80+
continue;
81+
}
82+
83+
$visited[] = $visiting;
84+
$operationName = 'get';
85+
86+
87+
if (null === $parentOperation) {
88+
$rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
89+
$rootShortname = $rootResourceMetadata->getShortName();
90+
91+
$operation['identifiers'] = [['id', $rootResourceClass]];
92+
$operation['route_name'] = sprintf(
93+
'%s%s_%s_%s%s',
94+
RouteNameGenerator::ROUTE_NAME_PREFIX,
95+
RouteNameGenerator::inflector($rootShortname),
96+
RouteNameGenerator::inflector($operation['property'], $operation['collection'] ?? false),
97+
$operationName,
98+
self::SUBRESOURCE_SUFFIX
99+
);
100+
$operation['path'] = $this->operationPathResolver->resolveOperationPath($rootShortname, $operation, OperationType::SUBRESOURCE, $operation['route_name']);
101+
$operation['shortnames'][] = $rootShortname;
102+
} else {
103+
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
104+
$operation['identifiers'] = $parentOperation['identifiers'];
105+
$operation['identifiers'][] = [$parentOperation['property'], $resourceClass];
106+
107+
$operation['shortnames'][] = $resourceMetadata->getShortName();
108+
$operation['route_name'] = str_replace('get'.self::SUBRESOURCE_SUFFIX, RouteNameGenerator::inflector($property, $operation['collection']).'_get'.self::SUBRESOURCE_SUFFIX, $parentOperation['route_name']);
109+
$operation['path'] = $this->operationPathResolver->resolveOperationPath($parentOperation['path'], $operation, OperationType::SUBRESOURCE, $operation['route_name']);
110+
}
111+
112+
$tree[$visiting] = $operation;
113+
$this->computeSubresourceOperations($subresource->getResourceClass(), $tree, $rootResourceClass, $operation, $visited);
114+
}
115+
}
116+
}

src/PathResolver/UnderscoreOperationPathResolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function resolveOperationPath(string $resourceShortName, array $operation
3838
if ($operationType === OperationType::SUBRESOURCE && 1 < count($operation['identifiers'])) {
3939
$path = str_replace('.{_format}', '', $resourceShortName);
4040
} else {
41-
$path = '/'.RouteNameGenerator::routeNameResolver($resourceShortName, true);
41+
$path = '/'.RouteNameGenerator::inflector($resourceShortName, true);
4242
}
4343

4444
if ($operationType === OperationType::ITEM) {
@@ -47,7 +47,7 @@ public function resolveOperationPath(string $resourceShortName, array $operation
4747

4848
if ($operationType === OperationType::SUBRESOURCE) {
4949
list($key) = end($operation['identifiers']);
50-
$property = RouteNameGenerator::routeNameResolver($operation['property'], $operation['collection']);
50+
$property = RouteNameGenerator::inflector($operation['property'], $operation['collection']);
5151
$path .= sprintf('/{%s}/%s', $key, $property);
5252
}
5353

0 commit comments

Comments
 (0)