Skip to content

Commit 1af17e5

Browse files
author
abluchet
committed
temp
1 parent df776e8 commit 1af17e5

17 files changed

+420
-77
lines changed

src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ public function getConfigTreeBuilder()
4343
->scalarNode('title')->defaultValue('')->info('The title of the API.')->end()
4444
->scalarNode('description')->defaultValue('')->info('The description of the API.')->end()
4545
->scalarNode('version')->defaultValue('0.0.0')->info('The version of the API.')->end()
46-
->scalarNode('default_operation_path_resolver')->defaultValue('api_platform.operation_path_resolver.underscore')->info('Specify the default operation path resolver to use for generating resources operations path.')->end()
46+
->scalarNode('default_operation_path_resolver')->defaultValue('api_platform.operation_path_resolver.generator')->info('Specify the default operation path resolver to use for generating resources operations path.')->end()
47+
//@TODO add config path name
4748
->scalarNode('name_converter')->defaultNull()->info('Specify a name converter to use.')->end()
4849
->scalarNode('api_resources_directory')->defaultValue('Entity')->info('The name of the directory within the bundles that contains the api resources.')->end()
4950
->arrayNode('eager_loading')

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,19 +85,29 @@
8585

8686
<!-- Resources Operations path resolver -->
8787

88+
<service id="api_platform.path_name_generator" alias="api_platform.path_name_generator.underscore" public="false" />
89+
90+
<service id="api_platform.path_name_generator.underscore" class="ApiPlatform\Core\PathResolver\UnderscorePathNameGenerator" public="false" />
91+
<service id="api_platform.path_name_generator.dash" class="ApiPlatform\Core\PathResolverDashPathNameGenerator" public="false" />
92+
8893
<service id="api_platform.operation_path_resolver" alias="api_platform.operation_path_resolver.router" public="false" />
8994

9095
<service id="api_platform.operation_path_resolver.router" class="ApiPlatform\Core\Bridge\Symfony\Routing\RouterOperationPathResolver" public="false">
9196
<argument type="service" id="api_platform.router" />
9297
<argument type="service" id="api_platform.operation_path_resolver.custom" />
98+
<argument type="service" id="api_platform.subresource_operation_factory" />
9399
</service>
94100

95101
<service id="api_platform.operation_path_resolver.custom" class="ApiPlatform\Core\PathResolver\CustomOperationPathResolver" public="false">
96102
<argument type="service" id="api_platform.operation_path_resolver.default" />
97103
</service>
98104

99-
<service id="api_platform.operation_path_resolver.underscore" class="ApiPlatform\Core\PathResolver\UnderscoreOperationPathResolver" public="false" />
100-
<service id="api_platform.operation_path_resolver.dash" class="ApiPlatform\Core\PathResolver\DashOperationPathResolver" public="false" />
105+
<service id="api_platform.operation_path_resolver.generator" class="ApiPlatform\Core\PathResolver\OperationPathResolver" public="false">
106+
<argument type="service" id="api_platform.path_name_generator" />
107+
</service>
108+
109+
<!-- <service id="api_platform.operation_path_resolver.underscore" class="ApiPlatform\Core\PathResolver\UnderscoreOperationPathResolver" public="false" /> -->
110+
<!-- <service id="api_platform.operation_path_resolver.dash" class="ApiPlatform\Core\PathResolver\DashOperationPathResolver" public="false" /> -->
101111

102112
<!-- Event listeners -->
103113

@@ -204,11 +214,10 @@
204214
</service>
205215

206216
<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" />
208217
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
209218
<argument type="service" id="api_platform.metadata.property.name_collection_factory" />
210219
<argument type="service" id="api_platform.metadata.property.metadata_factory" />
211-
<argument type="service" id="api_platform.operation_path_resolver.custom" />
220+
<argument type="service" id="api_platform.path_name_generator" />
212221
</service>
213222
</services>
214223

src/Bridge/Symfony/Routing/ApiLoader.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
use ApiPlatform\Core\Api\OperationType;
1717
use ApiPlatform\Core\Exception\InvalidResourceException;
1818
use ApiPlatform\Core\Exception\RuntimeException;
19-
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
20-
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2119
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
2220
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
2321
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;

src/Bridge/Symfony/Routing/RouteNameGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private function __construct()
4545
*
4646
* @return string
4747
*/
48-
public static function generate(string $operationName, string $resourceShortName, $operationType, array $subresourceContext = []): string
48+
public static function generate(string $operationName, string $resourceShortName, $operationType): string
4949
{
5050
if (OperationType::SUBRESOURCE === $operationType = OperationTypeDeprecationHelper::getOperationType($operationType)) {
5151
throw new InvalidArgumentException('Subresource operations are not supported by the RouteNameGenerator.');

src/Bridge/Symfony/Routing/RouterOperationPathResolver.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Symfony\Routing;
1515

16+
use ApiPlatform\Core\Api\OperationType;
1617
use ApiPlatform\Core\Api\OperationTypeDeprecationHelper;
17-
use ApiPlatform\Core\Bridge\Symfony\Routing\SubresourceOperationFactory;
1818
use ApiPlatform\Core\Exception\InvalidArgumentException;
1919
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
2020
use Symfony\Component\Routing\RouterInterface;
@@ -52,10 +52,14 @@ public function resolveOperationPath(string $resourceShortName, array $operation
5252

5353
if (isset($operation['route_name'])) {
5454
$routeName = $operation['route_name'];
55-
} elseif (null !== $operationName) {
56-
$routeName = RouteNameGenerator::generate($operationName, $resourceShortName, $operationType, $operation);
55+
} elseif (OperationType::SUBRESOURCE !== $operationType) {
56+
if (null !== $operationName) {
57+
$routeName = RouteNameGenerator::generate($operationName, $resourceShortName, $operationType, $operation);
58+
} else {
59+
return $this->deferred->resolveOperationPath($resourceShortName, $operation, OperationTypeDeprecationHelper::getOperationType($operationType), $operationName);
60+
}
5761
} else {
58-
return $this->deferred->resolveOperationPath($resourceShortName, $operation, OperationTypeDeprecationHelper::getOperationType($operationType), $operationName);
62+
throw new InvalidArgumentException('Subresource operations are not supported by the OperationPathResolver.');
5963
}
6064

6165
if (!$route = $this->router->getRouteCollection()->get($routeName)) {

src/Bridge/Symfony/Routing/SubresourceOperationFactory.php

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,44 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Symfony\Routing;
1515

16-
use ApiPlatform\Core\Api\OperationType;
1716
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
1817
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
1918
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
20-
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
21-
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
19+
use ApiPlatform\Core\PathResolver\PathNameGeneratorInterface;
2220

2321
/**
2422
* @internal
2523
*/
2624
final class SubresourceOperationFactory
2725
{
2826
const SUBRESOURCE_SUFFIX = '_subresource';
27+
const FORMAT_SUFFIX = '.{_format}';
2928

30-
public function __construct(ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, OperationPathResolverInterface $operationPathResolver)
29+
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PathNameGeneratorInterface $pathNameGenerator)
3130
{
32-
$this->resourceNameCollectionFactory = $resourceNameCollectionFactory;
3331
$this->resourceMetadataFactory = $resourceMetadataFactory;
3432
$this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
3533
$this->propertyMetadataFactory = $propertyMetadataFactory;
36-
$this->operationPathResolver = $operationPathResolver;
34+
$this->pathNameGenerator = $pathNameGenerator;
3735
}
3836

3937
public function create(string $resourceClass): array
4038
{
4139
$tree = [];
4240
$this->computeSubresourceOperations($resourceClass, $tree);
41+
4342
return $tree;
4443
}
4544

4645
/**
4746
* Handles subresource operations recursively and declare their corresponding routes.
4847
*
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
48+
* @param string $resourceClass
49+
* @param array $tree
50+
* @param string $rootResourceClass null on the first iteration, it then keeps track of the origin resource class
51+
* @param array $parentOperation the previous call operation
5352
*/
54-
private function computeSubresourceOperations(string $resourceClass, array &$tree, string $rootResourceClass = null, array $parentOperation = null, array $visited = [])
53+
private function computeSubresourceOperations(string $resourceClass, array &$tree, string $rootResourceClass = null, array $parentOperation = null)
5554
{
5655
if (null === $rootResourceClass) {
5756
$rootResourceClass = $resourceClass;
@@ -65,29 +64,37 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
6564
}
6665

6766
$subresource = $propertyMetadata->getSubresource();
68-
$subresourceMetadata = $this->resourceMetadataFactory->create($subresource->getResourceClass());
67+
$subresourceClass = $subresource->getResourceClass();
68+
$subresourceMetadata = $this->resourceMetadataFactory->create($subresourceClass);
6969

70-
$operation = [
71-
'property' => $property,
72-
'collection' => $subresource->isCollection(),
73-
'resource_class' => $subresource->getResourceClass(),
74-
'shortnames' => [$subresourceMetadata->getShortName()],
75-
];
70+
if (null === $parentOperation) {
71+
$visiting = "$rootResourceClass-$property-$subresourceClass";
72+
} else {
73+
$prefix = '';
74+
$visiting = "{$parentOperation['property']}-{$parentOperation['resource_class']}-$property-$subresourceClass";
7675

77-
$visiting = "$rootResourceClass $resourceClass $property {$subresource->getResourceClass()}";
76+
foreach ($parentOperation['identifiers'] as $key => list($param, $class)) {
77+
$prefix .= 0 === $key ? "$class" : "-$param-$class";
78+
}
7879

79-
if (in_array($visiting, $visited, true)) {
80-
continue;
80+
if (false !== strpos($prefix, $visiting)) {
81+
continue;
82+
}
83+
84+
$visiting = $prefix.'-'.$visiting;
8185
}
8286

83-
$visited[] = $visiting;
8487
$operationName = 'get';
85-
88+
$operation = [
89+
'property' => $property,
90+
'collection' => $subresource->isCollection(),
91+
'resource_class' => $subresourceClass,
92+
'shortNames' => [$subresourceMetadata->getShortName()],
93+
];
8694

8795
if (null === $parentOperation) {
8896
$rootResourceMetadata = $this->resourceMetadataFactory->create($rootResourceClass);
8997
$rootShortname = $rootResourceMetadata->getShortName();
90-
9198
$operation['identifiers'] = [['id', $rootResourceClass]];
9299
$operation['route_name'] = sprintf(
93100
'%s%s_%s_%s%s',
@@ -97,20 +104,29 @@ private function computeSubresourceOperations(string $resourceClass, array &$tre
97104
$operationName,
98105
self::SUBRESOURCE_SUFFIX
99106
);
100-
$operation['path'] = $this->operationPathResolver->resolveOperationPath($rootShortname, $operation, OperationType::SUBRESOURCE, $operation['route_name']);
101-
$operation['shortnames'][] = $rootShortname;
107+
108+
$operation['path'] = sprintf(
109+
'/%s/{id}/%s%s',
110+
$this->pathNameGenerator->getPathName($rootShortname, true),
111+
$this->pathNameGenerator->getPathName($operation['property'], $operation['collection']),
112+
self::FORMAT_SUFFIX
113+
);
114+
115+
$operation['shortNames'][] = $rootShortname;
102116
} else {
103117
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
104118
$operation['identifiers'] = $parentOperation['identifiers'];
105119
$operation['identifiers'][] = [$parentOperation['property'], $resourceClass];
106-
107-
$operation['shortnames'][] = $resourceMetadata->getShortName();
108120
$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']);
121+
$operation['shortNames'][] = $resourceMetadata->getShortName();
122+
123+
$operation['path'] = str_replace(self::FORMAT_SUFFIX, '', $parentOperation['path']);
124+
list($key) = end($operation['identifiers']);
125+
$operation['path'] .= sprintf('/{%s}/%s%s', $key, $this->pathNameGenerator->getPathName($property, $operation['collection']), self::FORMAT_SUFFIX);
110126
}
111127

112128
$tree[$visiting] = $operation;
113-
$this->computeSubresourceOperations($subresource->getResourceClass(), $tree, $rootResourceClass, $operation, $visited);
129+
$this->computeSubresourceOperations($subresourceClass, $tree, $rootResourceClass, $operation);
114130
}
115131
}
116132
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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\PathResolver;
15+
16+
use Doctrine\Common\Util\Inflector;
17+
18+
/**
19+
* Generate a path name with an underscore separator according to a string and whether it needs pluralization.
20+
*
21+
* @author Antoine Bluchet <[email protected]>
22+
*/
23+
class DashPathNameGenerator implements PathNameGeneratorInterface
24+
{
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function getPathName(string $name, bool $pluralize = true): string
29+
{
30+
$name = $this->dashize($name);
31+
32+
return $pluralize ? Inflector::pluralize($name) : $name;
33+
}
34+
35+
private function dashize(string $string): string
36+
{
37+
return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '-$1', $string));
38+
}
39+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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\PathResolver;
15+
16+
use ApiPlatform\Core\Api\OperationType;
17+
use ApiPlatform\Core\Api\OperationTypeDeprecationHelper;
18+
use ApiPlatform\Core\Exception\InvalidArgumentException;
19+
20+
/**
21+
* Generates an operation path.
22+
*
23+
* @author Antoine Bluchet <[email protected]>
24+
*/
25+
final class OperationPathResolver implements OperationPathResolverInterface
26+
{
27+
private $pathNameGenerator;
28+
29+
public function __construct(PathNameGeneratorInterface $pathNameGenerator)
30+
{
31+
$this->pathNameGenerator = $pathNameGenerator;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function resolveOperationPath(string $resourceShortName, array $operation, $operationType/*, string $operationName = null*/): string
38+
{
39+
if (func_num_args() < 4) {
40+
@trigger_error(sprintf('Method %s() will have a 4th `string $operationName` argument in version 3.0. Not defining it is deprecated since 2.1.', __METHOD__), E_USER_DEPRECATED);
41+
}
42+
43+
$operationType = OperationTypeDeprecationHelper::getOperationType($operationType);
44+
45+
if (OperationType::SUBRESOURCE === $operationType) {
46+
throw new InvalidArgumentException('Subresource operations are not supported by the OperationPathResolver.');
47+
}
48+
49+
$path = '/'.$this->pathNameGenerator->getPathName($resourceShortName, true);
50+
51+
if ($operationType === OperationType::ITEM) {
52+
$path .= '/{id}';
53+
}
54+
55+
$path .= '.{_format}';
56+
57+
return $path;
58+
}
59+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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\PathResolver;
15+
16+
/**
17+
* Generate a path name according to a string and whether it needs pluralization.
18+
*
19+
* @author Antoine Bluchet <[email protected]>
20+
*/
21+
interface PathNameGeneratorInterface
22+
{
23+
/**
24+
* Transforms a given string to a tableized, pluralized string.
25+
*
26+
* @param string $name usually a ResourceMetadata shortname
27+
*
28+
* @return string A string that is a part of the route name
29+
*/
30+
public function getPathName(string $name, bool $pluralize = true): string;
31+
}

0 commit comments

Comments
 (0)