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

Skip to content

Commit 4c092b3

Browse files
author
Iltar van der Berg
committed
Added an ArgumentResolver with clean extension point
1 parent b3ed1f0 commit 4c092b3

26 files changed

+952
-93
lines changed

UPGRADE-3.1.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ HttpKernel
3434
* The `ControllerResolver::getArguments()` method is deprecated and will be
3535
removed in 4.0. If you have your own `ControllerResolverInterface`
3636
implementation, you should replace this method by implementing the
37-
`ArgumentResolverInterface` and injecting it in the HttpKernel.
37+
`ArgumentResolverInterface` and injecting it in the HttpKernel, or using
38+
the `ArgumentResolver` and injecting this in the `HttpKernel`.
3839

3940
Serializer
4041
----------
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function process(ContainerBuilder $container)
29+
{
30+
if (!$container->hasDefinition('argument_resolver')) {
31+
return;
32+
}
33+
34+
$definition = $container->getDefinition('argument_resolver');
35+
$argumentResolvers = $this->findAndSortTaggedServices('controller_argument.value_resolver', $container);
36+
$definition->replaceArgument(1, $argumentResolvers);
37+
}
38+
39+
/**
40+
* Finds all services with the given tag name and order them by their priority.
41+
*
42+
* @param string $tagName
43+
* @param ContainerBuilder $container
44+
*
45+
* @return array
46+
*/
47+
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
48+
{
49+
$services = $container->findTaggedServiceIds($tagName);
50+
51+
$sortedServices = array();
52+
foreach ($services as $serviceId => $tags) {
53+
foreach ($tags as $attributes) {
54+
$priority = isset($attributes['priority']) ? $attributes['priority'] : 0;
55+
$sortedServices[$priority][] = new Reference($serviceId);
56+
}
57+
}
58+
59+
if (empty($sortedServices)) {
60+
return array();
61+
}
62+
63+
krsort($sortedServices);
64+
65+
// Flatten the array
66+
return call_user_func_array('array_merge', $sortedServices);
67+
}
68+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public function load(array $configs, ContainerBuilder $container)
170170
'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener',
171171
'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener',
172172
'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver',
173-
'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver',
173+
'Symfony\\Component\\HttpKernel\\Controller\\LegacyArgumentResolver',
174174
'Symfony\\Component\\HttpKernel\\Event\\KernelEvent',
175175
'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent',
176176
'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/web.xml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,28 @@
1717
<argument type="service" id="logger" on-invalid="ignore" />
1818
</service>
1919

20-
<service id="argument_resolver" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver" public="false" />
20+
<service id="argument_resolver" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver" public="false">
21+
<argument type="service" id="argument_metadata_actory" />
22+
<argument type="collection" />
23+
</service>
24+
25+
<service id="argument_metadata_actory" class="Symfony\Component\HttpKernel\ControllerMetadata\Argument\ArgumentMetadataFactory" public="false" />
26+
27+
<service id="argument_value_resolver.argument_from_attribute" class="Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\ArgumentFromAttribute" public="false">
28+
<tag name="controller_argument.value_resolver" priority="100" />
29+
</service>
30+
31+
<service id="argument_value_resolver.variadic_argument_from_attribute" class="Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\VariadicArgumentFromAttribute" public="false">
32+
<tag name="controller_argument.value_resolver" priority="75" />
33+
</service>
34+
35+
<service id="argument_value_resolver.argument_is_request" class="Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\ArgumentIsRequest" public="false">
36+
<tag name="controller_argument.value_resolver" priority="50" />
37+
</service>
38+
39+
<service id="argument_value_resolver.default_argument_value" class="Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\DefaultArgumentValue" public="false">
40+
<tag name="controller_argument.value_resolver" priority="-100" />
41+
</service>
2142

2243
<service id="response_listener" class="Symfony\Component\HttpKernel\EventListener\ResponseListener">
2344
<tag name="kernel.event_subscriber" />

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ CHANGELOG
44
3.1.0
55
-----
66
* deprecated passing objects as URI attributes to the ESI and SSI renderers
7-
* Added an `ArgumentResolver` with `getArguments()` and the respective interface `ArgumentResolverInterface`
8-
* Deprecated `ControllerResolver::getArguments()`, which uses the `ArgumentResolver` as BC layer by extending it
9-
* The `HttpKernel` now accepts an additional argument for an `ArgumentResolver`
7+
* Added a `LegacyArgumentResolver` with `getArguments()` and the respective interface `ArgumentResolverInterface`
8+
* Deprecated `ControllerResolver::getArguments()`, which uses the `LegacyArgumentResolver` as BC layer by extending it
9+
* The `HttpKernel` now accepts an additional argument for an `ArgumentResolverInterface`
10+
* Added the `ArgumentResolver` which features an extension point to resolve arguments in a more dynamic way
1011

1112
3.0.0
1213
-----

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,56 +12,69 @@
1212
namespace Symfony\Component\HttpKernel\Controller;
1313

1414
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\ControllerMetadata\Argument\ArgumentMetadataFactoryInterface;
1516

1617
/**
17-
* Responsible for the creation of the action arguments.
18+
* Responsible for the resolving of arguments passed to an action.
1819
*
19-
* @author Fabien Potencier <fabien@symfony.com>
20+
* @author Iltar van der Berg <kjarli@gmail.com>
2021
*/
21-
class ArgumentResolver implements ArgumentResolverInterface
22+
final class ArgumentResolver implements ArgumentResolverInterface
2223
{
2324
/**
24-
* {@inheritdoc}
25+
* @var ArgumentMetadataFactoryInterface
2526
*/
26-
public function getArguments(Request $request, $controller)
27-
{
28-
if (is_array($controller)) {
29-
$r = new \ReflectionMethod($controller[0], $controller[1]);
30-
} elseif (is_object($controller) && !$controller instanceof \Closure) {
31-
$r = new \ReflectionObject($controller);
32-
$r = $r->getMethod('__invoke');
33-
} else {
34-
$r = new \ReflectionFunction($controller);
35-
}
27+
private $argumentMetadataFactory;
28+
29+
/**
30+
* @var ArgumentValueResolverInterface[]
31+
*/
32+
private $argumentValueResolvers;
3633

37-
return $this->doGetArguments($request, $controller, $r->getParameters());
34+
/**
35+
* @param ArgumentMetadataFactoryInterface $argumentMetadataFactory
36+
* @param ArgumentValueResolverInterface[] $argumentValueResolvers
37+
*/
38+
public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory = null, array $argumentValueResolvers = [])
39+
{
40+
$this->argumentMetadataFactory = $argumentMetadataFactory;
41+
$this->argumentValueResolvers = $argumentValueResolvers;
3842
}
3943

40-
protected function doGetArguments(Request $request, $controller, array $parameters)
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function getArguments(Request $request, $controller)
4148
{
42-
$attributes = $request->attributes->all();
4349
$arguments = array();
44-
foreach ($parameters as $param) {
45-
if (array_key_exists($param->name, $attributes)) {
46-
if (PHP_VERSION_ID >= 50600 && $param->isVariadic() && is_array($attributes[$param->name])) {
47-
$arguments = array_merge($arguments, array_values($attributes[$param->name]));
48-
} else {
49-
$arguments[] = $attributes[$param->name];
50+
51+
foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) {
52+
$isResolved = false;
53+
foreach ($this->argumentValueResolvers as $resolver) {
54+
if ($resolver->supports($request, $metadata)) {
55+
$resolved = $resolver->getValue($request, $metadata);
56+
57+
// variadic is a special case, always being the last and being an array
58+
if (is_array($resolved) && $metadata->isVariadic()) {
59+
return array_merge($arguments, $resolved);
60+
}
61+
62+
$arguments[] = $resolved;
63+
$isResolved = true;
64+
break;
5065
}
51-
} elseif ($param->getClass() && $param->getClass()->isInstance($request)) {
52-
$arguments[] = $request;
53-
} elseif ($param->isDefaultValueAvailable()) {
54-
$arguments[] = $param->getDefaultValue();
55-
} else {
66+
}
67+
68+
if (!$isResolved) {
69+
$repr = $controller;
70+
5671
if (is_array($controller)) {
5772
$repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]);
5873
} elseif (is_object($controller)) {
5974
$repr = get_class($controller);
60-
} else {
61-
$repr = $controller;
6275
}
6376

64-
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).', $repr, $param->name));
77+
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).', $repr, $metadata->getArgumentName()));
6578
}
6679
}
6780

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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\ArgumentValueResolver;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\ControllerMetadata\Argument\ArgumentMetadataInterface;
17+
18+
/**
19+
* Grabs a non-variadic value from the request and returns it.
20+
*
21+
* Opposite of {@see VariadicArgumentFromAttribute}.
22+
*
23+
* @author Iltar van der Berg <[email protected]>
24+
*/
25+
final class ArgumentFromAttribute implements ArgumentValueResolverInterface
26+
{
27+
/**
28+
* {@inheritdoc}
29+
*/
30+
public function supports(Request $request, ArgumentMetadataInterface $argument)
31+
{
32+
return !$argument->isVariadic() && $request->attributes->has($argument->getArgumentName());
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function getValue(Request $request, ArgumentMetadataInterface $argument)
39+
{
40+
return $request->attributes->get($argument->getArgumentName());
41+
}
42+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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\ArgumentValueResolver;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\ControllerMetadata\Argument\ArgumentMetadataInterface;
17+
18+
/**
19+
* Supports the same instance as the request object passed along.
20+
*
21+
* @author Iltar van der Berg <[email protected]>
22+
*/
23+
final class ArgumentIsRequest implements ArgumentValueResolverInterface
24+
{
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function supports(Request $request, ArgumentMetadataInterface $argument)
29+
{
30+
return $argument->getArgumentType() === Request::class || is_subclass_of($request, $argument->getArgumentType());
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function getValue(Request $request, ArgumentMetadataInterface $argument)
37+
{
38+
return $request;
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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\ArgumentValueResolver;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\ControllerMetadata\Argument\ArgumentMetadataInterface;
17+
18+
/**
19+
* Returns the default value defined in the action signature if present and no value has been given.
20+
*
21+
* @author Iltar van der Berg <[email protected]>
22+
*/
23+
final class DefaultArgumentValue implements ArgumentValueResolverInterface
24+
{
25+
/**
26+
* {@inheritdoc}
27+
*/
28+
public function supports(Request $request, ArgumentMetadataInterface $argument)
29+
{
30+
return $argument->hasDefaultValue() && !$request->attributes->has($argument->getArgumentName());
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function getValue(Request $request, ArgumentMetadataInterface $argument)
37+
{
38+
return $argument->getDefaultValue();
39+
}
40+
}

0 commit comments

Comments
 (0)