8000 [FrameworkBundle] Add new "routing.controller" tag to inject autowire… · symfony/symfony@7438115 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7438115

Browse files
[FrameworkBundle] Add new "routing.controller" tag to inject autowired services into actions
1 parent 837e23a commit 7438115

File tree

5 files changed

+170
-0
lines changed

5 files changed

+170
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Argument\ServiceClosureArgument;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
16+
use Symfony\Component\DependencyInjection\AutowirableReference;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\ContainerInterface;
19+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
20+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
21+
use Symfony\Component\DependencyInjection\LazyProxy\InheritanceProxyHelper;
22+
use Symfony\Component\DependencyInjection\Reference;
23+
use Symfony\Component\DependencyInjection\ServiceLocator;
24+
25+
/**
26+
* Creates the controller arguments service locator required by "argument_resolver.service".
27+
*
28+
* @author Nicolas Grekas <p@tchwork.com>
29+
*/
30+
class RoutingControllerPass implements CompilerPassInterface
31+
{
32+
public function process(ContainerBuilder $container)
33+
{
34+
if (false === $container->hasDefinition('argument_resolver.service')) {
35+
return;
36+
}
37+
38+
$serviceResolver = $container->getDefinition('argument_resolver.service');
39+
$parameterBag = $container->getParameterBag();
40+
$controllers = array();
41+
42+
foreach ($container->findTaggedServiceIds('routing.controller') as $id => $tags) {
43+
$def = $container->getDefinition($id);
44+
$class = $def->getClass();
45+
46+
if ($def->isAbstract()) {
47+
continue;
48+
}
49+
50+
while (!$class && $def instanceof ChildDefinition) {
51+
$def = $container->findDefinition($def->getParent());
52+
$class = $def->getClass();
53+
}
54+
$class = $parameterBag->resolveValue($class);
55+
56+
if (!$r = $container->getReflectionClass($class)) {
57+
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
58+
}
59+
60+
$methods = array();
61+
foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $r) {
62+
$methods[strtolower($r->name)] = $r;
63+
}
64+
65+
$actions = array();
66+
foreach ($tags as $attributes) {
67+
if (!isset($attributes['action'])) {
68+
throw new InvalidArgumentException(sprintf('Service "%s" must define the "action" attribute on "routing.controller" tags.', $id));
69+
}
70+
$action = strtolower($attributes['action']);
71+
72+
if (false !== strpos($action, '*')) {
73+
$regex = '/^'.str_replace('\*', '.*', preg_quote($action, '/')).'$/';
74+
$found = false;
75+
76+
foreach ($methods as $name => $r) {
77+
if (preg_match($regex, $name)) {
78+
$actions[$methods[$action]->name] = $methods[$action];
79+
$found = true;
80+
}
81+
}
82+
83+
if (!$found) {
84+
$container->log($this, sprintf('No "action" found for service "%s": class "%s" has no "%s" methods.', $id, $class, $attributes['action']));
85+
}
86+
} elseif (isset($methods[$action])) {
87+
$actions[$methods[$action]->name] = $methods[$action];
88+
} else {
89+
throw new InvalidArgumentException(sprintf('Invalid "action" for service "%s": no "%s" method found on class "%s".', $id, $class, $attributes['action']));
90+
}
91+
}
92+
93+
if (!$actions) {
94+
continue;
95+
}
96+
97+
foreach ($actions as $name => $r) {
98+
$args = array();
99+
foreach ($r->getParameters() as $p) {
100+
if ($type = InheritanceProxyHelper::getTypeHint($r, $p, true)) {
101+
$args[$p->name] = new ServiceClosureArgument(new AutowirableReference($type, $type, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, false));
102+
}
103+
}
104+
if ($args) {
105+
$argsId = sprintf('arguments.%s:%s', $id, $name);
106+
$container->register($argsId, ServiceLocator::class)->addArgument($args)->setPublic(false);
107+
$controllers[$id.':'.$name] = new Reference($argsId);
108+
}
109+
}
110+
}
111+
112+
$serviceResolver->replaceArgument(0, new ServiceLocatorArgument($controllers));
113+
}
114+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class UnusedTagsPass implements CompilerPassInterface
3434
'kernel.event_subscriber',
3535
'kernel.fragment_renderer',
3636
'monolog.logger',
37+
'routing.controller',
3738
'routing.expression_language_provider',
3839
'routing.loader',
3940
'security.expression_language_provider',

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\PropertyInfoPass;
2323
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass;
2424
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
25+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingControllerPass;
2526
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass;
2627
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
2728
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
@@ -76,6 +77,7 @@ public function build(ContainerBuilder $container)
7677
{
7778
parent::build($container);
7879

80+
$container->addCompilerPass(new RoutingControllerPass());
7981
$container->addCompilerPass(new RoutingResolverPass());
8082
$container->addCompilerPass(new ProfilerPass());
8183
// must be registered before removing private services as some might be listeners/subscribers

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
<tag name="controller.argument_value_resolver" priority="50" />
3737
</service>
3838

39+
<service id="argument_resolver.service" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver" public="false">
40+
<tag name="controller.argument_value_resolver" priority="-50" />
41+
<argument /> <!-- service locator -->
42+
</service>
43+
3944
<service id="argument_resolver.default" class="Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver" public="false">
4045
<tag name="controller.argument_value_resolver" priority="-100" />
4146
</service>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\ArgumentResolver;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
17+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
18+
19+
/**
20+
* Yields a service keyed by _controller and argument name.
21+
*
22+
* @author Nicolas Grekas <p@tchwork.com>
23+
*/
24+
final class ServiceValueResolver implements ArgumentValueResolverInterface
25+
{
26+
private $container;
27+
28+
public function __construct(ContainerInterface $container)
29+
{
30+
$this->container = $container;
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function supports(Request $request, ArgumentMetadata $argument)
37+
{
38+
return is_string($controller = $request->attributes->get('_controller')) && $this->container->has($controller) && $this->container->get($controller)->has($argument->getName());
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function resolve(Request $request, ArgumentMetadata $argument)
45+
{
46+
yield $this->container->get($request->attributes->get('_controller'))->get($argument->getName());
47+
}
48+
}

0 commit comments

Comments
 (0)
0