Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cef7e5b

Browse files
committed
feature #18308 Added an ArgumentResolver with clean extension point (iltar, HeahDude)
This PR was merged into the 3.1-dev branch. Discussion ---------- Added an ArgumentResolver with clean extension point | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #17933 (pre-work), #1547, #10710 | License | MIT | Doc PR | symfony/symfony-docs#6422 **This PR is a follow up for and blocked by: #18187**, relates to #11457 by @wouterj. When reviewing, please take the last commit: [Added an ArgumentResolver with clean extension point](4c092b3) This PR provides: - The ability to tag your own `ArgumentValueResolverInterface`. This means that you can effectively expand on the argument resolving in the `HttpKernel` without having to implement your own `ArgumentResolver`. - The possibility to cache away argument metadata via a new `ArgumentMetadataFactory` which simply fetches the data from the cache, effectively omitting 1 reflection call per request. *Not implemented in this PR, but possible once this is merged.* - The possibility to add a PSR-7 adapter to resolve the correct request, avoids the paramconverters - The possibility to add a value resolver to fetch stuff from $request->query - Drupal could simplify [their argument resolving](https://github.com/drupal/drupal/blob/8.1.x/core/lib/Drupal/Core/Controller/ControllerResolver.php) by a lot - etc. The aim for this PR is to provide a 100% BC variant to add argument resolving in a clean way, this is shown by the 2 tests: `LegacyArgumentResolverTest` and `ArgumentResolverTest`. /cc @dawehner @larowlan if you have time, can you check the impact for Drupal? I think this should be a very simple change which should make it more maintainable. Commits ------- 1bf80c9 Improved DX for the ArgumentResolver f29bf4c Refactor ArgumentResolverTest cee5106 cs fixes cfcf764 Added an ArgumentResolver with clean extension point 360fc5f Extracting arg resolving from ControllerResolver
2 parents 162338e + 1bf80c9 commit cef7e5b

39 files changed

+1486
-98
lines changed

UPGRADE-3.1.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ HttpKernel
6666
deprecated and will be removed in Symfony 4.0. The inline fragment
6767
renderer should be used with object attributes.
6868

69+
* The `ControllerResolver::getArguments()` method has been deprecated and will
70+
be removed in 4.0. If you have your own `ControllerResolverInterface`
71+
implementation, you should inject either an `ArgumentResolverInterface`
72+
instance or the new `ArgumentResolver` in the `HttpKernel`.
73+
6974
Serializer
7075
----------
7176

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[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+
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
18+
/**
19+
* Gathers and configures the argument value resolvers.
20+
*
21+
* @author Iltar van der Berg <[email protected]>
22+
*/
23+
class ControllerArgumentValueResolverPass implements CompilerPassInterface
24+
{
25+
public function process(ContainerBuilder $container)
26+
{
27+
if (!$container->hasDefinition('argument_resolver')) {
28+
return;
29+
}
30+
31+
$definition = $container->getDefinition('argument_resolver');
32+
$argumentResolvers = $this->findAndSortTaggedServices('controller_argument.value_resolver', $container);
33+
$definition->replaceArgument(1, $argumentResolvers);
34+
}
35+
36+
/**
37+
* Finds all services with the given tag name and order them by their priority.
38+
*
39+
* @param string $tagName
40+
* @param ContainerBuilder $container
41+
*
42+
* @return array
43+
*/
44+
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
45+
{
46+
$services = $container->findTaggedServiceIds($tagName);
47+
48+
$sortedServices = array();
49+
foreach ($services as $serviceId => $tags) {
50+
foreach ($tags as $attributes) {
51+
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
52+
$sortedServices[$priority][] = new Reference($serviceId);
53+
}
54+
}
55+
56+
if (empty($sortedServices)) {
57+
return array();
58+
}
59+
60+
krsort($sortedServices);
61+
62+
// Flatten the array
63+
return call_user_func_array('array_merge', $sortedServices);
64+
}
65+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,6 @@ public function load(array $configs, ContainerBuilder $container)
150150

151151
$loader->load('debug.xml');
152152

153-
$definition = $container->findDefinition('http_kernel');
154-
$definition->replaceArgument(1, new Reference('debug.controller_resolver'));
155-
156153
// replace the regular event_dispatcher service with the debug one
157154
$definition = $container->findDefinition('event_dispatcher');
158155
$definition->setPublic(false);
@@ -173,6 +170,9 @@ public function load(array $configs, ContainerBuilder $container)
173170
'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener',
174171
'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener',
175172
'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver',
173+
'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver',
174+
'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata',
175+
'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadataFactory',
176176
'Symfony\\Component\\HttpKernel\\Event\\KernelEvent',
177177
'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent',
178178
'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent',

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass;
1515
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInitializersPass;
1616
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass;
17+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ControllerArgumentValueResolverPass;
1718
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass;
1819
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
1920
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
@@ -87,6 +88,7 @@ public function build(ContainerBuilder $container)
8788
$container->addCompilerPass(new FragmentRendererPass(), PassConfig::TYPE_AFTER_REMOVING);
8889
$container->addCompilerPass(new SerializerPass());
8990
$container->addCompilerPass(new PropertyInfoPass());
91+
$container->addCompilerPass(new ControllerArgumentValueResolverPass());
9092

9193
if ($container->getParameter('kernel.debug')) {
9294
$container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING);

src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@
1717
<argument type="service" id="logger" on-invalid="null" />
1818
</service>
1919

20-
<service id="debug.controller_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableControllerResolver">
21-
<argument type="service" id="controller_resolver" />
20+
<service id="debug.controller_resolver" decorates="controller_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableControllerResolver">
21+
<argument type="service" id="debug.controller_resolver.inner" />
22+
<argument type="service" id="debug.stopwatch" />
23+
<argument type="service" id="argument_resolver" />
24+
</service>
25+
26+
<service id="debug.argument_resolver" decorates="argument_resolver" class="Symfony\Component\HttpKernel\Controller\TraceableArgumentResolver">
27+
<argument type="service" id="debug.argument_resolver.inner" />
2228
<argument type="service" id="debug.stopwatch" />
2329
</service>
2430
</services>

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<argument type="service" id="event_dispatcher" />
1414
<argument type="service" id="controller_resolver" />
1515
<argument type="service" id="request_stack" />
16+
<argument type="service" id="argument_resolver" />
1617
</service>
1718

1819
<service id="request_stack" class="Symfony\Component\HttpFoundation\RequestStack" />

src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,29 @@
1717
<argument type="service" id="logger" on-invalid="ignore" />
1818
</service>
1919

20+
<service id="argument_metadata_factory" class="Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory" public="false" />
21+
22+
<service id="argument_resolver" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver" public="false">
23+
<argument type="service" id="argument_metadata_factory" />
24+
<argument type="collection" />
25+
</service>
26+
27+
<service id="argument_resolver.request_attribute" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver" public="false">
28+
<tag name="controller_argument.value_resolver" priority="100" />
29+
</service>
30+
31+
<service id="argument_resolver.request" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver" public="false">
32+
<tag name="controller_argument.value_resolver" priority="50" />
33+
</service>
34+
35+
<service id="argument_resolver.default" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver" public="false">
36+
<tag name="controller_argument.value_resolver" priority="-100" />
37+
</service>
38+
39+
<service id="argument_resolver.variadic" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver" public="false">
40+
<tag name="controller_argument.value_resolver" priority="-150" />
41+
</service>
42+
2043
<service id="response_listener" class="Symfony\Component\HttpKernel\EventListener\ResponseListener">
2144
<tag name="kernel.event_subscriber" />
2245
<argument>%kernel.charset%</argument>

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"symfony/config": "~2.8|~3.0",
2424
"symfony/event-dispatcher": "~2.8|~3.0",
2525
"symfony/http-foundation": "~3.1",
26-
"symfony/http-kernel": "~2.8|~3.0",
26+
"symfony/http-kernel": "~3.1",
2727
"symfony/polyfill-mbstring": "~1.0",
2828
"symfony/filesystem": "~2.8|~3.0",
2929
"symfony/finder": "~2.8|~3.0",

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ CHANGELOG
44
3.1.0
55
-----
66
* deprecated passing objects as URI attributes to the ESI and SSI renderers
7+
* added `Symfony\Component\HttpKernel\Controller\LegacyArgumentResolver`
8+
* deprecated `ControllerResolver::getArguments()`
9+
* made `ControllerResolver` extend the `LegacyArgumentResolver` for BC
10+
* added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface`
11+
* added `Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface` as argument to `HttpKernel`
12+
* added `Symfony\Component\HttpKernel\Controller\ArgumentResolver`
713

814
3.0.0
915
-----
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[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+
namespace Symfony\Component\HttpKernel\Controller;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver;
16+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestAttributeValueResolver;
17+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\RequestValueResolver;
18+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\VariadicValueResolver;
19+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
20+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface;
21+
22+
/**
23+
* Responsible for resolving the arguments passed to an action.
24+
*
25+
* @author Iltar van der Berg <[email protected]>
26+
*/
27+
final class ArgumentResolver implements ArgumentResolverInterface
28+
{
29+
private $argumentMetadataFactory;
30+
31+
/**
32+
* @var ArgumentValueResolverInterface[]
33+
*/
34+
private $argumentValueResolvers;
35+
36+
public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, array $argumentValueResolvers = array())
37+
{
38+
$this->argumentMetadataFactory = $argumentMetadataFactory ?: new ArgumentMetadataFactory();
39+
$this->argumentValueResolvers = $argumentValueResolvers ?: array(
40+
new RequestAttributeValueResolver(),
41+
new RequestValueResolver(),
42+
new DefaultValueResolver(),
43+
new VariadicValueResolver(),
44+
);
45+
}
46+
47+
/**
48+
* {@inheritdoc}
49+
*/
50+
public function getArguments(Request $request, $controller)
51+
{
52+
$arguments = array();
53+
54+
foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) {
55+
foreach ($this->argumentValueResolvers as $resolver) {
56+
if (!$resolver->supports($request, $metadata)) {
57+
continue;
58+
}
59+
60+
$resolved = $resolver->resolve($request, $metadata);
61+
62+
if (!$resolved instanceof \Generator) {
63+
throw new \InvalidArgumentException(sprintf('%s::resolve() must yield at least one value.', get_class($resolver)));
64+
}
65+
66+
foreach ($resolved as $append) {
67+
$arguments[] = $append;
68+
}
69+
70+
// continue to the next controller argument
71+
continue 2;
72+
}
73+
74+
$representative = $controller;
75+
76+
if (is_array($representative)) {
77+
$representative = sprintf('%s::%s()', get_class($representative[0]), $representative[1]);
78+
} elseif (is_object($representative)) {
79+
$representative = get_class($representative);
80+
}
81+
82+
throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $representative, $metadata->getName()));
83+
}
84+
85+
return $arguments;
86+
}
87+
}

0 commit comments

Comments
 (0)