From f9b88c6b9afc8bd0cb4e7277d36dbe6c935c14a8 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 23 Jun 2014 21:53:18 -0600 Subject: [PATCH] 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). --- .../Controller/ControllerNameParser.php | 50 ++++++++++++++++++- .../Controller/ControllerNameParserTest.php | 34 +++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php index 4b1c665a94..e05f143b4b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerNameParser.php @@ -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; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerNameParserTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerNameParserTest.php index b18ade8793..09ed3afe32 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerNameParserTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerNameParserTest.php @@ -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]; })) ;