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

Skip to content

Commit e6d8bc5

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

File tree

3 files changed

+100
-12
lines changed

3 files changed

+100
-12
lines changed

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,7 @@ public function testGetControllerInvokableService()
101101
*/
102102
public function testGetControllerOnNonUndefinedFunction($controller, $exceptionName = null, $exceptionMessage = null)
103103
{
104-
$this->setExpectedException($exceptionName, $exceptionMessage);
105-
106-
parent::testGetControllerOnNonUndefinedFunction($controller);
104+
parent::testGetControllerOnNonUndefinedFunction($controller, $exceptionName, $exceptionMessage);
107105
}
108106

109107
public function getUndefinedControllers()
@@ -113,9 +111,9 @@ public function getUndefinedControllers()
113111
array('foo::bar', '\InvalidArgumentException', 'Class "foo" does not exist.'),
114112
array('stdClass', '\LogicException', 'Unable to parse the controller name "stdClass".'),
115113
array(
116-
'Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar',
114+
'Symfony\Component\HttpKernel\Tests\Controller\ControllerTest::bar',
117115
'\InvalidArgumentException',
118-
'Controller "Symfony\Component\HttpKernel\Tests\Controller\ControllerResolverTest::bar" for URI "/" is not callable.',
116+
'The controller for URI "/" is not callable. Expected method "bar" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"',
119117
),
120118
);
121119
}

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

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ 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+
$message = sprintf('The controller for URI "%s" is not callable. ', $request->getPathInfo());
84+
85+
$message .= $this->getControllerError($callable);
86+
87+
throw new \InvalidArgumentException($message);
8488
}
8589

8690
return $callable;
@@ -167,4 +171,68 @@ protected function instantiateController($class)
167171
{
168172
return new $class();
169173
}
174+
175+
private function getControllerError($callable)
176+
{
177+
if (is_string($callable) && false !== strpos($callable, '::')) {
178+
$callable = explode('::', $callable);
179+
}
180+
181+
if (is_string($callable) && !function_exists($callable)) {
182+
return sprintf('Function %s does not exist.', $callable);
183+
}
184+
185+
if (is_array($callable)) {
186+
if (2 !== count($callable)) {
187+
return sprintf('Invalid format for controller, expected array(controller, method)');
188+
}
189+
190+
list($controller, $method) = $callable;
191+
192+
if (is_string($controller) && !class_exists($controller)) {
193+
return sprintf('Class "%s" does not exist.', $controller);
194+
}
195+
196+
$reflection = new \ReflectionClass($controller);
197+
198+
if (!method_exists($controller, $method)) {
199+
$collection = array_map(
200+
function (\ReflectionMethod $method) {
201+
return $method->getName();
202+
},
203+
$reflection->getMethods(\ReflectionMethod::IS_PUBLIC)
204+
);
205+
206+
$alternatives = array();
207+
208+
foreach ($collection as $item) {
209+
$lev = levenshtein($method, $item);
210+
211+
if ($lev <= strlen($method) / 3 || false !== strpos($item, $method)) {
212+
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
213+
}
214+
}
215+
216+
asort($alternatives);
217+
218+
$message = sprintf('Expected method "%s" on class "%s"', $method, $reflection->getName());
219+
220+
if (count($alternatives) > 0) {
221+
$message .= sprintf(', did you mean "%s"?', implode('", "', array_keys($alternatives)));
222+
} else {
223+
$message .= sprintf('. Available methods: "%s"', implode('", "', $collection));
224+
}
225+
226+
return $message;
227+
}
228+
229+
return sprintf(
230+
'Method "%s" on class "%s" should be public and non-abstract',
231+
$method,
232+
$reflection->getName()
233+
);
234+
}
235+
236+
return sprintf('Invalid type for controller given, expected string or array, got %s', gettype($callable));
237+
}
170238
}

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

Lines changed: 28 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,13 @@ 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('foo', 'InvalidArgumentException', 'Unable to find controller "foo".'),
129+
array('foo::bar', 'InvalidArgumentException', 'Class "foo" does not exist.'),
130+
array('stdClass', 'InvalidArgumentException', 'Unable to find controller "stdClass".'),
131+
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"?'),
132+
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'),
133+
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'),
134+
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"'),
132135
);
133136
}
134137

@@ -236,3 +239,22 @@ protected function controllerMethod5(Request $request)
236239
function some_controller_function($foo, $foobar)
237240
{
238241
}
242+
243+
class ControllerTest
244+
{
245+
public function publicAction()
246+
{
247+
}
248+
249+
private function privateAction()
250+
{
251+
}
252+
253+
protected function protectedAction()
254+
{< 5C87 /div>
255+
}
256+
257+
public static function staticAction()
258+
{
259+
}
260+
}

0 commit comments

Comments
 (0)
0