Skip to content

Commit 2d49eed

Browse files
committed
[HttpKernel] Add a ContainerControllerResolver (psr-11)
1 parent 8156089 commit 2d49eed

File tree

2 files changed

+224
-0
lines changed

2 files changed

+224
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Psr\Log\LoggerInterface;
16+
17+
/**
18+
* A controller resolver searching for a controller in a psr-11 container when using the "service:method" notation.
19+
*
20+
* @author Fabien Potencier <fabien@symfony.com>
21+
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
22+
*/
23+
class ContainerControllerResolver extends ControllerResolver
24+
{
25+
protected $container;
26+
27+
public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
28+
{
29+
$this->container = $container;
30+
31+
parent::__construct($logger);
32+
}
33+
34+
/**
35+
* Returns a callable for the given controller.
36+
*
37+
* @param string $controller A Controller string
38+
*
39+
* @return mixed A PHP callable
40+
*
41+
* @throws \LogicException When the name could not be parsed
42+
* @throws \InvalidArgumentException When the controller class does not exist
43+
*/
44+
protected function createController($controller)
45+
{
46+
if (false !== strpos($controller, '::')) {
47+
return parent::createController($controller);
48+
}
49+
50+
if (1 == substr_count($controller, ':')) {
51+
// controller in the "service:method" notation
52+
list($service, $method) = explode(':', $controller, 2);
53+
54+
return array($this->container->get($service), $method);
55+
}
56+
57+
if ($this->container->has($controller) && method_exists($service = $this->container->get($controller), '__invoke')) {
58+
// invokable controller in the "service" notation
59+
return $service;
60+
}
61+
62+
throw new \LogicException(sprintf('Unable to parse the controller name "%s".', $controller));
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
protected function instantiateController($class)
69+
{
70+
if ($this->container->has($class)) {
71+
return $this->container->get($class);
72+
}
73+
74+
return parent::instantiateController($class);
75+
}
76+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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\Tests\Controller;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Psr\Log\LoggerInterface;
16+
use Symfony\Component\HttpFoundation\Request;
17+
use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver;
18+
19+
class ContainerControllerResolverTest extends ControllerResolverTest
20+
{
21+
public function testGetControllerService()
22+
{
23+
$container = $this->createMockContainer();
24+
$container->expects($this->once())
25+
->method('get')
26+
->with('foo')
27+
->will($this->returnValue($this))
28+
;
29+
30+
$resolver = $this->createControllerResolver(null, $container);
31+
$request = Request::create('/');
32+
$request->attributes->set('_controller', 'foo:controllerMethod1');
33+
34+
$controller = $resolver->getController($request);
35+
36+
$this->assertInstanceOf(get_class($this), $controller[0]);
37+
$this->assertSame('controllerMethod1', $controller[1]);
38+
}
39+
40+
public function testGetControllerInvokableService()
41+
{
42+
$invokableController = new InvokableController('bar');
43+
44+
$container = $this->createMockContainer();
45+
$container->expects($this->once())
46+
->method('has')
47+
->with('foo')
48+
->will($this->returnValue(true))
49+
;
50+
$container->expects($this->once())
51+
->method('get')
52+
->with('foo')
53+
->will($this->returnValue($invokableController))
54+
;
55+
56+
$resolver = $this->createControllerResolver(null, $container);
57+
$request = Request::create('/');
58+
$request->attributes->set('_controller', 'foo');
59+
60+
$controller = $resolver->getController($request);
61+
62+
$this->assertEquals($invokableController, $controller);
63+
}
64+
65+
public function testGetControllerInvokableServiceWithClassNameAsName()
66+
{
67+
$invokableController = new InvokableController('bar');
68+
$className = __NAMESPACE__.'\InvokableController';
69+
70+
$container = $this->createMockContainer();
71+
$container->expects($this->once())
72+
->method('has')
73+
->with($className)
74+
->will($this->returnValue(true))
75+
;
76+
$container->expects($this->once())
77+
->method('get')
78+
->with($className)
79+
->will($this->returnValue($invokableController))
80+
;
81+
82+
$resolver = $this->createControllerResolver(null, $container);
83+
$request = Request::create('/');
84+
$request->attributes->set('_controller', $className);
85+
86+
$controller = $resolver->getController($request);
87+
88+
$this->assertEquals($invokableController, $controller);
89+
}
90+
91+
/**
92+
* @dataProvider getUndefinedControllers
93+
*/
94+
public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null)
95+
{
96+
// All this logic needs to be duplicated, since calling parent::testGetControllerOnNonUndefinedFunction will override the expected excetion and not use the regex
97+
$resolver = $this->createControllerResolver();
98+
if (method_exists($this, 'expectException')) {
99+
$this->expectException($exceptionName);
100+
$this->expectExceptionMessageRegExp($exceptionMessage);
101+
} else {
102+
$this->setExpectedExceptionRegExp($exceptionName, $exceptionMessage);
103+
}
104+
105+
$request = Request::create('/');
106+
$request->attributes->set('_controller', $controller);
107+
$resolver->getController($request);
108+
}
109+
110+
public function getUndefinedControllers()
111+
{
112+
return array(
113+
array('foo', \LogicException::class, '/Unable to parse the controller name "foo"\./'),
114+
array('oof::bar', \InvalidArgumentException::class, '/Class "oof" does not exist\./'),
115+
array('stdClass', \LogicException::class, '/Unable to parse the controller name "stdClass"\./'),
116+
array(
117+
'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar',
118+
\InvalidArgumentException::class,
119+
'/.?[cC]ontroller(.*?) for URI "\/" is not callable\.( Expected method(.*) Available methods)?/',
120+
),
121+
);
122+
}
123+
124+
protected function createControllerResolver(LoggerInterface $logger = null, ContainerInterface $container = null)
125+
{
126+
if (!$container) {
127+
$container = $this->createMockContainer();
128+
}
129+
130+
return new ContainerControllerResolver($container, $logger);
131+
}
132+
133+
protected function createMockContainer()
134+
{
135+
return $this->getMockBuilder(ContainerInterface::class)->getMock();
136+
}
137+
}
138+
139+
class InvokableController
140+
{
141+
public function __construct($bar) // mandatory argument to prevent automatic instantiation
142+
{
143+
}
144+
145+
public function __invoke()
146+
{
147+
}
148+
}

0 commit comments

Comments
 (0)