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

Skip to content

Commit a17ea7b

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

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

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

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