8000 Add better error message when controller action isn't callable · symfony/symfony@c7ff100 · GitHub
[go: up one dir, main page]

Skip to content

Commit c7ff100

Browse files
committed
Add better error message when controller action isn't callable
1 parent 98b714b commit c7ff100

File tree

3 files changed

+101
-13
lines changed

3 files changed

+101
-13
lines changed

src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerResolverTest.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,25 @@ public function testGetControllerInvokableService()
101101
*/
102102
public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null)
103103
{
104-
$this->setExpectedException($exceptionName, $exceptionMessage);
104+
// All this logic needs to be duplicated, since calling parent::testGetControllerOnNonUndefinedFunction will override the expected excetion and not use the regex
105+
$resolver = $this->createControllerResolver();
106+
$this->setExpectedExceptionRegExp($exceptionName, $exceptionMessage);
105107

106-
parent::testGetControllerOnNonUndefinedFunction($controller);
108+
$request = Request::create('/');
109+
$request->attributes->set('_controller', $controller);
110+
$resolver->getController($request);
107111
}
108112

109113
public function getUndefinedControllers()
110114
{
111115
return array(
112-
array('foo', '\LogicException', 'Unable to parse the controller name "foo".'),
113-
array('foo::bar', '\InvalidArgumentException', 'Class "foo" does not exist.'),
114-
array('stdClass', '\LogicException', 'Unable to parse the controller name "stdClass".'),
116+
array('foo', '\LogicException', '/Unable to parse the controller name "foo"\./'),
117+
array('foo::bar', '\InvalidArgumentException', '/Class "foo" does not exist\./'),
118+
array('stdClass', '\LogicException', '/Unable to parse the controller name "stdClass"\./'),
115119
array(
116120
'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar',
117121
'\InvalidArgumentException',
118-
'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar" for URI "/" is not callable.',
122+
'/.?[cC]ontroller(.*?) for URI "\/" is not callable\.( Expected method(.*) Available methods)?/',
119123
),
120124
);
121125
}

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

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public function getController(Request $request)
8080
$callable = $this->createController($controller);
8181

8282
if (!is_callable($callable)) {
83-
throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', $controller, $request->getPathInfo()));
83+
throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $this->getControllerError($callable)));
8484
}
8585

8686
return $callable;
@@ -167,4 +167,65 @@ protected function instantiateController($class)
167167
{
168168
return new $class();
169169
}
170+
171+
private function getControllerError($callable)
172+
{
173+
if (is_string($callable)) {
174+
if (false !== strpos($callable, '::')) {
175+
$callable = explode('::', $callable);
176+
}
177+
178+
if (class_exists($callable) && !method_exists($callable, '__invoke')) {
179+
return sprintf('Class "%s" does not have a method "__invoke".', $callable);
180+
}
181+
182+
if (!function_exists($callable)) {
183+
return sprintf('Function "%s" does not exist.', $callable);
184+
}
185+
}
186+
187+
if (!is_array($callable)) {
188+
return sprintf('Invalid type for controller given, expected string or array, got "%s".', gettype($callable));
189+
}
190+
191+
if (2 !== count($callable)) {
192+
return sprintf('Invalid format for controller, expected array(controller, method) or controller::method.');
193+
}
194+
195+
list($controller, $method) = $callable;
196+
197+
if (is_string($controller) && !class_exists($controller)) {
198+
return sprintf('Class "%s" does not exist.', $controller);
199+
}
200+
201+
$className = is_object($controller) ? get_class($controller) : $controller;
202+
203+
if (method_exists($controller, $method)) {
204+
return sprintf('Method "%s" on class "%s" should be public and non-abstract.', $method, $className);
205+
}
206+
207+
$collection = get_class_methods($controller);
208+
209+
$alternatives = array();
210+
211+
foreach ($collection as $item) {
212+
$lev = levenshtein($method, $item);
213+
214+
if ($lev <= strlen($method) / 3 || false !== strpos($item, $method)) {
215+
$alternatives[] = $item;
216+
}
217+
}
218+
219+
asort($alternatives);
220+
221+
$message = sprintf('Expected method "%s" on class "%s"', $method, $className);
222+
223+
if (count($alternatives) > 0) {
224+
$message .= sprintf(', did you mean "%s"?', implode('", "', $alternatives));
225+
} else {
226+
$message .= sprintf('. Available methods: "%s".', implode('", "', $collection));
227+
}
228+
229+
return $message;
230+
}
170231
}

src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ public function testGetControllerWithFunction()
111111

112112
/**
113113
* @dataProvider getUndefinedControllers
114-
* @expectedException \InvalidArgumentException
115114
*/
116-
public function testGetControllerOnNonUndefinedFunction($controller)
115+
public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null)
117116
{
118117
$resolver = $this->createControllerResolver();
118+
$this->setExpectedException($exceptionName, $exceptionMessage);
119119

120120
$request = Request::create('/');
121121
$request->attributes->set('_controller', $controller);
@@ -125,10 +125,14 @@ public function testGetControllerOnNonUndefinedFunction($controller)
125125
public function getUndefinedControllers()
126126
{
127127
return array(
128-
array('foo'),
129-
array('foo::bar'),
130-
array('stdClass'),
131-
array('Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar'),
128+
array(1, 'InvalidArgumentException', 'Unable to find controller "1".'),
129+
array('foo', 'InvalidArgumentException', 'Unable to find controller "foo".'),
130+
array('foo::bar', 'InvalidArgumentException', 'Class "foo" does not exist.'),
131+
array('stdClass', 'InvalidArgumentException', 'Unable to find controller "stdClass".'),
132+
array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::staticsAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Expected method "staticsAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest", did you mean "staticAction"?'),
133+
array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::privateAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Method "privateAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'),
134+
array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::protectedAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Method "protectedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest" should be public and non-abstract'),
135+
array('Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::undefinedAction', 'InvalidArgumentException', 'The controller for URI "/" is not callable. Expected method "undefinedAction" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"'),
132136
);
133137
}
134138

@@ -236,3 +240,22 @@ protected function controllerMethod5(Request $request)
236240
function some_controller_function($foo, $foobar)
237241
{
238242
}
243+
244+
class ControllerTest
245+
{
246+
public function publicAction()
247+
{
248+
}
249+
250+
private function privateAction()
251+
{
252+
}
253+
254+
protected function protectedAction()
255+
{
256+
}
257+
258+
public static function staticAction()
259+
{
260+
}
261+
}

0 commit comments

Comments
 (0)
0