Skip to content

Commit 22630f7

Browse files
authored
Merge pull request #2233 from dunglas/resolve-parameters
Allow to use container parameters as class name
2 parents 4dacd1a + f7e816e commit 22630f7

20 files changed

+982
-87
lines changed

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ parameters:
2525
# https://github.com/doctrine/doctrine2/pull/7298/files
2626
- '#Strict comparison using === between null and int will always evaluate to false\.#'
2727
- '#Strict comparison using !== between null and null will always evaluate to false\.#'
28+
- '#Class ApiPlatform\\Core\\Tests\\Fixtures\\TestBundle\\Entity\\DummyBis not found.#'
2829

2930
# Expected, due to deprecations
3031
- '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\QueryResult(Item|Collection)ExtensionInterface::getResult\(\) invoked with 4 parameters, 1 required\.#'

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,15 +207,15 @@ private function registerMetadataConfiguration(ContainerBuilder $container, arra
207207
));
208208
}
209209

210-
$container->getDefinition('api_platform.metadata.extractor.xml')->addArgument($xmlResources);
210+
$container->getDefinition('api_platform.metadata.extractor.xml')->replaceArgument(0, $xmlResources);
211211

212212
if (class_exists(Annotation::class)) {
213213
$loader->load('metadata/annotation.xml');
214214
}
215215

216216
if (class_exists(Yaml::class)) {
217217
$loader->load('metadata/yaml.xml');
218-
$container->getDefinition('api_platform.metadata.extractor.yaml')->addArgument($yamlResources);
218+
$container->getDefinition('api_platform.metadata.extractor.yaml')->replaceArgument(0, $yamlResources);
219219
}
220220

221221
if (interface_exists(DocBlockFactoryInterface::class)) {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<services>
8-
<service id="api_platform.metadata.extractor.xml" class="ApiPlatform\Core\Metadata\Extractor\XmlExtractor" public="false" />
8+
<service id="api_platform.metadata.extractor.xml" class="ApiPlatform\Core\Metadata\Extractor\XmlExtractor" public="false">
9+
<argument type="collection"></argument>
10+
<argument type="service" id="service_container" />
11+
</service>
912

1013
<service id="api_platform.metadata.resource.name_collection_factory" alias="api_platform.metadata.resource.name_collection_factory.xml" />
1114
<service id="api_platform.metadata.resource.name_collection_factory.xml" class="ApiPlatform\Core\Metadata\Resource\Factory\ExtractorResourceNameCollectionFactory" public="false">

src/Bridge/Symfony/Bundle/Resources/config/metadata/yaml.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<services>
8-
<service id="api_platform.metadata.extractor.yaml" class="ApiPlatform\Core\Metadata\Extractor\YamlExtractor" public="false" />
8+
<service id="api_platform.metadata.extractor.yaml" class="ApiPlatform\Core\Metadata\Extractor\YamlExtractor" public="false">
9+
<argument type="collection"></argument>
10+
<argument type="service" id="service_container" />
11+
</service>
912

1013
<service id="api_platform.metadata.resource.name_collection_factory.yaml" decorates="api_platform.metadata.resource.name_collection_factory" class="ApiPlatform\Core\Metadata\Resource\Factory\ExtractorResourceNameCollectionFactory" public="false">
1114
<argument type="service" id="api_platform.metadata.extractor.yaml" />

src/Metadata/Extractor/AbstractExtractor.php

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313

1414
namespace ApiPlatform\Core\Metadata\Extractor;
1515

16+
use Psr\Container\ContainerInterface;
17+
use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
18+
1619
/**
1720
* Base file extractor.
1821
*
@@ -22,13 +25,16 @@ abstract class AbstractExtractor implements ExtractorInterface
2225
{
2326
protected $paths;
2427
protected $resources;
28+
private $container;
29+
private $collectedParameters = [];
2530

2631
/**
2732
* @param string[] $paths
2833
*/
29-
public function __construct(array $paths)
34+
public function __construct(array $paths, ContainerInterface $container = null)
3035
{
3136
$this->paths = $paths;
37+
$this->container = $container;
3238
}
3339

3440
/**
@@ -52,4 +58,70 @@ public function getResources(): array
5258
* Extracts metadata from a given path.
5359
*/
5460
abstract protected function extractPath(string $path);
61+
62+
/**
63+
* Recursively replaces placeholders with the service container parameters.
64+
*
65+
* @see https://github.com/symfony/symfony/blob/6fec32c/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php
66+
*
67+
* @copyright (c) Fabien Potencier <[email protected]>
68+
*
69+
* @param mixed $value The source which might contain "%placeholders%"
70+
*
71+
* @throws \RuntimeException When a container value is not a string or a numeric value
72+
*
73+
* @return mixed The source with the placeholders replaced by the container
74+
* parameters. Arrays are resolved recursively.
75+
*/
76+
protected function resolve($value)
77+
{
78+
if (null === $this->container) {
79+
return $value;
80+
}
81+
82+
if (\is_array($value)) {
83+
foreach ($value as $key => $val) {
84+
$value[$key] = $this->resolve($val);
85+
}
86+
87+
return $value;
88+
}
89+
90+
if (!\is_string($value)) {
91+
return $value;
92+
}
93+
94+
$escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($value) {
95+
$parameter = $match[1];
96+
97+
// skip %%
98+
if (!isset($parameter)) {
99+
return '%%';
100+
}
101+
102+
if (preg_match('/^env\(\w+\)$/', $parameter)) {
103+
throw new \RuntimeException(sprintf('Using "%%%s%%" is not allowed in routing configuration.', $parameter));
104+
}
105+
106+
if (array_key_exists($parameter, $this->collectedParameters)) {
107+
return $this->collectedParameters[$parameter];
108+
}
109+
110+
if ($this->container instanceof SymfonyContainerInterface) {
111+
$resolved = $this->container->getParameter($parameter);
112+
} else {
113+
$resolved = $this->container->get($parameter);
114+
}
115+
116+
if (\is_string($resolved) || is_numeric($resolved)) {
117+
$this->collectedParameters[$parameter] = $resolved;
118+
119+
return (string) $resolved;
120+
}
121+
122+
throw new\ RuntimeException(sprintf('The container parameter "%s", used in the resource configuration value "%s", must be a string or numeric, but it is of type %s.', $parameter, $value, \gettype($resolved)));
123+
}, $value);
124+
125+
return str_replace('%%', '%', $escapedValue);
126+
}
55127
}

src/Metadata/Extractor/XmlExtractor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected function extractPath(string $path)
3939
}
4040

4141
foreach ($xml->resource as $resource) {
42-
$resourceClass = (string) $resource['class'];
42+
$resourceClass = $this->resolve((string) $resource['class']);
4343

4444
$this->resources[$resourceClass] = [
4545
'shortName' => $this->phpize($resource, 'shortName', 'string'),

src/Metadata/Extractor/YamlExtractor.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ protected function extractPath(string $path)
5353
private function extractResources(array $resourcesYaml, string $path)
5454
{
5555
foreach ($resourcesYaml as $resourceName => $resourceYaml) {
56+
$resourceName = $this->resolve($resourceName);
57+
5658
if (null === $resourceYaml) {
5759
$resourceYaml = [];
5860
}
@@ -108,7 +110,7 @@ private function extractProperties(array $resourceYaml, string $resourceName, st
108110
'required' => $this->phpize($propertyValues, 'required', 'bool'),
109111
'identifier' => $this->phpize($propertyValues, 'identifier', 'bool'),
110112
'iri' => $this->phpize($propertyValues, 'iri', 'string'),
111-
'attributes' => $propertyValues['attributes'] ?? null,
113+
'attributes' => $propertyValues['attributes'] ?? [],
112114
'subresource' => $propertyValues['subresource'] ?? null,
113115
];
114116
}

tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ private function getBaseContainerBuilderProphecy()
644644

645645
foreach (['yaml', 'xml'] as $format) {
646646
$definitionProphecy = $this->prophesize(Definition::class);
647-
$definitionProphecy->addArgument(Argument::type('array'))->shouldBeCalled();
647+
$definitionProphecy->replaceArgument(0, Argument::type('array'))->shouldBeCalled();
648648
$containerBuilderProphecy->getDefinition('api_platform.metadata.extractor.'.$format)->willReturn($definitionProphecy->reveal())->shouldBeCalled();
649649
}
650650

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
resources:
2+
'App\Entity\Greeting':
3+
collectionOperations:
4+
get:
5+
filters:
6+
- 'greeting.search_filter'
7+
post: ~
8+
itemOperations:
9+
get: ~
10+
put: ~
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" ?>
2+
3+
<resources xmlns="https://api-platform.com/schema/metadata"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="https://api-platform.com/schema/metadata
6+
https://api-platform.com/schema/metadata/metadata-2.0.xsd">
7+
</resources>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" ?>
2+
3+
<resources xmlns="https://api-platform.com/schema/metadata"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="https://api-platform.com/schema/metadata
6+
https://api-platform.com/schema/metadata/metadata-2.0.xsd">
7+
<resource class="%dummy_class%"/>
8+
<resource class="%dummy_class%Bis"/>
9+
<resource
10+
class="%file_config_dummy_class%"
11+
shortName="thedummyshortname"
12+
description="Dummy resource"
13+
iri="someirischema"
14+
>
15+
<itemOperations>
16+
<itemOperation name="my_op_name">
17+
<attribute name="method">GET</attribute>
18+
</itemOperation>
19+
<itemOperation name="my_other_op_name">
20+
<attribute name="method">POST</attribute>
21+
</itemOperation>
22+
</itemOperations>
23+
<collectionOperations>
24+
<collectionOperation name="my_collection_op">
25+
<attribute name="method">POST</attribute>
26+
<attribute name="path">the/collection/path</attribute>
27+
</collectionOperation>
28+
</collectionOperations>
29+
<subresourceOperations>
30+
<subresourceOperation name="my_collection_subresource">
31+
<attribute name="path">the/subresource/path</attribute>
32+
</subresourceOperation>
33+
</subresourceOperations>
34+
<graphql>
35+
<operation name="query">
36+
<attribute name="normalization_context">
37+
<attribute name="groups">
38+
<attribute>graphql</attribute>
39+
</attribute>
40+
</attribute>
41+
</operation>
42+
</graphql>
43+
<attribute name="normalization_context">
44+
<attribute name="groups">
45+
<attribute>default</attribute>
46+
</attribute>
47+
</attribute>
48+
<attribute name="denormalization_context">
49+
<attribute name="groups">
50+
<attribute>default</attribute>
51+
</attribute>
52+
</attribute>
53+
<attribute name="hydra_context">
54+
<attribute name="@type">hydra:Operation</attribute>
55+
<attribute name="@hydra:title">File config Dummy</attribute>
56+
</attribute>
57+
58+
<property
59+
name="foo"
60+
description="The dummy foo"
61+
readable="true"
62+
writable="true"
63+
readableLink="false"
64+
writableLink="false"
65+
required="true"
66+
>
67+
<subresource collection="true" resourceClass="Foo" maxDepth="1" />
68+
<attribute name="foo">
69+
<attribute>Foo</attribute>
70+
</attribute>
71+
<attribute name="bar">
72+
<attribute>
73+
<attribute>Bar</attribute>
74+
</attribute>
75+
<attribute name="baz">Baz</attribute>
76+
</attribute>
77+
<attribute name="baz">Baz</attribute>
78+
</property>
79+
80+
<property name="name" description="The dummy name"/>
81+
</resource>
82+
</resources>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
resources:
2+
'%dummy_class%': ~
3+
'%dummy_class%Bis': ~
4+
'%file_config_dummy_class%':
5+
shortName: 'thedummyshortname'
6+
description: 'Dummy resource'
7+
itemOperations:
8+
my_op_name:
9+
method: 'GET'
10+
my_other_op_name:
11+
method: 'POST'
12+
collectionOperations:
13+
my_collection_op:
14+
method: 'POST'
15+
path: 'the/collection/path'
16+
subresourceOperations:
17+
my_collection_subresource:
18+
path: 'the/subresource/path'
19+
graphql:
20+
query:
21+
normalization_context:
22+
groups: ['graphql']
23+
attributes:
24+
normalization_context:
25+
groups: ['default']
26+
denormalization_context:
27+
groups: ['default']
28+
hydra_context:
29+
'@type': 'hydra:Operation'
30+
'@hydra:title': 'File config Dummy'
31+
# Use this syntax with Symfony YAML 3.4+:
32+
#'@hydra:title': !php/const ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\FileConfigDummy::HYDRA_TITLE
33+
iri: 'someirischema'
34+
properties:
35+
'foo':
36+
subresource: {collection: true, resourceClass: 'Foo', maxDepth: 1}
37+
description: 'The dummy foo'
38+
readable: true
39+
writable: true
40+
readableLink: false
41+
writableLink: false
42+
required: true
43+
attributes:
44+
'foo': ['Foo']
45+
'bar':
46+
'0': ['Bar']
47+
'baz': 'Baz'
48+
'baz': 'Baz'
49+
'name':
50+
description: 'The dummy name'
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
resources:
2+
'Foo': 'invalid'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
resources:
2+
'Foo':
3+
properties: 'invalid'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
resources:
2+
'Foo':
3+
properties:
4+
myprop: 'invalid'

0 commit comments

Comments
 (0)