Improving the exception message when the bundle name is wrong for the controller in a route

Usually, it is wrong because you've chosen the wrong bundle name in your _controller syntax.
But this also tries to imply that you *might* be missing your bundle initialization in AppKernel
(though I think this is much much less common).
This commit is contained in:
Ryan Weaver 2014-06-23 21:53:18 -06:00
parent 8b54211471
commit f9b88c6b9a
2 changed files with 82 additions and 2 deletions

View File

@ -46,6 +46,7 @@ class ControllerNameParser
*/
public function parse($controller)
{
$originalController = $controller;
if (3 != count($parts = explode(':', $controller))) {
throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid "a:b:c" controller string.', $controller));
}
@ -54,8 +55,24 @@ class ControllerNameParser
$controller = str_replace('/', '\\', $controller);
$bundles = array();
// this throws an exception if there is no such bundle
foreach ($this->kernel->getBundle($bundle, false) as $b) {
try {
// this throws an exception if there is no such bundle
$allBundles = $this->kernel->getBundle($bundle, false);
} catch (\InvalidArgumentException $e) {
$message = sprintf(
'The "%s" (from the _controller value "%s") does not exist or is not enabled in your kernel!',
$bundle,
$originalController
);
if ($alternative = $this->findAlternative($bundle)) {
$message .= sprintf(' Did you mean "%s:%s:%s"?', $alternative, $controller, $action);
}
throw new \InvalidArgumentException($message, 0, $e);
}
foreach ($allBundles as $b) {
$try = $b->getNamespace().'\\Controller\\'.$controller.'Controller';
if (class_exists($try)) {
return $try.'::'.$action.'Action';
@ -100,4 +117,33 @@ class ControllerNameParser
throw new \InvalidArgumentException(sprintf('Unable to find a bundle that defines controller "%s".', $controller));
}
/**
* Attempts to find a bundle that is *similar* to the given bundle name
*
* @param string $nonExistentBundleName
* @return string
*/
private function findAlternative($nonExistentBundleName)
{
$bundleNames = array_map(function ($b) {
return $b->getName();
}, $this->kernel->getBundles());
$alternative = null;
$shortest = null;
foreach ($bundleNames as $bundleName) {
// if there's a partial match, return it immediately
if (false !== strpos($bundleName, $nonExistentBundleName)) {
return $bundleName;
}
$lev = levenshtein($nonExistentBundleName, $bundleName);
if ($lev <= strlen($nonExistentBundleName) / 3 && ($alternative === null || $lev < $shortest)) {
$alternative = $bundleName;
}
}
return $alternative;
}
}

View File

@ -107,6 +107,36 @@ class ControllerNameParserTest extends TestCase
);
}
/**
* @expectedException
* @dataProvider getInvalidBundleNameTests
*/
public function testInvalidBundleName($bundleName, $suggestedBundleName)
{
$parser = $this->createParser();
try {
$parser->parse($bundleName);
} catch (\Exception $e) {
$this->assertInstanceOf('\InvalidArgumentException', $e, '->parse() throws a \InvalidArgumentException if the bundle does not exist');
if (false === $suggestedBundleName) {
// make sure we don't have a suggestion
$this->assertNotContains('Did you mean', $e->getMessage());
} else {
$this->assertContains(sprintf('Did you mean "%s"', $suggestedBundleName), $e->getMessage());
}
}
}
public function getInvalidBundleNameTests()
{
return array(
array('FoodBundle:Default:index', 'FooBundle:Default:index'),
array('CrazyBundle:Default:index', false),
);
}
private function createParser()
{
$bundles = array(
@ -121,6 +151,10 @@ class ControllerNameParserTest extends TestCase
->expects($this->any())
->method('getBundle')
->will($this->returnCallback(function ($bundle) use ($bundles) {
if (!isset($bundles[$bundle])) {
throw new \InvalidArgumentException(sprintf('Invalid bundle name "%s"', $bundle));
}
return $bundles[$bundle];
}))
;