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

Skip to content

Commit 1cabe34

Browse files
committed
Add better error message when controller action isn't callable
1 parent a469c56 commit 1cabe34

File tree

3 files changed

+89
-13
lines changed

3 files changed

+89
-13
lines changed

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

Lines changed: 4 additions & 6 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,10 +111,10 @@ 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.'
119-
)
116+
'The controller for URI "/" is not callable. Expected method "bar" on class "Symfony\Component\HttpKernel\Tests\Controller\ControllerTest". Available methods: "publicAction", "staticAction"',
117+
),
120118
);
121119
}
122120

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

Lines changed: 57 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;
@@ -155,4 +159,56 @@ protected function createController($controller)
155159

156160
return array(new $class(), $method);
157161
}
162+
163+
private function getControllerError($callable)
164+
{
165+
if (is_array($callable) || (is_string($callable) && false !== strpos($callable, '::'))) {
166+
list($controller, $method) = is_array($callable) ? $callable : explode('::', $callable);
167+
168+
if (is_string($controller) && !class_exists($controller)) {
169+
return sprintf('Class "%s" does not exist.', $controller);
170+
}
171+
172+
$reflection = new \ReflectionClass($controller);
173+
174+
if (!method_exists($controller, $method)) {
175+
$collection = array_map(
176+
function (\ReflectionMethod $method) {
177+
return $method->getName();
178+
},
179+
$reflection->getMethods(\ReflectionMethod::IS_PUBLIC)
180+
);
181+
182+
$alternatives = array();
183+
184+
foreach ($collection as $item) {
185+
$lev = levenshtein($method, $item);
186+
187+
if ($lev <= strlen($method) / 3 || false !== strpos($item, $method)) {
188+
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
189+
}
190+
}
191+
192+
asort($alternatives);
193+
194+
$message = sprintf('Expected method "%s" on class "%s"', $method, $reflection->getName());
195+
196+
if (count($alternatives) > 0) {
197+
$message .= sprintf(', did you mean "%s"?', implode('", "', array_keys($alternatives)));
198+
} else {
199+
$message .= sprintf('. Available methods: "%s"', implode('", "', $collection));
200+
}
201+
202+
return $message;
203+
}
204+
205+
return sprintf(
206+
'Method "%s" on class "%s" should be public and non-abstract',
207+
$method,
208+
$reflection->getName()
209+
);
210+
} elseif (is_string($callable) && !function_exists($callable)) {
211+
return sprintf('Function %s does not exist.', $callable);
212+
}
213+
}
158214
}

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

@@ -240,3 +243,22 @@ protected function controllerMethod5(Request $request)
240243
function some_controller_function($foo, $foobar)
241244
{
242245
}
246+
247+
class ControllerTest
248+
{
249+
public function publicAction()
250+
{
251+
}
252+
253+
private function privateAction()
254+
{
255+
}
256+
257+
protected function protectedAction()
258+
{
259+
}
260+
261+
public static function staticAction()
262+
{
263+
}
264+
}

0 commit comments

Comments
 (0)
0