From 51f60fcb592188d2c50b1d2b77cf05bea5f277cb Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 8 Sep 2015 20:28:50 -0400 Subject: [PATCH 01/34] Adding a new framework-specific Route class This will join with future commits and a RouteCollectionBuilder --- .../Bundle/FrameworkBundle/Routing/Route.php | 101 ++++++++++++++++++ .../Tests/Routing/RouteTest.php | 67 ++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Route.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php new file mode 100644 index 0000000000000..066fed4e8012a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing; + +use Symfony\Component\Routing\Route as BaseRoute; + +/** + * A framework-specific Route object to help with common route tasks. + * + * @author Ryan Weaver + */ +class Route extends BaseRoute +{ + private $name; + + /** + * Set the controller string on the route. + * + * @param string $controller + * @return $this + */ + public function setController($controller) + { + $this->setDefault('_controller', $controller); + + return $this; + } + + /** + * Set the request format for this route. + * + * @param string $format + * @return $this + */ + public function setRequestFormat($format) + { + $this->setDefault('_format', $format); + + return $this; + } + + /** + * Set the locale for this route. + * + * @param string $locale + * @return $this + */ + public function setLocale($locale) + { + $this->setDefault('_locale', $locale); + + return $this; + } + + /** + * Set the name of this route - IF this route is added via a mechanism that + * supports this, like RouteCollectionBuilder. + * + * @param string $name + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + public function getName() + { + return $this->name; + } + + /** + * Generates a route name based on details of this route. + * + * @return string + */ + public function generateRouteName() + { + $methods = implode('_', $this->getMethods()).'_'; + + $routeName = $methods.$this->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteTest.php new file mode 100644 index 0000000000000..3311aec642d18 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Routing; + +use Symfony\Bundle\FrameworkBundle\Routing\Route; + +class RouteTest extends \PHPUnit_Framework_TestCase +{ + public function testSetController() + { + $route = new Route('/foo'); + $route->setController('AppBundle:Pet:puppy'); + + $this->assertEquals('AppBundle:Pet:puppy', $route->getDefault('_controller')); + } + + public function testSetRequestFormat() + { + $route = new Route('/foo'); + $route->setRequestFormat('json'); + + $this->assertEquals('json', $route->getDefault('_format')); + } + + public function testSetLocale() + { + $route = new Route('/foo'); + $route->setLocale('de'); + + $this->assertEquals('de', $route->getDefault('_locale')); + } + + public function testSetName() + { + $route = new Route('/foo'); + $route->setName('foo_route'); + + $this->assertEquals('foo_route', $route->getName()); + } + + /** + * @dataProvider provideRouteAndExpectedRouteName + */ + public function testDefaultRouteNameGeneration(Route $route, $expectedRouteName) + { + $this->assertEquals($expectedRouteName, $route->generateRouteName()); + } + + public function provideRouteAndExpectedRouteName() + { + return array( + array(new Route('/Invalid%Symbols#Stripped', array(), array(), array(), '', array(), array('POST')), 'POST_InvalidSymbolsStripped'), + array(new Route('/post/{id}', array(), array(), array(), '', array(), array('GET')), 'GET_post_id'), + array(new Route('/colon:pipe|dashes-escaped'), '_colon_pipe_dashes_escaped'), + array(new Route('/underscores_and.periods'), '_underscores_and.periods'), + ); + } +} From 6891ec8869eb647a6f658c65f6741936f5cb2dff Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 8 Sep 2015 21:27:56 -0400 Subject: [PATCH 02/34] Adding a class to make adding/importing routes easier and more fluid This is modeled off of Silex's ControllerCollection. The main advantages over using the raw RouteCollection are: * Ability to create a Route using a fluid interface (add) * Ability to import a resource, add that collection and modify it using a fluid interface (import) * Support for auto-generating route names --- .../Routing/RouteCollectionBuilder.php | 147 ++++++++++++++ .../Routing/RouteCollectionBuilderTest.php | 182 ++++++++++++++++++ 2 files changed, 329 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php new file mode 100644 index 0000000000000..2b1e58d28bf98 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Routing; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Routing\RouteCollection; + +/** + * Helps add and import routes into a RouteCollection. + * + * @author Ryan Weaver + */ +class RouteCollectionBuilder +{ + private $loader; + + private $routes = array(); + + private $resources = array(); + + private $defaults = array(); + + /** + * @param Loader $loader + */ + public function __construct(Loader $loader) + { + $this->loader = $loader; + } + + /** + * Import an external routing resource, like a file. + * + * @param mixed $resource + * @param string $prefix + * @param string $type + * @return RouteCollection + */ + public function import($resource, $prefix = null, $type = null) + { + /** @var RouteCollection $subCollection */ + $subCollection = $this->loader->import($resource, $type); + $subCollection->addPrefix($prefix); + + $this->routes[] = $subCollection; + + // return the collection so more options can be added to it + return $subCollection; + } + + /** + * Adds a route and returns it for future modification. + * + * @param string $path The route path + * @param string $controller The route controller string + * @param string $name The name to give this route + * @return Route + */ + public function add($path, $controller, $name = null) + { + $route = new Route($path); + $route->setController($controller); + $route->setName($name); + + $this->routes[] = $route; + + return $route; + } + + /** + * Add a raw RouteCollection + * + * @param RouteCollection $collection + */ + public function addCollection(RouteCollection $collection) + { + $this->routes[] = $collection; + } + + /** + * Add some default values to all routes. + * + * @param array $defaults + */ + public function addDefaults(array $defaults) + { + $this->defaults = $defaults; + } + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource A resource instance + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + } + + /** + * Creates the final ArrayCollection, returns it, and clears everything. + * + * @return RouteCollection + */ + public function flush() + { + $routes = new RouteCollection(); + + foreach ($this->routes as $route) { + if ($route instanceof Route) { + // add the single route + if (!$name = $route->getName()) { + // auto-generate a route name + $name = $route->generateRouteName(); + } + + $routes->add($name, $route); + } else { + // $route is actually a RouteCollection + $routes->addCollection($route); + } + } + + $routes->addDefaults($this->defaults); + + foreach ($this->resources as $resource) { + $routes->addResource($resource); + } + + // reset all the values + $this->defaults = array(); + $this->resources = array(); + $this->routes = array(); + + return $routes; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php new file mode 100644 index 0000000000000..df31b637c8ba0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Routing; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Bundle\FrameworkBundle\Routing\RouteCollectionBuilder; +use Symfony\Component\Config\Resource\FileResource; + +class RouteCollectionBuilderTest extends \PHPUnit_Framework_TestCase +{ + public function testImport() + { + $loader = $this->getLoader(); + + $expectedCollection = new RouteCollection(); + $expectedCollection->add('one_test_route', new Route('/foo/path')); + + $loader + ->expects($this->once()) + ->method('import') + ->with('admin_routing.yml', 'yaml') + ->will($this->returnValue($expectedCollection)); + + // import the file! + $collectionBuilder = new RouteCollectionBuilder($loader); + $addedCollection = $collectionBuilder->import('admin_routing.yml', '/admin', 'yaml'); + + // the exact collection is passed back + $this->assertSame($expectedCollection, $addedCollection); + + $route = $addedCollection->get('one_test_route'); + $this->assertNotNull($route); + $this->assertEquals('/admin/foo/path', $route->getPath(), 'The prefix should be applied'); + } + + public function testAdd() + { + $loader = $this->getLoader(); + $collectionBuilder = new RouteCollectionBuilder($loader); + + $addedRoute = $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout'); + $addedRoute2 = $collectionBuilder->add('/blogs', 'AppBundle:Blog:list', 'blog_list'); + $this->assertInstanceOf('Symfony\Bundle\FrameworkBundle\Routing\Route', $addedRoute); + $this->assertEquals('AppBundle:Order:checkout', $addedRoute->getDefault('_controller')); + $this->assertEquals('blog_list', $addedRoute2->getName()); + } + + public function testFlushOrdering() + { + $loadedCollection1 = new RouteCollection(); + $loadedCollection1->add('first_collection_route1', new Route('/collection/first/blog1')); + $loadedCollection1->add('first_collection_route2', new Route('/collection/first/blog2')); + + $loadedCollection2 = new RouteCollection(); + $loadedCollection2->add('second_collection_route1', new Route('/collection/second/product1')); + $loadedCollection2->add('second_collection_route2', new Route('/collection/second/product2')); + + $importedCollection = new RouteCollection(); + $importedCollection->add('imported_route1', new Route('/imported/foo1')); + $importedCollection->add('imported_route2', new Route('/imported/foo2')); + + $loader = $this->getLoader(); + $loader + ->expects($this->once()) + ->method('import') + ->will($this->returnValue($importedCollection)); + + $collectionBuilder = new RouteCollectionBuilder($loader); + + // 1) Add a route + $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout') + ->setName('checkout_route'); + // 2) Add a collection directly + $collectionBuilder->addCollection($loadedCollection1); + // 3) Import from a file + $collectionBuilder->import('admin_routing.yml'); + // 4) Add another route + $collectionBuilder->add('/', 'AppBundle:Default:homepage') + ->setName('homepage'); + // 5) Add another collection + $collectionBuilder->addCollection($loadedCollection2); + // 6) Add another route + $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard') + ->setName('admin_dashboard'); + + // set a default value + $collectionBuilder->addDefaults(array('_locale' => 'fr')); + // set an extra resource + $collectionBuilder->addResource(new FileResource('foo_routing.xml')); + + $actualCollection = $collectionBuilder->flush(); + + $this->assertCount(9, $actualCollection); + $actualRouteNames = array_keys($actualCollection->all()); + $this->assertEquals(array( + 'checkout_route', + 'first_collection_route1', + 'first_collection_route2', + 'imported_route1', + 'imported_route2', + 'homepage', + 'second_collection_route1', + 'second_collection_route2', + 'admin_dashboard' + ), $actualRouteNames); + + // make sure the defaults were set + $checkoutRoute = $actualCollection->get('checkout_route'); + $defaults = $checkoutRoute->getDefaults(); + $this->assertArrayHasKey('_locale', $defaults); + $this->assertEquals('fr', $defaults['_locale']); + + // technically, we should expect 2 here (admin_routing.yml + foo_routing.xml) + // but, admin_routing.yml would be added to the collection via the loader, which is mocked + $this->assertCount(1, $actualCollection->getResources(), 'The added resource is included'); + } + + public function testFlushSetsRouteNames() + { + $loader = $this->getLoader(); + $collectionBuilder = new RouteCollectionBuilder($loader); + + // add a "named" route + $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard') + ->setName('admin_dashboard'); + // add an unnamed route + $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') + ->setMethods('GET'); + + $actualCollection = $collectionBuilder->flush(); + $actualRouteNames = array_keys($actualCollection->all()); + $this->assertEquals(array( + 'admin_dashboard', + 'GET_blogs', + ), $actualRouteNames); + } + + public function testFlushClearsEverything() + { + $loader = $this->getLoader(); + $collectionBuilder = new RouteCollectionBuilder($loader); + + // add a "named" route + $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard') + ->setName('admin_dashboard'); + $collectionBuilder->addDefaults(array('_locale' => 'fr')); + $collectionBuilder->addResource(new FileResource('foo_routing.xml')); + + // flush once + $collectionBuilder->flush(); + + // flush again - should not contain previous stuff + $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') + ->setName('blog_list'); + $secondCollection = $collectionBuilder->flush(); + + $this->assertCount(1, $secondCollection); + $this->assertCount(0, $secondCollection->getResources()); + $this->assertArrayNotHasKey('_locale', $secondCollection->get('blog_list')->getDefaults()); + } + + private function getLoader() + { + $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\Loader') + ->disableOriginalConstructor() + ->getMock(); + + return $loader; + } + + // test that flush leaves everything cleared +} From 4e430a0a614d38ee5c13f0e1809201838477f53f Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 12 Sep 2015 19:25:21 -0400 Subject: [PATCH 03/34] Maintaining all the RouteCollection abilities to RouteCollectionBuilder This allows us to add all of these values AFTER all of the routes have been added, which is the behavior that makes more sense. Also adding a feature so that you can mount "collections" or routes at a time, and set the controllerClass on those to save coding routes from an entire class. --- .../Routing/RouteCollectionBuilder.php | 226 ++++++++++++++++-- .../Routing/RouteCollectionBuilderTest.php | 45 ++-- 2 files changed, 234 insertions(+), 37 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 2b1e58d28bf98..9d77eb2dbac13 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -16,7 +16,7 @@ use Symfony\Component\Routing\RouteCollection; /** - * Helps add and import routes into a RouteCollection. + * Helps add and import routes into a RouteCollection.. * * @author Ryan Weaver */ @@ -24,11 +24,32 @@ class RouteCollectionBuilder { private $loader; + /** + * A mixture of different objects that hold routes. + * + * @var Route[]|RouteCollectionBuilder[]|RouteCollection[] + */ private $routes = array(); + private $defaults = array(); + + private $prefix; + + private $host; + + private $condition; + + private $requirements = array(); + + private $options = array(); + + private $schemes; + + private $methods; + private $resources = array(); - private $defaults = array(); + private $controllerClass; /** * @param Loader $loader @@ -41,10 +62,12 @@ public function __construct(Loader $loader) /** * Import an external routing resource, like a file. * + * Returns a RouteCollectionBuilder so you can continue to tweak options on the routes. + * * @param mixed $resource * @param string $prefix * @param string $type - * @return RouteCollection + * @return RouteCollectionBuilder */ public function import($resource, $prefix = null, $type = null) { @@ -52,10 +75,12 @@ public function import($resource, $prefix = null, $type = null) $subCollection = $this->loader->import($resource, $type); $subCollection->addPrefix($prefix); - $this->routes[] = $subCollection; + // turn this into a RouteCollectionBuilder + $builder = new RouteCollectionBuilder($this->loader); + $builder->addRouteCollection($subCollection); + $this->routes[] = $builder; - // return the collection so more options can be added to it - return $subCollection; + return $builder; } /** @@ -68,43 +93,131 @@ public function import($resource, $prefix = null, $type = null) */ public function add($path, $controller, $name = null) { + // what if we have some other Route sub-class in our project: CmsRoute + // is it really ok to copy all the CmsRoute objects to our Route? + // -> we MUST somehow allow Component Route objects (no name attached) + $route = new Route($path); $route->setController($controller); $route->setName($name); - $this->routes[] = $route; return $route; } /** - * Add a raw RouteCollection + * @return RouteCollectionBuilder + */ + public function createCollection() + { + return new RouteCollectionBuilder($this->loader); + } + + /** + * Add a RouteCollectionBuilder. * - * @param RouteCollection $collection + * @param $prefix + * @param RouteCollectionBuilder $routes */ - public function addCollection(RouteCollection $collection) + public function mount($prefix, RouteCollectionBuilder $routes) { - $this->routes[] = $collection; + $routes->setPrefix($prefix); + $this->routes[] = $routes; } /** - * Add some default values to all routes. + * Adds a RouteCollection directly. * - * @param array $defaults + * @param RouteCollection $collection */ - public function addDefaults(array $defaults) + public function addRouteCollection(RouteCollection $collection) + { + $this->routes[] = $collection; + } + + public function setPrefix($prefix) + { + $this->prefix = trim(trim($prefix), '/'); + + return $this; + } + + public function setHost($pattern) + { + $this->host = $pattern; + + return $this; + } + + public function setCondition($condition) + { + $this->condition = $condition; + + return $this; + } + + public function setDefault($key, $value) + { + $this->defaults[$key] = $value; + + return $this; + } + + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $regex; + + return $this; + } + + public function setOption($key, $value) + { + $this->options[$key] = $value; + + return $this; + } + + public function setSchemes($schemes) { - $this->defaults = $defaults; + $this->schemes = $schemes; + + return $this; + } + + public function setMethods($methods) + { + $this->methods = $methods; + + return $this; } /** * Adds a resource for this collection. * - * @param ResourceInterface $resource A resource instance + * @return $this */ public function addResource(ResourceInterface $resource) { $this->resources[] = $resource; + + return $this; + } + + /** + * Set a controller class that all added routes should use. + * + * @param string $controllerClass + * @return $this + */ + public function setControllerClass($controllerClass) + { + if (!class_exists($controllerClass)) { + throw new \LogicException(sprintf('The controller class "%s" does not exist.', $controllerClass)); + } + + $this->controllerClass = $controllerClass; + + return $this; } /** @@ -118,30 +231,97 @@ public function flush() foreach ($this->routes as $route) { if ($route instanceof Route) { - // add the single route + // auto-generate the route name if needed if (!$name = $route->getName()) { - // auto-generate a route name $name = $route->generateRouteName(); } + $this->ensureRouteController($route); + + $route->setDefaults(array_merge($this->defaults, $route->getDefaults())); + $route->setRequirements(array_merge($this->requirements, $route->getRequirements())); + $route->setOptions(array_merge($this->options, $route->getOptions())); + + if ($this->prefix) { + $route->setPath('/'.$this->prefix.$route->getPath()); + } + + if (!$route->getHost()) { + $route->setHost($this->host); + } + + if (!$route->getCondition()) { + $route->setCondition($this->condition); + } + + if (!$route->getSchemes()) { + $route->setSchemes($this->schemes); + } + + if (!$route->getMethods()) { + $route->setMethods($this->methods); + } + $routes->add($name, $route); + } elseif ($route instanceof RouteCollectionBuilder) { + $subCollection = $route->flush(); + $routes->addCollection($subCollection); } else { - // $route is actually a RouteCollection + /** @var RouteCollection $route */ $routes->addCollection($route); } } - $routes->addDefaults($this->defaults); - foreach ($this->resources as $resource) { $routes->addResource($resource); } // reset all the values - $this->defaults = array(); - $this->resources = array(); $this->routes = array(); + $this->resources = array(); + $this->defaults = array(); + $this->options = array(); + $this->requirements = array(); + $this->prefix = null; + $this->host = null; + $this->condition = null; + $this->schemes = null; + $this->methods = null; + $this->controllerClass = null; return $routes; } + + /** + * Attempts to safely prefix controllers with the controller class if necessary. + * + * @param Route $route + */ + private function ensureRouteController(Route $route) + { + // only do work if there is a controller class set + if (!$this->controllerClass) { + return; + } + + $controller = $route->getDefault('_controller'); + + // only apply controller class to a (non-empty) string + if (!is_string($controller) || !$controller) { + return; + } + + // is the controller already a callable function/class? + if (method_exists($controller, '__invoke') || function_exists($controller)) { + return; + } + + // is this already a controller format (a:b:c, or a:b, or a::b)? + if (false !== strpos($controller, ':')) { + return; + } + + $controller = sprintf('%s::%s', $this->controllerClass, $controller); + $route->setDefault('_controller', $controller); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index df31b637c8ba0..62729868dbc8e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -31,13 +31,15 @@ public function testImport() ->with('admin_routing.yml', 'yaml') ->will($this->returnValue($expectedCollection)); - // import the file! + // import the file! (with a prefix) $collectionBuilder = new RouteCollectionBuilder($loader); - $addedCollection = $collectionBuilder->import('admin_routing.yml', '/admin', 'yaml'); + $addedBuilder = $collectionBuilder->import('admin_routing.yml', '/admin', 'yaml'); - // the exact collection is passed back - $this->assertSame($expectedCollection, $addedCollection); + // we should get back a RouteCollectionBuilder + $this->assertInstanceOf('Symfony\Bundle\FrameworkBundle\Routing\RouteCollectionBuilder', $addedBuilder); + // get the collection back so we can look at it + $addedCollection = $addedBuilder->flush(); $route = $addedCollection->get('one_test_route'); $this->assertNotNull($route); $this->assertEquals('/admin/foo/path', $route->getPath(), 'The prefix should be applied'); @@ -81,20 +83,20 @@ public function testFlushOrdering() $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout') ->setName('checkout_route'); // 2) Add a collection directly - $collectionBuilder->addCollection($loadedCollection1); + $collectionBuilder->addRouteCollection($loadedCollection1); // 3) Import from a file $collectionBuilder->import('admin_routing.yml'); // 4) Add another route $collectionBuilder->add('/', 'AppBundle:Default:homepage') ->setName('homepage'); // 5) Add another collection - $collectionBuilder->addCollection($loadedCollection2); + $collectionBuilder->addRouteCollection($loadedCollection2); // 6) Add another route $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard') ->setName('admin_dashboard'); // set a default value - $collectionBuilder->addDefaults(array('_locale' => 'fr')); + $collectionBuilder->setDefault('_locale', 'fr'); // set an extra resource $collectionBuilder->addResource(new FileResource('foo_routing.xml')); @@ -151,22 +153,39 @@ public function testFlushClearsEverything() $collectionBuilder = new RouteCollectionBuilder($loader); // add a "named" route - $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard') - ->setName('admin_dashboard'); - $collectionBuilder->addDefaults(array('_locale' => 'fr')); + $collectionBuilder->add('/post', 'AppBundle:Admin:dashboard') + ->setName('admin_post'); + $collectionBuilder->setPrefix('/admin'); + $collectionBuilder->setDefault('_locale', 'fr'); + $collectionBuilder->setMethods('POST'); + $collectionBuilder->setSchemes('https'); + $collectionBuilder->setCondition('foo'); + $collectionBuilder->setControllerClass('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController'); + $collectionBuilder->setHost('example.com'); + $collectionBuilder->setOption('expose', true); + $collectionBuilder->setRequirement('id', '\d+'); $collectionBuilder->addResource(new FileResource('foo_routing.xml')); // flush once $collectionBuilder->flush(); // flush again - should not contain previous stuff - $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') + $collectionBuilder->add('/blogs', 'list') ->setName('blog_list'); $secondCollection = $collectionBuilder->flush(); $this->assertCount(1, $secondCollection); $this->assertCount(0, $secondCollection->getResources()); - $this->assertArrayNotHasKey('_locale', $secondCollection->get('blog_list')->getDefaults()); + $blogListRoute = $secondCollection->get('blog_list'); + $this->assertArrayNotHasKey('_locale', $blogListRoute->getDefaults()); + $this->assertEmpty($blogListRoute->getMethods()); + $this->assertEmpty($blogListRoute->getSchemes()); + $this->assertEmpty($blogListRoute->getCondition()); + // controller class should not have been added + $this->assertEquals('list', $blogListRoute->getDefault('_controller')); + $this->assertEmpty($blogListRoute->getHost()); + $this->assertNull($blogListRoute->getOption('expose')); + $this->assertNull($blogListRoute->getRequirement('id')); } private function getLoader() @@ -177,6 +196,4 @@ private function getLoader() return $loader; } - - // test that flush leaves everything cleared } From 0ddadc5a89f6de096b2de4fd63cdf41b065c2e8a Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 13 Sep 2015 12:09:09 -0400 Subject: [PATCH 04/34] Moving the prefix to the builder, so that it's consistent with other behavior This is an edge case, but in theory, now if you added more routes to this same embedded RouteCollectionBuilder, those would also have the prefix. --- .../Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 9d77eb2dbac13..92edcb9bf4194 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -73,10 +73,10 @@ public function import($resource, $prefix = null, $type = null) { /** @var RouteCollection $subCollection */ $subCollection = $this->loader->import($resource, $type); - $subCollection->addPrefix($prefix); // turn this into a RouteCollectionBuilder $builder = new RouteCollectionBuilder($this->loader); + $builder->setPrefix($prefix); $builder->addRouteCollection($subCollection); $this->routes[] = $builder; From 06ea9002eb5dbf7668317cea06a56be6d2da01ae Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 13 Sep 2015 12:10:17 -0400 Subject: [PATCH 05/34] Adding phpdoc --- .../Routing/RouteCollectionBuilder.php | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 92edcb9bf4194..cbbe828f4c528 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -106,6 +106,8 @@ public function add($path, $controller, $name = null) } /** + * Returns a RouteCollectionBuilder that can be configured and then added with mount() + * * @return RouteCollectionBuilder */ public function createCollection() @@ -135,6 +137,12 @@ public function addRouteCollection(RouteCollection $collection) $this->routes[] = $collection; } + /** + * Sets a prefix (e.g. /admin) to be used with all embedded routes. + * + * @param string $prefix + * @return $this + */ public function setPrefix($prefix) { $this->prefix = trim(trim($prefix), '/'); @@ -142,6 +150,12 @@ public function setPrefix($prefix) return $this; } + /** + * Sets the host on all embedded routes (unless already set). + * + * @param string $pattern + * @return $this + */ public function setHost($pattern) { $this->host = $pattern; @@ -149,6 +163,12 @@ public function setHost($pattern) return $this; } + /** + * Sets a condition on all embedded routes (unless already set). + * + * @param string $condition + * @return $this + */ public function setCondition($condition) { $this->condition = $condition; @@ -156,6 +176,14 @@ public function setCondition($condition) return $this; } + /** + * Sets a default value that will be added to all embedded routes (unless that + * default value is already set. + * + * @param string $key + * @param mixed $value + * @return $this + */ public function setDefault($key, $value) { $this->defaults[$key] = $value; @@ -163,6 +191,14 @@ public function setDefault($key, $value) return $this; } + /** + * Sets a requirement that will be added to all embedded routes (unless that + * requirement is already set. + * + * @param string $key + * @param mixed $value + * @return $this + */ public function setRequirement($key, $regex) { $this->requirements[$key] = $regex; @@ -170,6 +206,14 @@ public function setRequirement($key, $regex) return $this; } + /** + * Sets an opiton that will be added to all embedded routes (unless that + * option is already set. + * + * @param string $key + * @param mixed $value + * @return $this + */ public function setOption($key, $value) { $this->options[$key] = $value; @@ -177,6 +221,12 @@ public function setOption($key, $value) return $this; } + /** + * Sets the schemes on all embedded routes (unless already set). + * + * @param array|string $schemes + * @return $this + */ public function setSchemes($schemes) { $this->schemes = $schemes; @@ -184,6 +234,12 @@ public function setSchemes($schemes) return $this; } + /** + * Sets the methods on all embedded routes (unless already set). + * + * @param array|string $methods + * @return $this + */ public function setMethods($methods) { $this->methods = $methods; @@ -204,7 +260,11 @@ public function addResource(ResourceInterface $resource) } /** - * Set a controller class that all added routes should use. + * Set a controller class that all added embedded should use. + * + * With this, the controller for embedded routes can just be a method name. + * If an embedded route has a full controller (e.g. class::methodName), the + * controllerClass won't be applied to that route. * * @param string $controllerClass * @return $this From 6b922a6c8603a7cf9c792a9696b632932da81928 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 13 Sep 2015 12:10:26 -0400 Subject: [PATCH 06/34] Using InvalidArgumentException --- .../Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index cbbe828f4c528..3066720e9bd84 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -272,7 +272,7 @@ public function addResource(ResourceInterface $resource) public function setControllerClass($controllerClass) { if (!class_exists($controllerClass)) { - throw new \LogicException(sprintf('The controller class "%s" does not exist.', $controllerClass)); + throw new \InvalidArgumentException(sprintf('The controller class "%s" does not exist.', $controllerClass)); } $this->controllerClass = $controllerClass; From 14518ed4b38f8843b281cad3d1868feec0d9cbe1 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 13 Sep 2015 12:11:46 -0400 Subject: [PATCH 07/34] No change - renaming variable --- .../Routing/RouteCollectionBuilder.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 3066720e9bd84..02f02df8ea6fe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -287,7 +287,7 @@ public function setControllerClass($controllerClass) */ public function flush() { - $routes = new RouteCollection(); + $routeCollection = new RouteCollection(); foreach ($this->routes as $route) { if ($route instanceof Route) { @@ -322,18 +322,20 @@ public function flush() $route->setMethods($this->methods); } - $routes->add($name, $route); + $routeCollection->add($name, $route); } elseif ($route instanceof RouteCollectionBuilder) { $subCollection = $route->flush(); - $routes->addCollection($subCollection); + + $routeCollection->addCollection($subCollection); } else { /** @var RouteCollection $route */ - $routes->addCollection($route); + + $routeCollection->addCollection($route); } } foreach ($this->resources as $resource) { - $routes->addResource($resource); + $routeCollection->addResource($resource); } // reset all the values @@ -349,7 +351,7 @@ public function flush() $this->methods = null; $this->controllerClass = null; - return $routes; + return $routeCollection; } /** From 7972fc908b82ba0bdb25cc925aa42826b41fdefe Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 13 Sep 2015 12:12:14 -0400 Subject: [PATCH 08/34] Adding many more tests, which included a few small bug fixes with values on compile --- .../Routing/RouteCollectionBuilder.php | 4 +- .../Routing/RouteCollectionBuilderTest.php | 187 ++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 02f02df8ea6fe..1c9183fc10dc8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -302,7 +302,7 @@ public function flush() $route->setRequirements(array_merge($this->requirements, $route->getRequirements())); $route->setOptions(array_merge($this->options, $route->getOptions())); - if ($this->prefix) { + if (null !== $this->prefix) { $route->setPath('/'.$this->prefix.$route->getPath()); } @@ -325,10 +325,12 @@ public function flush() $routeCollection->add($name, $route); } elseif ($route instanceof RouteCollectionBuilder) { $subCollection = $route->flush(); + $subCollection->addPrefix($this->prefix); $routeCollection->addCollection($subCollection); } else { /** @var RouteCollection $route */ + $route->addPrefix($this->prefix); $routeCollection->addCollection($route); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index 62729868dbc8e..a9b28f49bc55f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -188,6 +188,193 @@ public function testFlushClearsEverything() $this->assertNull($blogListRoute->getRequirement('id')); } + public function testFlushSetsDetailsOnChildrenRoutes() + { + $loader = $this->getLoader(); + $routes = new RouteCollectionBuilder($loader); + + $routes->add('/blogs/{page}', 'listAction', 'blog_list') + // unique things for the route + ->setDefault('page', 1) + ->setRequirement('id', '\d+') + ->setOption('expose', true) + // things that the collection will try to override (but won't) + ->setDefault('_format', 'html') + ->setRequirement('_format', 'json|xml') + ->setOption('fooBar', true) + ->setHost('example.com') + ->setCondition('request.isSecure()') + ->setSchemes('https') + ->setMethods('POST'); + + // a simple route, nothing added to it + $routes->add('/blogs/{id}', 'editAction', 'blog_edit'); + + // configure the collection itself + $routes + // things that will not override the child route + ->setDefault('_format', 'json') + ->setRequirement('_format', 'xml') + ->setOption('fooBar', false) + ->setHost('symfony.com') + ->setCondition('request.query.get("page")==1') + // some unique things that should be set on the child + ->setDefault('_locale', 'fr') + ->setRequirement('_locale', 'fr|en') + ->setOption('niceRoute', true) + ->setSchemes('http') + ->setMethods(array('GET', 'POST')); + + $collection = $routes->flush(); + $actualListRoute = $collection->get('blog_list'); + + $this->assertEquals(1, $actualListRoute->getDefault('page')); + $this->assertEquals('\d+', $actualListRoute->getRequirement('id')); + $this->assertEquals(true, $actualListRoute->getOption('expose')); + // none of these should be overridden + $this->assertEquals('html', $actualListRoute->getDefault('_format')); + $this->assertEquals('json|xml', $actualListRoute->getRequirement('_format')); + $this->assertEquals(true, $actualListRoute->getOption('fooBar')); + $this->assertEquals('example.com', $actualListRoute->getHost()); + $this->assertEquals('request.isSecure()', $actualListRoute->getCondition()); + $this->assertEquals(array('https'), $actualListRoute->getSchemes()); + $this->assertEquals(array('POST'), $actualListRoute->getMethods()); + // inherited from the main collection + $this->assertEquals('fr', $actualListRoute->getDefault('_locale')); + $this->assertEquals('fr|en', $actualListRoute->getRequirement('_locale')); + $this->assertEquals(true, $actualListRoute->getOption('niceRoute')); + + $actualEditRoute = $collection->get('blog_edit'); + // inherited from the collection + $this->assertEquals('symfony.com', $actualEditRoute->getHost()); + $this->assertEquals('request.query.get("page")==1', $actualEditRoute->getCondition()); + $this->assertEquals(array('http'), $actualEditRoute->getSchemes()); + $this->assertEquals(array('GET', 'POST'), $actualEditRoute->getMethods()); + } + + /** + * @dataProvider providePrefixTests + */ + public function testFlushPrefixesPaths($collectionPrefix, $routePath, $expectedPath) + { + $loader = $this->getLoader(); + $routes = new RouteCollectionBuilder($loader); + $routes->setPrefix($collectionPrefix); + + $routes->add($routePath, 'someController', 'test_route'); + $collection = $routes->flush(); + + $this->assertEquals($expectedPath, $collection->get('test_route')->getPath()); + } + + public function providePrefixTests() + { + $tests = array(); + // empty prefix is of course ok + $tests[] = array('', '/foo', '/foo'); + // normal prefix - does not matter if it's a wildcard + $tests[] = array('/{admin}', '/foo', '/{admin}/foo'); + // shows that a prefix will always be given the starting slash + $tests = array(); + $tests[] = array('0', '/foo', '/0/foo'); + + // spaces are ok, and double slahses at the end are cleaned + $tests[] = array('/ /', '/foo', '/ /foo'); + + return $tests; + } + + public function testFlushSetsPrefixedWithMultipleLevels() + { + $loader = $this->getLoader(); + $routes = new RouteCollectionBuilder($loader); + + $routes->add('homepage', 'MainController::homepageAction', 'homepage'); + + $adminRoutes = $routes->createCollection(); + $adminRoutes->add('/dashboard', 'AdminController::dashboardAction', 'admin_dashboard'); + + // embedded collection under /admin + $adminBlogRoutes = $routes->createCollection(); + $adminBlogRoutes->add('/new', 'BlogController::newAction', 'admin_blog_new'); + // mount into admin, but before the parent collection has been mounted + $adminRoutes->mount('/blog', $adminBlogRoutes); + + // now mount the /admin routes, above should all still be /blog/admin + $routes->mount('/admin', $adminRoutes); + // add a route after mounting + $adminRoutes->add('/users', 'AdminController::userAction', 'admin_users'); + + // add another sub-collection after the mount + $otherAdminRoutes = $routes->createCollection(); + $otherAdminRoutes->add('/sales', 'StatsController::indexAction', 'admin_stats_sales'); + $adminRoutes->mount('/stats', $otherAdminRoutes); + + // add a normal collection and see that it is also prefixed + $importedCollection = new RouteCollection(); + $importedCollection->add('imported_route', new Route('/foo')); + $loader + ->expects($this->any()) + ->method('import') + ->will($this->returnValue($importedCollection)); + // import this from the /admin route builder + $routeBuilderFromImport = $adminRoutes->import('admin.yml', '/imported'); + // setting the prefix via this method has no affect (i.e. no /imported/imported) + $routeBuilderFromImport->setPrefix('/imported'); + + $collection = $routes->flush(); + $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix'); + $this->assertEquals('/admin/users', $collection->get('admin_users')->getPath(), 'Routes after mounting have the prefix'); + $this->assertEquals('/admin/blog/new', $collection->get('admin_blog_new')->getPath(), 'Sub-collections receive prefix even if mounted before parent prefix'); + $this->assertEquals('/admin/stats/sales', $collection->get('admin_stats_sales')->getPath(), 'Sub-collections receive prefix if mounted after parent prefix'); + $this->assertEquals('/admin/imported/foo', $collection->get('imported_route')->getPath(), 'Normal RouteCollections are also prefixed properly'); + } + + /** + * @dataProvider provideControllerClassTests + */ + public function testSetControllerClass($routeController, $controllerClass, $expectedFinalController) + { + $loader = $this->getLoader(); + $routes = new RouteCollectionBuilder($loader); + $routes->setControllerClass('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController'); + + $routes->add('/', $routeController, 'test_route'); + $collection = $routes->flush(); + $this->assertEquals($expectedFinalController, $collection->get('test_route')->getDefault('_controller')); + } + + public function provideControllerClassTests() + { + $controllerClass = 'Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController'; + + $tests = array(); + $tests[] = array('withControllerAction', $controllerClass, $controllerClass.'::'.'withControllerAction'); + + // the controllerClass should not be used in many cases + $tests[] = array('', $controllerClass, ''); + $tests[] = array('Some\Class\FooController::fooAction', $controllerClass, 'Some\Class\FooController::fooAction'); + $tests[] = array('AppBundle:Default:index', $controllerClass, 'AppBundle:Default:index'); + $tests[] = array('foo_controller:fooAction', $controllerClass, 'foo_controller:fooAction'); + $tests[] = array(array('Acme\FooController', 'fooAction'), $controllerClass, array('Acme\FooController', 'fooAction')); + + $closure = function() {}; + $tests[] = array($closure, $controllerClass, $closure); + + return $tests; + } + + /** + * @expectedException InvalidArgumentException + */ + public function testExceptiononBadControllerClass() + { + $loader = $this->getLoader(); + $routes = new RouteCollectionBuilder($loader); + + $routes->setControllerClass('Acme\FakeController'); + } + private function getLoader() { $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\Loader') From e11b7e0e6e01b5044533de20b1b3a04e1da2ed9b Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 13 Sep 2015 12:19:38 -0400 Subject: [PATCH 09/34] Removed prefix argument from mount() - and added it instead to createCollection() The motivation is to avoid confusion: the "prefix" is a property of the embedded RouteCollectionBuilder itself - and so setting it, mutates that object. With mount(), it may have appeared that the RouteCollectionBuilder your mounting isn't mutated with the prefix - but that this value is somehow stored on the parent. --- .../Routing/RouteCollectionBuilder.php | 14 ++++++++------ .../Tests/Routing/RouteCollectionBuilderTest.php | 12 ++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 1c9183fc10dc8..a4d0899acc91f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -106,24 +106,26 @@ public function add($path, $controller, $name = null) } /** - * Returns a RouteCollectionBuilder that can be configured and then added with mount() + * Returns a RouteCollectionBuilder that can be configured and then added with mount(). * + * @param string $prefix A prefix to apply to all routes added to this collection * @return RouteCollectionBuilder */ - public function createCollection() + public function createCollection($prefix = null) { - return new RouteCollectionBuilder($this->loader); + $builder = new RouteCollectionBuilder($this->loader); + $builder->setPrefix($prefix); + + return $builder; } /** * Add a RouteCollectionBuilder. * - * @param $prefix * @param RouteCollectionBuilder $routes */ - public function mount($prefix, RouteCollectionBuilder $routes) + public function mount(RouteCollectionBuilder $routes) { - $routes->setPrefix($prefix); $this->routes[] = $routes; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index a9b28f49bc55f..dafbc31ac0435 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -291,24 +291,24 @@ public function testFlushSetsPrefixedWithMultipleLevels() $routes->add('homepage', 'MainController::homepageAction', 'homepage'); - $adminRoutes = $routes->createCollection(); + $adminRoutes = $routes->createCollection('/admin'); $adminRoutes->add('/dashboard', 'AdminController::dashboardAction', 'admin_dashboard'); // embedded collection under /admin - $adminBlogRoutes = $routes->createCollection(); + $adminBlogRoutes = $routes->createCollection('/blog'); $adminBlogRoutes->add('/new', 'BlogController::newAction', 'admin_blog_new'); // mount into admin, but before the parent collection has been mounted - $adminRoutes->mount('/blog', $adminBlogRoutes); + $adminRoutes->mount($adminBlogRoutes); // now mount the /admin routes, above should all still be /blog/admin - $routes->mount('/admin', $adminRoutes); + $routes->mount($adminRoutes); // add a route after mounting $adminRoutes->add('/users', 'AdminController::userAction', 'admin_users'); // add another sub-collection after the mount - $otherAdminRoutes = $routes->createCollection(); + $otherAdminRoutes = $routes->createCollection('/stats'); $otherAdminRoutes->add('/sales', 'StatsController::indexAction', 'admin_stats_sales'); - $adminRoutes->mount('/stats', $otherAdminRoutes); + $adminRoutes->mount($otherAdminRoutes); // add a normal collection and see that it is also prefixed $importedCollection = new RouteCollection(); From 4d9091697d944997717659832f218a9109c727d5 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 13 Sep 2015 21:27:27 -0400 Subject: [PATCH 10/34] fabbot! --- .../Bundle/FrameworkBundle/Routing/Route.php | 4 +++ .../Routing/RouteCollectionBuilder.php | 34 +++++++++++++------ .../Routing/RouteCollectionBuilderTest.php | 8 ++--- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php index 066fed4e8012a..4d4d8abd4abbe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php @@ -26,6 +26,7 @@ class Route extends BaseRoute * Set the controller string on the route. * * @param string $controller + * * @return $this */ public function setController($controller) @@ -39,6 +40,7 @@ public function setController($controller) * Set the request format for this route. * * @param string $format + * * @return $this */ public function setRequestFormat($format) @@ -52,6 +54,7 @@ public function setRequestFormat($format) * Set the locale for this route. * * @param string $locale + * * @return $this */ public function setLocale($locale) @@ -66,6 +69,7 @@ public function setLocale($locale) * supports this, like RouteCollectionBuilder. * * @param string $name + * * @return $this */ public function setName($name) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index a4d0899acc91f..5c2c0405ec874 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -64,9 +64,10 @@ public function __construct(Loader $loader) * * Returns a RouteCollectionBuilder so you can continue to tweak options on the routes. * - * @param mixed $resource + * @param mixed $resource * @param string $prefix * @param string $type + * * @return RouteCollectionBuilder */ public function import($resource, $prefix = null, $type = null) @@ -75,7 +76,7 @@ public function import($resource, $prefix = null, $type = null) $subCollection = $this->loader->import($resource, $type); // turn this into a RouteCollectionBuilder - $builder = new RouteCollectionBuilder($this->loader); + $builder = new self($this->loader); $builder->setPrefix($prefix); $builder->addRouteCollection($subCollection); $this->routes[] = $builder; @@ -86,9 +87,10 @@ public function import($resource, $prefix = null, $type = null) /** * Adds a route and returns it for future modification. * - * @param string $path The route path - * @param string $controller The route controller string - * @param string $name The name to give this route + * @param string $path The route path + * @param string $controller The route controller string + * @param string $name The name to give this route + * * @return Route */ public function add($path, $controller, $name = null) @@ -109,11 +111,12 @@ public function add($path, $controller, $name = null) * Returns a RouteCollectionBuilder that can be configured and then added with mount(). * * @param string $prefix A prefix to apply to all routes added to this collection + * * @return RouteCollectionBuilder */ public function createCollection($prefix = null) { - $builder = new RouteCollectionBuilder($this->loader); + $builder = new self($this->loader); $builder->setPrefix($prefix); return $builder; @@ -143,6 +146,7 @@ public function addRouteCollection(RouteCollection $collection) * Sets a prefix (e.g. /admin) to be used with all embedded routes. * * @param string $prefix + * * @return $this */ public function setPrefix($prefix) @@ -156,6 +160,7 @@ public function setPrefix($prefix) * Sets the host on all embedded routes (unless already set). * * @param string $pattern + * * @return $this */ public function setHost($pattern) @@ -169,6 +174,7 @@ public function setHost($pattern) * Sets a condition on all embedded routes (unless already set). * * @param string $condition + * * @return $this */ public function setCondition($condition) @@ -183,7 +189,8 @@ public function setCondition($condition) * default value is already set. * * @param string $key - * @param mixed $value + * @param mixed $value + * * @return $this */ public function setDefault($key, $value) @@ -198,7 +205,8 @@ public function setDefault($key, $value) * requirement is already set. * * @param string $key - * @param mixed $value + * @param mixed $value + * * @return $this */ public function setRequirement($key, $regex) @@ -213,7 +221,8 @@ public function setRequirement($key, $regex) * option is already set. * * @param string $key - * @param mixed $value + * @param mixed $value + * * @return $this */ public function setOption($key, $value) @@ -227,6 +236,7 @@ public function setOption($key, $value) * Sets the schemes on all embedded routes (unless already set). * * @param array|string $schemes + * * @return $this */ public function setSchemes($schemes) @@ -240,6 +250,7 @@ public function setSchemes($schemes) * Sets the methods on all embedded routes (unless already set). * * @param array|string $methods + * * @return $this */ public function setMethods($methods) @@ -269,6 +280,7 @@ public function addResource(ResourceInterface $resource) * controllerClass won't be applied to that route. * * @param string $controllerClass + * * @return $this */ public function setControllerClass($controllerClass) @@ -325,13 +337,13 @@ public function flush() } $routeCollection->add($name, $route); - } elseif ($route instanceof RouteCollectionBuilder) { + } elseif ($route instanceof self) { $subCollection = $route->flush(); $subCollection->addPrefix($this->prefix); $routeCollection->addCollection($subCollection); } else { - /** @var RouteCollection $route */ + /* @var RouteCollection $route */ $route->addPrefix($this->prefix); $routeCollection->addCollection($route); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index dafbc31ac0435..ac7588ba52157 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -113,7 +113,7 @@ public function testFlushOrdering() 'homepage', 'second_collection_route1', 'second_collection_route2', - 'admin_dashboard' + 'admin_dashboard', ), $actualRouteNames); // make sure the defaults were set @@ -230,11 +230,11 @@ public function testFlushSetsDetailsOnChildrenRoutes() $this->assertEquals(1, $actualListRoute->getDefault('page')); $this->assertEquals('\d+', $actualListRoute->getRequirement('id')); - $this->assertEquals(true, $actualListRoute->getOption('expose')); + $this->assertTrue($actualListRoute->getOption('expose')); // none of these should be overridden $this->assertEquals('html', $actualListRoute->getDefault('_format')); $this->assertEquals('json|xml', $actualListRoute->getRequirement('_format')); - $this->assertEquals(true, $actualListRoute->getOption('fooBar')); + $this->assertTrue($actualListRoute->getOption('fooBar')); $this->assertEquals('example.com', $actualListRoute->getHost()); $this->assertEquals('request.isSecure()', $actualListRoute->getCondition()); $this->assertEquals(array('https'), $actualListRoute->getSchemes()); @@ -242,7 +242,7 @@ public function testFlushSetsDetailsOnChildrenRoutes() // inherited from the main collection $this->assertEquals('fr', $actualListRoute->getDefault('_locale')); $this->assertEquals('fr|en', $actualListRoute->getRequirement('_locale')); - $this->assertEquals(true, $actualListRoute->getOption('niceRoute')); + $this->assertTrue($actualListRoute->getOption('niceRoute')); $actualEditRoute = $collection->get('blog_edit'); // inherited from the collection From 729ccbb4d312ad5765195b3c677984e013f675cd Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 19:55:59 -0400 Subject: [PATCH 11/34] Renaming flush() to build() --- .../Routing/RouteCollectionBuilder.php | 6 +++--- .../Routing/RouteCollectionBuilderTest.php | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 5c2c0405ec874..8330b96cec798 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -16,7 +16,7 @@ use Symfony\Component\Routing\RouteCollection; /** - * Helps add and import routes into a RouteCollection.. + * Helps add and import routes into a RouteCollection. * * @author Ryan Weaver */ @@ -299,7 +299,7 @@ public function setControllerClass($controllerClass) * * @return RouteCollection */ - public function flush() + public function build() { $routeCollection = new RouteCollection(); @@ -338,7 +338,7 @@ public function flush() $routeCollection->add($name, $route); } elseif ($route instanceof self) { - $subCollection = $route->flush(); + $subCollection = $route->build(); $subCollection->addPrefix($this->prefix); $routeCollection->addCollection($subCollection); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index ac7588ba52157..679ee7ad9dd0b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -39,7 +39,7 @@ public function testImport() $this->assertInstanceOf('Symfony\Bundle\FrameworkBundle\Routing\RouteCollectionBuilder', $addedBuilder); // get the collection back so we can look at it - $addedCollection = $addedBuilder->flush(); + $addedCollection = $addedBuilder->build(); $route = $addedCollection->get('one_test_route'); $this->assertNotNull($route); $this->assertEquals('/admin/foo/path', $route->getPath(), 'The prefix should be applied'); @@ -100,7 +100,7 @@ public function testFlushOrdering() // set an extra resource $collectionBuilder->addResource(new FileResource('foo_routing.xml')); - $actualCollection = $collectionBuilder->flush(); + $actualCollection = $collectionBuilder->build(); $this->assertCount(9, $actualCollection); $actualRouteNames = array_keys($actualCollection->all()); @@ -139,7 +139,7 @@ public function testFlushSetsRouteNames() $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') ->setMethods('GET'); - $actualCollection = $collectionBuilder->flush(); + $actualCollection = $collectionBuilder->build(); $actualRouteNames = array_keys($actualCollection->all()); $this->assertEquals(array( 'admin_dashboard', @@ -167,12 +167,12 @@ public function testFlushClearsEverything() $collectionBuilder->addResource(new FileResource('foo_routing.xml')); // flush once - $collectionBuilder->flush(); + $collectionBuilder->build(); // flush again - should not contain previous stuff $collectionBuilder->add('/blogs', 'list') ->setName('blog_list'); - $secondCollection = $collectionBuilder->flush(); + $secondCollection = $collectionBuilder->build(); $this->assertCount(1, $secondCollection); $this->assertCount(0, $secondCollection->getResources()); @@ -225,7 +225,7 @@ public function testFlushSetsDetailsOnChildrenRoutes() ->setSchemes('http') ->setMethods(array('GET', 'POST')); - $collection = $routes->flush(); + $collection = $routes->build(); $actualListRoute = $collection->get('blog_list'); $this->assertEquals(1, $actualListRoute->getDefault('page')); @@ -262,7 +262,7 @@ public function testFlushPrefixesPaths($collectionPrefix, $routePath, $expectedP $routes->setPrefix($collectionPrefix); $routes->add($routePath, 'someController', 'test_route'); - $collection = $routes->flush(); + $collection = $routes->build(); $this->assertEquals($expectedPath, $collection->get('test_route')->getPath()); } @@ -322,7 +322,7 @@ public function testFlushSetsPrefixedWithMultipleLevels() // setting the prefix via this method has no affect (i.e. no /imported/imported) $routeBuilderFromImport->setPrefix('/imported'); - $collection = $routes->flush(); + $collection = $routes->build(); $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix'); $this->assertEquals('/admin/users', $collection->get('admin_users')->getPath(), 'Routes after mounting have the prefix'); $this->assertEquals('/admin/blog/new', $collection->get('admin_blog_new')->getPath(), 'Sub-collections receive prefix even if mounted before parent prefix'); @@ -340,7 +340,7 @@ public function testSetControllerClass($routeController, $controllerClass, $expe $routes->setControllerClass('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController'); $routes->add('/', $routeController, 'test_route'); - $collection = $routes->flush(); + $collection = $routes->build(); $this->assertEquals($expectedFinalController, $collection->get('test_route')->getDefault('_controller')); } From e50995387b7afd4e262a3413b015979e1c37b3b7 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 19:56:43 -0400 Subject: [PATCH 12/34] Not clearing everything on build - unnecessary, and the RouteCollectionBuilder is not meant to be re-used --- .../Routing/RouteCollectionBuilder.php | 13 ------ .../Routing/RouteCollectionBuilderTest.php | 41 ------------------- 2 files changed, 54 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 8330b96cec798..fc99d8fc6e539 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -354,19 +354,6 @@ public function build() $routeCollection->addResource($resource); } - // reset all the values - $this->routes = array(); - $this->resources = array(); - $this->defaults = array(); - $this->options = array(); - $this->requirements = array(); - $this->prefix = null; - $this->host = null; - $this->condition = null; - $this->schemes = null; - $this->methods = null; - $this->controllerClass = null; - return $routeCollection; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index 679ee7ad9dd0b..d8f44d114da59 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -147,47 +147,6 @@ public function testFlushSetsRouteNames() ), $actualRouteNames); } - public function testFlushClearsEverything() - { - $loader = $this->getLoader(); - $collectionBuilder = new RouteCollectionBuilder($loader); - - // add a "named" route - $collectionBuilder->add('/post', 'AppBundle:Admin:dashboard') - ->setName('admin_post'); - $collectionBuilder->setPrefix('/admin'); - $collectionBuilder->setDefault('_locale', 'fr'); - $collectionBuilder->setMethods('POST'); - $collectionBuilder->setSchemes('https'); - $collectionBuilder->setCondition('foo'); - $collectionBuilder->setControllerClass('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController'); - $collectionBuilder->setHost('example.com'); - $collectionBuilder->setOption('expose', true); - $collectionBuilder->setRequirement('id', '\d+'); - $collectionBuilder->addResource(new FileResource('foo_routing.xml')); - - // flush once - $collectionBuilder->build(); - - // flush again - should not contain previous stuff - $collectionBuilder->add('/blogs', 'list') - ->setName('blog_list'); - $secondCollection = $collectionBuilder->build(); - - $this->assertCount(1, $secondCollection); - $this->assertCount(0, $secondCollection->getResources()); - $blogListRoute = $secondCollection->get('blog_list'); - $this->assertArrayNotHasKey('_locale', $blogListRoute->getDefaults()); - $this->assertEmpty($blogListRoute->getMethods()); - $this->assertEmpty($blogListRoute->getSchemes()); - $this->assertEmpty($blogListRoute->getCondition()); - // controller class should not have been added - $this->assertEquals('list', $blogListRoute->getDefault('_controller')); - $this->assertEmpty($blogListRoute->getHost()); - $this->assertNull($blogListRoute->getOption('expose')); - $this->assertNull($blogListRoute->getRequirement('id')); - } - public function testFlushSetsDetailsOnChildrenRoutes() { $loader = $this->getLoader(); From 01e1329910ae1ad3b64596a98ac5c1c9d701a054 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 19:57:28 -0400 Subject: [PATCH 13/34] Fixing phpdoc --- .../Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index fc99d8fc6e539..4a9b8a4421e7d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -205,7 +205,7 @@ public function setDefault($key, $value) * requirement is already set. * * @param string $key - * @param mixed $value + * @param mixed $regex * * @return $this */ From e39e0c4a86fb4f5312cac1521eacc2f659968068 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 20:11:27 -0400 Subject: [PATCH 14/34] Removing the FrameworkBundle Route and the ability to call Route::setName() Calling Route::setName() (because this value normally lives on the RouteCollection) made things uglier between normal Route objects and these new Route objects with setName() methods. To support both in RouteCollectionBuilder, we would have needed to do some weird things. Overall, this reduced the scope of the pull request, without too much penalty. --- .../Bundle/FrameworkBundle/Routing/Route.php | 105 ------------------ .../Routing/RouteCollectionBuilder.php | 50 ++++++--- .../Routing/RouteCollectionBuilderTest.php | 18 ++- .../Tests/Routing/RouteTest.php | 67 ----------- 4 files changed, 44 insertions(+), 196 deletions(-) delete mode 100644 src/Symfony/Bundle/FrameworkBundle/Routing/Route.php delete mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php deleted file mode 100644 index 4d4d8abd4abbe..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Route.php +++ /dev/null @@ -1,105 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Routing; - -use Symfony\Component\Routing\Route as BaseRoute; - -/** - * A framework-specific Route object to help with common route tasks. - * - * @author Ryan Weaver - */ -class Route extends BaseRoute -{ - private $name; - - /** - * Set the controller string on the route. - * - * @param string $controller - * - * @return $this - */ - public function setController($controller) - { - $this->setDefault('_controller', $controller); - - return $this; - } - - /** - * Set the request format for this route. - * - * @param string $format - * - * @return $this - */ - public function setRequestFormat($format) - { - $this->setDefault('_format', $format); - - return $this; - } - - /** - * Set the locale for this route. - * - * @param string $locale - * - * @return $this - */ - public function setLocale($locale) - { - $this->setDefault('_locale', $locale); - - return $this; - } - - /** - * Set the name of this route - IF this route is added via a mechanism that - * supports this, like RouteCollectionBuilder. - * - * @param string $name - * - * @return $this - */ - public function setName($name) - { - $this->name = $name; - - return $this; - } - - public function getName() - { - return $this->name; - } - - /** - * Generates a route name based on details of this route. - * - * @return string - */ - public function generateRouteName() - { - $methods = implode('_', $this->getMethods()).'_'; - - $routeName = $methods.$this->getPath(); - $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); - $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); - - // Collapse consecutive underscores down into a single underscore. - $routeName = preg_replace('/_+/', '_', $routeName); - - return $routeName; - } -} diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 4a9b8a4421e7d..c5c7543c79f84 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -14,6 +14,7 @@ use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; /** * Helps add and import routes into a RouteCollection. @@ -87,22 +88,24 @@ public function import($resource, $prefix = null, $type = null) /** * Adds a route and returns it for future modification. * - * @param string $path The route path - * @param string $controller The route controller string - * @param string $name The name to give this route + * @param string $path The route path + * @param string $controller The route controller string + * @param string|null $name The name to give this route * * @return Route */ public function add($path, $controller, $name = null) { - // what if we have some other Route sub-class in our project: CmsRoute - // is it really ok to copy all the CmsRoute objects to our Route? - // -> we MUST somehow allow Component Route objects (no name attached) - $route = new Route($path); - $route->setController($controller); - $route->setName($name); - $this->routes[] = $route; + $route->setDefault('_controller', $controller); + + if (null === $name) { + // un-named routes have integer keys, are named later + $this->routes[] = $route; + } else { + // case the $name into a string, to make sure a number is a string + $this->routes[(string)$name] = $route; + } return $route; } @@ -303,11 +306,11 @@ public function build() { $routeCollection = new RouteCollection(); - foreach ($this->routes as $route) { + foreach ($this->routes as $name => $route) { if ($route instanceof Route) { - // auto-generate the route name if needed - if (!$name = $route->getName()) { - $name = $route->generateRouteName(); + // auto-generate the route name if it is just an index key + if (is_int($name)) { + $name = $this->generateRouteName($route); } $this->ensureRouteController($route); @@ -389,4 +392,23 @@ private function ensureRouteController(Route $route) $controller = sprintf('%s::%s', $this->controllerClass, $controller); $route->setDefault('_controller', $controller); } + + /** + * Generates a route name based on details of this route. + * + * @return string + */ + private function generateRouteName(Route $route) + { + $methods = implode('_', $route->getMethods()).'_'; + + $routeName = $methods.$route->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index d8f44d114da59..ce87ba8e2ed4f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -52,9 +52,11 @@ public function testAdd() $addedRoute = $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout'); $addedRoute2 = $collectionBuilder->add('/blogs', 'AppBundle:Blog:list', 'blog_list'); - $this->assertInstanceOf('Symfony\Bundle\FrameworkBundle\Routing\Route', $addedRoute); + $this->assertInstanceOf('Symfony\Component\Routing\Route', $addedRoute); $this->assertEquals('AppBundle:Order:checkout', $addedRoute->getDefault('_controller')); - $this->assertEquals('blog_list', $addedRoute2->getName()); + + $finalCollection = $collectionBuilder->build(); + $this->assertSame($addedRoute2, $finalCollection->get('blog_list')); } public function testFlushOrdering() @@ -80,20 +82,17 @@ public function testFlushOrdering() $collectionBuilder = new RouteCollectionBuilder($loader); // 1) Add a route - $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout') - ->setName('checkout_route'); + $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout', 'checkout_route'); // 2) Add a collection directly $collectionBuilder->addRouteCollection($loadedCollection1); // 3) Import from a file $collectionBuilder->import('admin_routing.yml'); // 4) Add another route - $collectionBuilder->add('/', 'AppBundle:Default:homepage') - ->setName('homepage'); + $collectionBuilder->add('/', 'AppBundle:Default:homepage', 'homepage'); // 5) Add another collection $collectionBuilder->addRouteCollection($loadedCollection2); // 6) Add another route - $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard') - ->setName('admin_dashboard'); + $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); // set a default value $collectionBuilder->setDefault('_locale', 'fr'); @@ -133,8 +132,7 @@ public function testFlushSetsRouteNames() $collectionBuilder = new RouteCollectionBuilder($loader); // add a "named" route - $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard') - ->setName('admin_dashboard'); + $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); // add an unnamed route $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') ->setMethods('GET'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteTest.php deleted file mode 100644 index 3311aec642d18..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteTest.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Tests\Routing; - -use Symfony\Bundle\FrameworkBundle\Routing\Route; - -class RouteTest extends \PHPUnit_Framework_TestCase -{ - public function testSetController() - { - $route = new Route('/foo'); - $route->setController('AppBundle:Pet:puppy'); - - $this->assertEquals('AppBundle:Pet:puppy', $route->getDefault('_controller')); - } - - public function testSetRequestFormat() - { - $route = new Route('/foo'); - $route->setRequestFormat('json'); - - $this->assertEquals('json', $route->getDefault('_format')); - } - - public function testSetLocale() - { - $route = new Route('/foo'); - $route->setLocale('de'); - - $this->assertEquals('de', $route->getDefault('_locale')); - } - - public function testSetName() - { - $route = new Route('/foo'); - $route->setName('foo_route'); - - $this->assertEquals('foo_route', $route->getName()); - } - - /** - * @dataProvider provideRouteAndExpectedRouteName - */ - public function testDefaultRouteNameGeneration(Route $route, $expectedRouteName) - { - $this->assertEquals($expectedRouteName, $route->generateRouteName()); - } - - public function provideRouteAndExpectedRouteName() - { - return array( - array(new Route('/Invalid%Symbols#Stripped', array(), array(), array(), '', array(), array('POST')), 'POST_InvalidSymbolsStripped'), - array(new Route('/post/{id}', array(), array(), array(), '', array(), array('GET')), 'GET_post_id'), - array(new Route('/colon:pipe|dashes-escaped'), '_colon_pipe_dashes_escaped'), - array(new Route('/underscores_and.periods'), '_underscores_and.periods'), - ); - } -} From df1849ffd6e5e693fb2baa9732291d0177533edf Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 20:29:21 -0400 Subject: [PATCH 15/34] Simplifying by transforming RouteCollection's into RouteCollectionBuilder's This means that the RouteCollectionBuilder only nests Routes and RouteCollectionBuilders. It also means that when you import, those routes are added at the top level, whereas before we added the RouteCollection directly into the builder. --- .../Routing/RouteCollectionBuilder.php | 43 +++++++++++-------- .../Routing/RouteCollectionBuilderTest.php | 4 +- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index c5c7543c79f84..2c7081e194fda 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -28,7 +28,7 @@ class RouteCollectionBuilder /** * A mixture of different objects that hold routes. * - * @var Route[]|RouteCollectionBuilder[]|RouteCollection[] + * @var Route[]|RouteCollectionBuilder[] */ private $routes = array(); @@ -75,14 +75,9 @@ public function import($resource, $prefix = null, $type = null) { /** @var RouteCollection $subCollection */ $subCollection = $this->loader->import($resource, $type); + $subCollection->addPrefix($prefix); - // turn this into a RouteCollectionBuilder - $builder = new self($this->loader); - $builder->setPrefix($prefix); - $builder->addRouteCollection($subCollection); - $this->routes[] = $builder; - - return $builder; + return $this->addRouteCollection($subCollection); } /** @@ -128,21 +123,34 @@ public function createCollection($prefix = null) /** * Add a RouteCollectionBuilder. * - * @param RouteCollectionBuilder $routes + * @param RouteCollectionBuilder $builder */ - public function mount(RouteCollectionBuilder $routes) + public function mount(RouteCollectionBuilder $builder) { - $this->routes[] = $routes; + $this->routes[] = $builder; } /** - * Adds a RouteCollection directly. + * Adds a RouteCollection directly and returns those routes in a RouteCollectionBuilder. * * @param RouteCollection $collection + * @return $this */ public function addRouteCollection(RouteCollection $collection) { - $this->routes[] = $collection; + // create a builder from the RouteCollection + $builder = new self($this->loader); + foreach ($collection->all() as $name => $route) { + $builder->routes[(string) $name] = $route; + } + + foreach ($collection->getResources() as $resource) { + $builder->addResource($resource); + } + + $this->mount($builder); + + return $builder; } /** @@ -340,16 +348,13 @@ public function build() } $routeCollection->add($name, $route); - } elseif ($route instanceof self) { + } else { + /** @var self $route */ + $subCollection = $route->build(); $subCollection->addPrefix($this->prefix); $routeCollection->addCollection($subCollection); - } else { - /* @var RouteCollection $route */ - $route->addPrefix($this->prefix); - - $routeCollection->addCollection($route); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index ce87ba8e2ed4f..e45d3cee0e4d6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -275,9 +275,7 @@ public function testFlushSetsPrefixedWithMultipleLevels() ->method('import') ->will($this->returnValue($importedCollection)); // import this from the /admin route builder - $routeBuilderFromImport = $adminRoutes->import('admin.yml', '/imported'); - // setting the prefix via this method has no affect (i.e. no /imported/imported) - $routeBuilderFromImport->setPrefix('/imported'); + $adminRoutes->import('admin.yml', '/imported'); $collection = $routes->build(); $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix'); From 97b1eea7f5e8b0146cfa1dd0c2156c693bdfd603 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 20:48:42 -0400 Subject: [PATCH 16/34] Fixing a bug with knowing which keys should be auto-generated --- .../Routing/RouteCollectionBuilder.php | 34 +++++++++++++------ .../Routing/RouteCollectionBuilderTest.php | 4 +++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 2c7081e194fda..fe13508596be2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -93,14 +93,7 @@ public function add($path, $controller, $name = null) { $route = new Route($path); $route->setDefault('_controller', $controller); - - if (null === $name) { - // un-named routes have integer keys, are named later - $this->routes[] = $route; - } else { - // case the $name into a string, to make sure a number is a string - $this->routes[(string)$name] = $route; - } + $this->addRoute($route, $name); return $route; } @@ -141,7 +134,7 @@ public function addRouteCollection(RouteCollection $collection) // create a builder from the RouteCollection $builder = new self($this->loader); foreach ($collection->all() as $name => $route) { - $builder->routes[(string) $name] = $route; + $builder->addRoute($route, $name); } foreach ($collection->getResources() as $resource) { @@ -153,6 +146,25 @@ public function addRouteCollection(RouteCollection $collection) return $builder; } + /** + * Adds a Route object to the builder. + * + * @param Route $route + * @param string|null $name + * @return $this + */ + public function addRoute(Route $route, $name = null) + { + if (null === $name) { + // used as a flag to know which routes will need a name later + $name = '_unnamed_route_'.spl_object_hash($route); + } + + $this->routes[$name] = $route; + + return $this; + } + /** * Sets a prefix (e.g. /admin) to be used with all embedded routes. * @@ -316,8 +328,8 @@ public function build() foreach ($this->routes as $name => $route) { if ($route instanceof Route) { - // auto-generate the route name if it is just an index key - if (is_int($name)) { + // auto-generate the route name if it's been marked + if ('_unnamed_route_' === substr($name, 0, 15)) { $name = $this->generateRouteName($route); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index e45d3cee0e4d6..316f18931cfd4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -137,11 +137,15 @@ public function testFlushSetsRouteNames() $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') ->setMethods('GET'); + // integer route names are allowed - they don't confuse things + $collectionBuilder->add('/products', 'AppBundle:Product:list', 100); + $actualCollection = $collectionBuilder->build(); $actualRouteNames = array_keys($actualCollection->all()); $this->assertEquals(array( 'admin_dashboard', 'GET_blogs', + '100' ), $actualRouteNames); } From e1ecde4e7f4a4ea52c051cfa2be1bd7ce74952c7 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 20:50:11 -0400 Subject: [PATCH 17/34] Minor code improvement to centralize things --- .../FrameworkBundle/Routing/RouteCollectionBuilder.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index fe13508596be2..a07932fca6e13 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -75,9 +75,8 @@ public function import($resource, $prefix = null, $type = null) { /** @var RouteCollection $subCollection */ $subCollection = $this->loader->import($resource, $type); - $subCollection->addPrefix($prefix); - return $this->addRouteCollection($subCollection); + return $this->addRouteCollection($subCollection, $prefix); } /** @@ -127,12 +126,13 @@ public function mount(RouteCollectionBuilder $builder) * Adds a RouteCollection directly and returns those routes in a RouteCollectionBuilder. * * @param RouteCollection $collection + * @param string|null $prefix * @return $this */ - public function addRouteCollection(RouteCollection $collection) + public function addRouteCollection(RouteCollection $collection, $prefix = null) { // create a builder from the RouteCollection - $builder = new self($this->loader); + $builder = $this->createCollection($prefix); foreach ($collection->all() as $name => $route) { $builder->addRoute($route, $name); } From ecf43462cde717c320217dcdebcdd5d36a64821f Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 20:57:34 -0400 Subject: [PATCH 18/34] Renaming methods for clarity and consistency --- .../Routing/RouteCollectionBuilder.php | 10 +++++----- .../Tests/Routing/RouteCollectionBuilderTest.php | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index a07932fca6e13..2484d7c1b1d0a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -98,13 +98,13 @@ public function add($path, $controller, $name = null) } /** - * Returns a RouteCollectionBuilder that can be configured and then added with mount(). + * Returns a RouteCollectionBuilder that can be configured and then added with addBuilder(). * * @param string $prefix A prefix to apply to all routes added to this collection * * @return RouteCollectionBuilder */ - public function createCollection($prefix = null) + public function createBuilder($prefix = null) { $builder = new self($this->loader); $builder->setPrefix($prefix); @@ -117,7 +117,7 @@ public function createCollection($prefix = null) * * @param RouteCollectionBuilder $builder */ - public function mount(RouteCollectionBuilder $builder) + public function addBuilder(RouteCollectionBuilder $builder) { $this->routes[] = $builder; } @@ -132,7 +132,7 @@ public function mount(RouteCollectionBuilder $builder) public function addRouteCollection(RouteCollection $collection, $prefix = null) { // create a builder from the RouteCollection - $builder = $this->createCollection($prefix); + $builder = $this->createBuilder($prefix); foreach ($collection->all() as $name => $route) { $builder->addRoute($route, $name); } @@ -141,7 +141,7 @@ public function addRouteCollection(RouteCollection $collection, $prefix = null) $builder->addResource($resource); } - $this->mount($builder); + $this->addBuilder($builder); return $builder; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index 316f18931cfd4..2d9cc65fbe700 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -252,24 +252,24 @@ public function testFlushSetsPrefixedWithMultipleLevels() $routes->add('homepage', 'MainController::homepageAction', 'homepage'); - $adminRoutes = $routes->createCollection('/admin'); + $adminRoutes = $routes->createBuilder('/admin'); $adminRoutes->add('/dashboard', 'AdminController::dashboardAction', 'admin_dashboard'); // embedded collection under /admin - $adminBlogRoutes = $routes->createCollection('/blog'); + $adminBlogRoutes = $routes->createBuilder('/blog'); $adminBlogRoutes->add('/new', 'BlogController::newAction', 'admin_blog_new'); // mount into admin, but before the parent collection has been mounted - $adminRoutes->mount($adminBlogRoutes); + $adminRoutes->addBuilder($adminBlogRoutes); // now mount the /admin routes, above should all still be /blog/admin - $routes->mount($adminRoutes); + $routes->addBuilder($adminRoutes); // add a route after mounting $adminRoutes->add('/users', 'AdminController::userAction', 'admin_users'); // add another sub-collection after the mount - $otherAdminRoutes = $routes->createCollection('/stats'); + $otherAdminRoutes = $routes->createBuilder('/stats'); $otherAdminRoutes->add('/sales', 'StatsController::indexAction', 'admin_stats_sales'); - $adminRoutes->mount($otherAdminRoutes); + $adminRoutes->addBuilder($otherAdminRoutes); // add a normal collection and see that it is also prefixed $importedCollection = new RouteCollection(); From 0dce55dff4da56807892995baf99359fc5f5d0bf Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 15 Sep 2015 21:18:08 -0400 Subject: [PATCH 19/34] fabbot and possible test fixes --- .../FrameworkBundle/Routing/RouteCollectionBuilder.php | 4 +++- .../Tests/Routing/RouteCollectionBuilderTest.php | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 2484d7c1b1d0a..18198bd50e4c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -127,6 +127,7 @@ public function addBuilder(RouteCollectionBuilder $builder) * * @param RouteCollection $collection * @param string|null $prefix + * * @return $this */ public function addRouteCollection(RouteCollection $collection, $prefix = null) @@ -151,6 +152,7 @@ public function addRouteCollection(RouteCollection $collection, $prefix = null) * * @param Route $route * @param string|null $name + * * @return $this */ public function addRoute(Route $route, $name = null) @@ -361,7 +363,7 @@ public function build() $routeCollection->add($name, $route); } else { - /** @var self $route */ + /* @var self $route */ $subCollection = $route->build(); $subCollection->addPrefix($this->prefix); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index 2d9cc65fbe700..12712464d2ee3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -135,7 +135,7 @@ public function testFlushSetsRouteNames() $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); // add an unnamed route $collectionBuilder->add('/blogs', 'AppBundle:Blog:list') - ->setMethods('GET'); + ->setMethods(array('GET')); // integer route names are allowed - they don't confuse things $collectionBuilder->add('/products', 'AppBundle:Product:list', 100); @@ -145,7 +145,7 @@ public function testFlushSetsRouteNames() $this->assertEquals(array( 'admin_dashboard', 'GET_blogs', - '100' + '100', ), $actualRouteNames); } @@ -165,8 +165,8 @@ public function testFlushSetsDetailsOnChildrenRoutes() ->setOption('fooBar', true) ->setHost('example.com') ->setCondition('request.isSecure()') - ->setSchemes('https') - ->setMethods('POST'); + ->setSchemes(array('https')) + ->setMethods(array('POST')); // a simple route, nothing added to it $routes->add('/blogs/{id}', 'editAction', 'blog_edit'); @@ -183,7 +183,7 @@ public function testFlushSetsDetailsOnChildrenRoutes() ->setDefault('_locale', 'fr') ->setRequirement('_locale', 'fr|en') ->setOption('niceRoute', true) - ->setSchemes('http') + ->setSchemes(array('http')) ->setMethods(array('GET', 'POST')); $collection = $routes->build(); From f3d71ad1c946a8bc7dee78ffab0a9d4460375326 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 16 Sep 2015 21:56:43 -0400 Subject: [PATCH 20/34] Allowing LoaderInterface instead of Loader --- .../Routing/RouteCollectionBuilder.php | 34 ++++++++++++++++--- .../Routing/RouteCollectionBuilderTest.php | 32 ++++++++++++----- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 18198bd50e4c0..ed71b027d8f6a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -11,7 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Routing; -use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; @@ -53,9 +54,9 @@ class RouteCollectionBuilder private $controllerClass; /** - * @param Loader $loader + * @param LoaderInterface $loader */ - public function __construct(Loader $loader) + public function __construct(LoaderInterface $loader) { $this->loader = $loader; } @@ -74,7 +75,7 @@ public function __construct(Loader $loader) public function import($resource, $prefix = null, $type = null) { /** @var RouteCollection $subCollection */ - $subCollection = $this->loader->import($resource, $type); + $subCollection = $this->resolve($resource, $type)->load($resource, $type); return $this->addRouteCollection($subCollection, $prefix); } @@ -430,4 +431,29 @@ private function generateRouteName(Route $route) return $routeName; } + + /** + * Finds a loader able to load an imported resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return LoaderInterface A LoaderInterface instance + * + * @throws FileLoaderLoadException If no loader is found + */ + private function resolve($resource, $type = null) + { + if ($this->loader->supports($resource, $type)) { + return $this->loader; + } + + $loader = null === $this->loader->getResolver() ? false : $this->loader->getResolver()->resolve($resource, $type); + + if (false === $loader) { + throw new FileLoaderLoadException($resource); + } + + return $loader; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index 12712464d2ee3..e633ebe5d47d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -20,17 +20,27 @@ class RouteCollectionBuilderTest extends \PHPUnit_Framework_TestCase { public function testImport() { - $loader = $this->getLoader(); + $resolvedLoader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $resolver = $this->getMock('Symfony\Component\Config\Loader\LoaderResolverInterface'); + $resolver->expects($this->once()) + ->method('resolve') + ->with('admin_routing.yml', 'yaml') + ->will($this->returnValue($resolvedLoader)); $expectedCollection = new RouteCollection(); $expectedCollection->add('one_test_route', new Route('/foo/path')); - $loader + $resolvedLoader ->expects($this->once()) - ->method('import') + ->method('load') ->with('admin_routing.yml', 'yaml') ->will($this->returnValue($expectedCollection)); + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $loader->expects($this->any()) + ->method('getResolver') + ->will($this->returnValue($resolver)); + // import the file! (with a prefix) $collectionBuilder = new RouteCollectionBuilder($loader); $addedBuilder = $collectionBuilder->import('admin_routing.yml', '/admin', 'yaml'); @@ -74,9 +84,13 @@ public function testFlushOrdering() $importedCollection->add('imported_route2', new Route('/imported/foo2')); $loader = $this->getLoader(); + // make this loader able to do the import - keeps mocking simple + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); $loader ->expects($this->once()) - ->method('import') + ->method('load') ->will($this->returnValue($importedCollection)); $collectionBuilder = new RouteCollectionBuilder($loader); @@ -274,9 +288,13 @@ public function testFlushSetsPrefixedWithMultipleLevels() // add a normal collection and see that it is also prefixed $importedCollection = new RouteCollection(); $importedCollection->add('imported_route', new Route('/foo')); + // make this loader able to do the import - keeps mocking simple + $loader->expects($this->any()) + ->method('supports') + ->will($this->returnValue(true)); $loader ->expects($this->any()) - ->method('import') + ->method('load') ->will($this->returnValue($importedCollection)); // import this from the /admin route builder $adminRoutes->import('admin.yml', '/imported'); @@ -336,9 +354,7 @@ public function testExceptiononBadControllerClass() private function getLoader() { - $loader = $this->getMockBuilder('Symfony\Component\Config\Loader\Loader') - ->disableOriginalConstructor() - ->getMock(); + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); return $loader; } From 8132fcb5f75a2b441d51f2fde2e67d820557e306 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 16 Sep 2015 22:54:58 -0400 Subject: [PATCH 21/34] Updating setRequirements to avoid deprecated calls The problem is that Route::setMethods() ultimately sets the _method requirement for BC. But then, calling $route->getRequirements() later would return _method, which we'd then set on the route. This avoids that. --- .../FrameworkBundle/Routing/RouteCollectionBuilder.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index ed71b027d8f6a..23c2627ee6dc3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -339,9 +339,15 @@ public function build() $this->ensureRouteController($route); $route->setDefaults(array_merge($this->defaults, $route->getDefaults())); - $route->setRequirements(array_merge($this->requirements, $route->getRequirements())); $route->setOptions(array_merge($this->options, $route->getOptions())); + // we're extra careful here to avoid re-setting deprecated _method and _scheme + foreach ($this->requirements as $key => $val) { + if (!$route->hasRequirement($key)) { + $route->setRequirement($key, $val); + } + } + if (null !== $this->prefix) { $route->setPath('/'.$this->prefix.$route->getPath()); } From b2676ecf60f06413fc596516880b25d999112f6e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 26 Sep 2015 10:41:54 -0400 Subject: [PATCH 22/34] phpdoc typo --- .../Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 23c2627ee6dc3..d6be71fff96ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -299,7 +299,7 @@ public function addResource(ResourceInterface $resource) } /** - * Set a controller class that all added embedded should use. + * Set a controller class that all added embedded routes should use. * * With this, the controller for embedded routes can just be a method name. * If an embedded route has a full controller (e.g. class::methodName), the From bf6790b95f603644fac72aa1875c0588cb6f8edb Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 29 Sep 2015 21:51:11 -0400 Subject: [PATCH 23/34] Making RouteCollectionBuilder's LoaderInteface optional Just throw an exception if they try to import --- .../Routing/RouteCollectionBuilder.php | 6 ++- .../Routing/RouteCollectionBuilderTest.php | 40 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index d6be71fff96ce..8e479ce482255 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -56,7 +56,7 @@ class RouteCollectionBuilder /** * @param LoaderInterface $loader */ - public function __construct(LoaderInterface $loader) + public function __construct(LoaderInterface $loader = null) { $this->loader = $loader; } @@ -450,6 +450,10 @@ private function generateRouteName(Route $route) */ private function resolve($resource, $type = null) { + if (null === $this->loader) { + throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.'); + } + if ($this->loader->supports($resource, $type)) { return $this->loader; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php index e633ebe5d47d0..12738fdf4e8c9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php @@ -55,10 +55,18 @@ public function testImport() $this->assertEquals('/admin/foo/path', $route->getPath(), 'The prefix should be applied'); } + /** + * @expectedException \BadMethodCallException + */ + public function testImportWithoutLoaderThrowsException() + { + $collectionBuilder = new RouteCollectionBuilder(); + $collectionBuilder->import('routing.yml'); + } + public function testAdd() { - $loader = $this->getLoader(); - $collectionBuilder = new RouteCollectionBuilder($loader); + $collectionBuilder = new RouteCollectionBuilder(); $addedRoute = $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout'); $addedRoute2 = $collectionBuilder->add('/blogs', 'AppBundle:Blog:list', 'blog_list'); @@ -83,7 +91,7 @@ public function testFlushOrdering() $importedCollection->add('imported_route1', new Route('/imported/foo1')); $importedCollection->add('imported_route2', new Route('/imported/foo2')); - $loader = $this->getLoader(); + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); // make this loader able to do the import - keeps mocking simple $loader->expects($this->any()) ->method('supports') @@ -142,8 +150,7 @@ public function testFlushOrdering() public function testFlushSetsRouteNames() { - $loader = $this->getLoader(); - $collectionBuilder = new RouteCollectionBuilder($loader); + $collectionBuilder = new RouteCollectionBuilder(); // add a "named" route $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); @@ -165,8 +172,7 @@ public function testFlushSetsRouteNames() public function testFlushSetsDetailsOnChildrenRoutes() { - $loader = $this->getLoader(); - $routes = new RouteCollectionBuilder($loader); + $routes = new RouteCollectionBuilder(); $routes->add('/blogs/{page}', 'listAction', 'blog_list') // unique things for the route @@ -232,8 +238,7 @@ public function testFlushSetsDetailsOnChildrenRoutes() */ public function testFlushPrefixesPaths($collectionPrefix, $routePath, $expectedPath) { - $loader = $this->getLoader(); - $routes = new RouteCollectionBuilder($loader); + $routes = new RouteCollectionBuilder(); $routes->setPrefix($collectionPrefix); $routes->add($routePath, 'someController', 'test_route'); @@ -261,7 +266,7 @@ public function providePrefixTests() public function testFlushSetsPrefixedWithMultipleLevels() { - $loader = $this->getLoader(); + $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); $routes = new RouteCollectionBuilder($loader); $routes->add('homepage', 'MainController::homepageAction', 'homepage'); @@ -312,8 +317,7 @@ public function testFlushSetsPrefixedWithMultipleLevels() */ public function testSetControllerClass($routeController, $controllerClass, $expectedFinalController) { - $loader = $this->getLoader(); - $routes = new RouteCollectionBuilder($loader); + $routes = new RouteCollectionBuilder(); $routes->setControllerClass('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController'); $routes->add('/', $routeController, 'test_route'); @@ -342,20 +346,12 @@ public function provideControllerClassTests() } /** - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException */ public function testExceptiononBadControllerClass() { - $loader = $this->getLoader(); - $routes = new RouteCollectionBuilder($loader); + $routes = new RouteCollectionBuilder(); $routes->setControllerClass('Acme\FakeController'); } - - private function getLoader() - { - $loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); - - return $loader; - } } From 61e4bf743563fb9dc18c776d4f80aef374ca3bf0 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 29 Sep 2015 21:52:27 -0400 Subject: [PATCH 24/34] removing extra spaces --- .../Routing/RouteCollectionBuilder.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php index 8e479ce482255..41f7ef0fe603d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php @@ -24,8 +24,6 @@ */ class RouteCollectionBuilder { - private $loader; - /** * A mixture of different objects that hold routes. * @@ -33,24 +31,16 @@ class RouteCollectionBuilder */ private $routes = array(); + private $loader; private $defaults = array(); - private $prefix; - private $host; - private $condition; - private $requirements = array(); - private $options = array(); - private $schemes; - private $methods; - private $resources = array(); - private $controllerClass; /** From 8f0b956e5fbf9b644874ebef34fd9143c7520635 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 29 Sep 2015 21:54:43 -0400 Subject: [PATCH 25/34] moving into the component --- .../Routing/RouteCollectionBuilder.php | 2 +- .../Routing/Tests}/RouteCollectionBuilderTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/Symfony/{Bundle/FrameworkBundle => Component}/Routing/RouteCollectionBuilder.php (99%) rename src/Symfony/{Bundle/FrameworkBundle/Tests/Routing => Component/Routing/Tests}/RouteCollectionBuilderTest.php (98%) diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php similarity index 99% rename from src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php rename to src/Symfony/Component/Routing/RouteCollectionBuilder.php index 41f7ef0fe603d..96e104c07cb9d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\FrameworkBundle\Routing; +namespace Symfony\Component\Routing; use Symfony\Component\Config\Exception\FileLoaderLoadException; use Symfony\Component\Config\Loader\LoaderInterface; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php similarity index 98% rename from src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php rename to src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php index 12738fdf4e8c9..dfee42763318f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouteCollectionBuilderTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php @@ -9,11 +9,11 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\FrameworkBundle\Tests\Routing; +namespace Symfony\Component\Routing\Tests; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; -use Symfony\Bundle\FrameworkBundle\Routing\RouteCollectionBuilder; +use Symfony\Component\Routing\RouteCollectionBuilder; use Symfony\Component\Config\Resource\FileResource; class RouteCollectionBuilderTest extends \PHPUnit_Framework_TestCase @@ -46,7 +46,7 @@ public function testImport() $addedBuilder = $collectionBuilder->import('admin_routing.yml', '/admin', 'yaml'); // we should get back a RouteCollectionBuilder - $this->assertInstanceOf('Symfony\Bundle\FrameworkBundle\Routing\RouteCollectionBuilder', $addedBuilder); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollectionBuilder', $addedBuilder); // get the collection back so we can look at it $addedCollection = $addedBuilder->build(); From fbab6d437e382b4fa18dec7d6165948c6fe256d7 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 29 Sep 2015 22:09:45 -0400 Subject: [PATCH 26/34] Removing the ability to set a prefix on a builder: that only happens when you mount addBuilder was also renamed to mount --- .../Routing/RouteCollectionBuilder.php | 28 ++++--------------- .../Tests/RouteCollectionBuilderTest.php | 20 +++++++------ 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index 96e104c07cb9d..4f377133e9304 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -89,16 +89,13 @@ public function add($path, $controller, $name = null) } /** - * Returns a RouteCollectionBuilder that can be configured and then added with addBuilder(). - * - * @param string $prefix A prefix to apply to all routes added to this collection + * Returns a RouteCollectionBuilder that can be configured and then added with mount(). * * @return RouteCollectionBuilder */ - public function createBuilder($prefix = null) + public function createBuilder() { $builder = new self($this->loader); - $builder->setPrefix($prefix); return $builder; } @@ -108,8 +105,9 @@ public function createBuilder($prefix = null) * * @param RouteCollectionBuilder $builder */ - public function addBuilder(RouteCollectionBuilder $builder) + public function mount($prefix, RouteCollectionBuilder $builder) { + $builder->prefix = trim(trim($prefix), '/'); $this->routes[] = $builder; } @@ -124,7 +122,7 @@ public function addBuilder(RouteCollectionBuilder $builder) public function addRouteCollection(RouteCollection $collection, $prefix = null) { // create a builder from the RouteCollection - $builder = $this->createBuilder($prefix); + $builder = $this->createBuilder(); foreach ($collection->all() as $name => $route) { $builder->addRoute($route, $name); } @@ -133,7 +131,7 @@ public function addRouteCollection(RouteCollection $collection, $prefix = null) $builder->addResource($resource); } - $this->addBuilder($builder); + $this->mount($prefix, $builder); return $builder; } @@ -158,20 +156,6 @@ public function addRoute(Route $route, $name = null) return $this; } - /** - * Sets a prefix (e.g. /admin) to be used with all embedded routes. - * - * @param string $prefix - * - * @return $this - */ - public function setPrefix($prefix) - { - $this->prefix = trim(trim($prefix), '/'); - - return $this; - } - /** * Sets the host on all embedded routes (unless already set). * diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php index dfee42763318f..245c0adf4f27a 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php @@ -239,10 +239,13 @@ public function testFlushSetsDetailsOnChildrenRoutes() public function testFlushPrefixesPaths($collectionPrefix, $routePath, $expectedPath) { $routes = new RouteCollectionBuilder(); - $routes->setPrefix($collectionPrefix); $routes->add($routePath, 'someController', 'test_route'); - $collection = $routes->build(); + + $outerRoutes = new RouteCollectionBuilder(); + $outerRoutes->mount($collectionPrefix, $routes); + + $collection = $outerRoutes->build(); $this->assertEquals($expectedPath, $collection->get('test_route')->getPath()); } @@ -255,7 +258,6 @@ public function providePrefixTests() // normal prefix - does not matter if it's a wildcard $tests[] = array('/{admin}', '/foo', '/{admin}/foo'); // shows that a prefix will always be given the starting slash - $tests = array(); $tests[] = array('0', '/foo', '/0/foo'); // spaces are ok, and double slahses at the end are cleaned @@ -271,24 +273,24 @@ public function testFlushSetsPrefixedWithMultipleLevels() $routes->add('homepage', 'MainController::homepageAction', 'homepage'); - $adminRoutes = $routes->createBuilder('/admin'); + $adminRoutes = $routes->createBuilder(); $adminRoutes->add('/dashboard', 'AdminController::dashboardAction', 'admin_dashboard'); // embedded collection under /admin - $adminBlogRoutes = $routes->createBuilder('/blog'); + $adminBlogRoutes = $routes->createBuilder(); $adminBlogRoutes->add('/new', 'BlogController::newAction', 'admin_blog_new'); // mount into admin, but before the parent collection has been mounted - $adminRoutes->addBuilder($adminBlogRoutes); + $adminRoutes->mount('/blog', $adminBlogRoutes); // now mount the /admin routes, above should all still be /blog/admin - $routes->addBuilder($adminRoutes); + $routes->mount('/admin', $adminRoutes); // add a route after mounting $adminRoutes->add('/users', 'AdminController::userAction', 'admin_users'); // add another sub-collection after the mount - $otherAdminRoutes = $routes->createBuilder('/stats'); + $otherAdminRoutes = $routes->createBuilder(); $otherAdminRoutes->add('/sales', 'StatsController::indexAction', 'admin_stats_sales'); - $adminRoutes->addBuilder($otherAdminRoutes); + $adminRoutes->mount('/stats', $otherAdminRoutes); // add a normal collection and see that it is also prefixed $importedCollection = new RouteCollection(); From 012cb92ad792635fb35b160e0870a7df76428b12 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 29 Sep 2015 22:19:45 -0400 Subject: [PATCH 27/34] Removing the prefix from import, and making the user actually put that into the parent builder --- .../Routing/RouteCollectionBuilder.php | 48 ++++++---------- .../Tests/RouteCollectionBuilderTest.php | 56 +++++++------------ 2 files changed, 36 insertions(+), 68 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index 4f377133e9304..df75c344ecd95 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -52,22 +52,31 @@ public function __construct(LoaderInterface $loader = null) } /** - * Import an external routing resource, like a file. + * Import an external routing resource and returns the RouteCollectionBuilder * - * Returns a RouteCollectionBuilder so you can continue to tweak options on the routes. + * $routes->mount('/blog', $routes->import('blog.yml')); * * @param mixed $resource - * @param string $prefix * @param string $type * * @return RouteCollectionBuilder */ - public function import($resource, $prefix = null, $type = null) + public function import($resource, $type = null) { - /** @var RouteCollection $subCollection */ - $subCollection = $this->resolve($resource, $type)->load($resource, $type); + /** @var RouteCollection $collection */ + $collection = $this->resolve($resource, $type)->load($resource, $type); - return $this->addRouteCollection($subCollection, $prefix); + // create a builder from the RouteCollection + $builder = $this->createBuilder(); + foreach ($collection->all() as $name => $route) { + $builder->addRoute($route, $name); + } + + foreach ($collection->getResources() as $resource) { + $builder->addResource($resource); + } + + return $builder; } /** @@ -111,31 +120,6 @@ public function mount($prefix, RouteCollectionBuilder $builder) $this->routes[] = $builder; } - /** - * Adds a RouteCollection directly and returns those routes in a RouteCollectionBuilder. - * - * @param RouteCollection $collection - * @param string|null $prefix - * - * @return $this - */ - public function addRouteCollection(RouteCollection $collection, $prefix = null) - { - // create a builder from the RouteCollection - $builder = $this->createBuilder(); - foreach ($collection->all() as $name => $route) { - $builder->addRoute($route, $name); - } - - foreach ($collection->getResources() as $resource) { - $builder->addResource($resource); - } - - $this->mount($prefix, $builder); - - return $builder; - } - /** * Adds a Route object to the builder. * diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php index 245c0adf4f27a..07da135ea7e56 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php @@ -27,8 +27,9 @@ public function testImport() ->with('admin_routing.yml', 'yaml') ->will($this->returnValue($resolvedLoader)); + $originalRoute = new Route('/foo/path'); $expectedCollection = new RouteCollection(); - $expectedCollection->add('one_test_route', new Route('/foo/path')); + $expectedCollection->add('one_test_route', $originalRoute); $resolvedLoader ->expects($this->once()) @@ -41,18 +42,17 @@ public function testImport() ->method('getResolver') ->will($this->returnValue($resolver)); - // import the file! (with a prefix) - $collectionBuilder = new RouteCollectionBuilder($loader); - $addedBuilder = $collectionBuilder->import('admin_routing.yml', '/admin', 'yaml'); + // import the file! + $routes = new RouteCollectionBuilder($loader); + $importedRoutes = $routes->import('admin_routing.yml', 'yaml'); // we should get back a RouteCollectionBuilder - $this->assertInstanceOf('Symfony\Component\Routing\RouteCollectionBuilder', $addedBuilder); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollectionBuilder', $importedRoutes); // get the collection back so we can look at it - $addedCollection = $addedBuilder->build(); + $addedCollection = $importedRoutes->build(); $route = $addedCollection->get('one_test_route'); - $this->assertNotNull($route); - $this->assertEquals('/admin/foo/path', $route->getPath(), 'The prefix should be applied'); + $this->assertSame($originalRoute, $route); } /** @@ -79,14 +79,6 @@ public function testAdd() public function testFlushOrdering() { - $loadedCollection1 = new RouteCollection(); - $loadedCollection1->add('first_collection_route1', new Route('/collection/first/blog1')); - $loadedCollection1->add('first_collection_route2', new Route('/collection/first/blog2')); - - $loadedCollection2 = new RouteCollection(); - $loadedCollection2->add('second_collection_route1', new Route('/collection/second/product1')); - $loadedCollection2->add('second_collection_route2', new Route('/collection/second/product2')); - $importedCollection = new RouteCollection(); $importedCollection->add('imported_route1', new Route('/imported/foo1')); $importedCollection->add('imported_route2', new Route('/imported/foo2')); @@ -101,39 +93,31 @@ public function testFlushOrdering() ->method('load') ->will($this->returnValue($importedCollection)); - $collectionBuilder = new RouteCollectionBuilder($loader); + $routes = new RouteCollectionBuilder($loader); // 1) Add a route - $collectionBuilder->add('/checkout', 'AppBundle:Order:checkout', 'checkout_route'); - // 2) Add a collection directly - $collectionBuilder->addRouteCollection($loadedCollection1); - // 3) Import from a file - $collectionBuilder->import('admin_routing.yml'); + $routes->add('/checkout', 'AppBundle:Order:checkout', 'checkout_route'); + // 2) Import from a file + $routes->mount('/', $routes->import('admin_routing.yml')); + // 3) Add another route + $routes->add('/', 'AppBundle:Default:homepage', 'homepage'); // 4) Add another route - $collectionBuilder->add('/', 'AppBundle:Default:homepage', 'homepage'); - // 5) Add another collection - $collectionBuilder->addRouteCollection($loadedCollection2); - // 6) Add another route - $collectionBuilder->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); + $routes->add('/admin', 'AppBundle:Admin:dashboard', 'admin_dashboard'); // set a default value - $collectionBuilder->setDefault('_locale', 'fr'); + $routes->setDefault('_locale', 'fr'); // set an extra resource - $collectionBuilder->addResource(new FileResource('foo_routing.xml')); + $routes->addResource(new FileResource('foo_routing.xml')); - $actualCollection = $collectionBuilder->build(); + $actualCollection = $routes->build(); - $this->assertCount(9, $actualCollection); + $this->assertCount(5, $actualCollection); $actualRouteNames = array_keys($actualCollection->all()); $this->assertEquals(array( 'checkout_route', - 'first_collection_route1', - 'first_collection_route2', 'imported_route1', 'imported_route2', 'homepage', - 'second_collection_route1', - 'second_collection_route2', 'admin_dashboard', ), $actualRouteNames); @@ -304,7 +288,7 @@ public function testFlushSetsPrefixedWithMultipleLevels() ->method('load') ->will($this->returnValue($importedCollection)); // import this from the /admin route builder - $adminRoutes->import('admin.yml', '/imported'); + $adminRoutes->mount('/imported', $adminRoutes->import('admin.yml')); $collection = $routes->build(); $this->assertEquals('/admin/dashboard', $collection->get('admin_dashboard')->getPath(), 'Routes before mounting have the prefix'); From 33255dd76264e17eeecf9102fad5688b59d1bda6 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 29 Sep 2015 22:31:37 -0400 Subject: [PATCH 28/34] Fixing a bug with route name collission because the prefix wasn't accounted for --- .../Routing/RouteCollectionBuilder.php | 71 ++----------------- .../Tests/RouteCollectionBuilderTest.php | 51 +++++-------- 2 files changed, 22 insertions(+), 100 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index df75c344ecd95..211042391ee8c 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -41,7 +41,6 @@ class RouteCollectionBuilder private $schemes; private $methods; private $resources = array(); - private $controllerClass; /** * @param LoaderInterface $loader @@ -83,7 +82,7 @@ public function import($resource, $type = null) * Adds a route and returns it for future modification. * * @param string $path The route path - * @param string $controller The route controller string + * @param string $controller The route's controller * @param string|null $name The name to give this route * * @return Route @@ -256,28 +255,6 @@ public function addResource(ResourceInterface $resource) return $this; } - /** - * Set a controller class that all added embedded routes should use. - * - * With this, the controller for embedded routes can just be a method name. - * If an embedded route has a full controller (e.g. class::methodName), the - * controllerClass won't be applied to that route. - * - * @param string $controllerClass - * - * @return $this - */ - public function setControllerClass($controllerClass) - { - if (!class_exists($controllerClass)) { - throw new \InvalidArgumentException(sprintf('The controller class "%s" does not exist.', $controllerClass)); - } - - $this->controllerClass = $controllerClass; - - return $this; - } - /** * Creates the final ArrayCollection, returns it, and clears everything. * @@ -289,13 +266,6 @@ public function build() foreach ($this->routes as $name => $route) { if ($route instanceof Route) { - // auto-generate the route name if it's been marked - if ('_unnamed_route_' === substr($name, 0, 15)) { - $name = $this->generateRouteName($route); - } - - $this->ensureRouteController($route); - $route->setDefaults(array_merge($this->defaults, $route->getDefaults())); $route->setOptions(array_merge($this->options, $route->getOptions())); @@ -326,10 +296,14 @@ public function build() $route->setMethods($this->methods); } + // auto-generate the route name if it's been marked + if ('_unnamed_route_' === substr($name, 0, 15)) { + $name = $this->generateRouteName($route); + } + $routeCollection->add($name, $route); } else { /* @var self $route */ - $subCollection = $route->build(); $subCollection->addPrefix($this->prefix); @@ -344,39 +318,6 @@ public function build() return $routeCollection; } - /** - * Attempts to safely prefix controllers with the controller class if necessary. - * - * @param Route $route - */ - private function ensureRouteController(Route $route) - { - // only do work if there is a controller class set - if (!$this->controllerClass) { - return; - } - - $controller = $route->getDefault('_controller'); - - // only apply controller class to a (non-empty) string - if (!is_string($controller) || !$controller) { - return; - } - - // is the controller already a callable function/class? - if (method_exists($controller, '__invoke') || function_exists($controller)) { - return; - } - - // is this already a controller format (a:b:c, or a:b, or a::b)? - if (false !== strpos($controller, ':')) { - return; - } - - $controller = sprintf('%s::%s', $this->controllerClass, $controller); - $route->setDefault('_controller', $controller); - } - /** * Generates a route name based on details of this route. * diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php index 07da135ea7e56..fb50c19bdc176 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php @@ -298,46 +298,27 @@ public function testFlushSetsPrefixedWithMultipleLevels() $this->assertEquals('/admin/imported/foo', $collection->get('imported_route')->getPath(), 'Normal RouteCollections are also prefixed properly'); } - /** - * @dataProvider provideControllerClassTests - */ - public function testSetControllerClass($routeController, $controllerClass, $expectedFinalController) + public function testAutomaticRouteNamesDoNotConflict() { $routes = new RouteCollectionBuilder(); - $routes->setControllerClass('Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController'); - - $routes->add('/', $routeController, 'test_route'); - $collection = $routes->build(); - $this->assertEquals($expectedFinalController, $collection->get('test_route')->getDefault('_controller')); - } - public function provideControllerClassTests() - { - $controllerClass = 'Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController'; - - $tests = array(); - $tests[] = array('withControllerAction', $controllerClass, $controllerClass.'::'.'withControllerAction'); - - // the controllerClass should not be used in many cases - $tests[] = array('', $controllerClass, ''); - $tests[] = array('Some\Class\FooController::fooAction', $controllerClass, 'Some\Class\FooController::fooAction'); - $tests[] = array('AppBundle:Default:index', $controllerClass, 'AppBundle:Default:index'); - $tests[] = array('foo_controller:fooAction', $controllerClass, 'foo_controller:fooAction'); - $tests[] = array(array('Acme\FooController', 'fooAction'), $controllerClass, array('Acme\FooController', 'fooAction')); - - $closure = function() {}; - $tests[] = array($closure, $controllerClass, $closure); + $adminRoutes = $routes->createBuilder(); + // route 1 + $adminRoutes->add('/dashboard', ''); - return $tests; - } + $accountRoutes = $routes->createBuilder(); + // route 2 + $accountRoutes->add('/dashboard', '') + ->setMethods(array('GET')); + // route 3 + $accountRoutes->add('/dashboard', '') + ->setMethods(array('POST')); - /** - * @expectedException \InvalidArgumentException - */ - public function testExceptiononBadControllerClass() - { - $routes = new RouteCollectionBuilder(); + $routes->mount('/admin', $adminRoutes); + $routes->mount('/account', $accountRoutes); - $routes->setControllerClass('Acme\FakeController'); + $collection = $routes->build(); + // there are 2 routes (i.e. with non-conflicting names) + $this->assertCount(3, $collection->all()); } } From 356b1147d5eb6683a1b98ef27dc799cc9389adb4 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 29 Sep 2015 22:42:19 -0400 Subject: [PATCH 29/34] Refactoring into private method --- .../Component/Routing/RouteCollectionBuilder.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index 211042391ee8c..6f3ed4d6a4a84 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -63,7 +63,7 @@ public function __construct(LoaderInterface $loader = null) public function import($resource, $type = null) { /** @var RouteCollection $collection */ - $collection = $this->resolve($resource, $type)->load($resource, $type); + $collection = $this->load($resource, $type); // create a builder from the RouteCollection $builder = $this->createBuilder(); @@ -338,23 +338,23 @@ private function generateRouteName(Route $route) } /** - * Finds a loader able to load an imported resource. + * Finds a loader able to load an imported resource and loads it. * * @param mixed $resource A resource * @param string|null $type The resource type or null if unknown * - * @return LoaderInterface A LoaderInterface instance + * @return RouteCollection * * @throws FileLoaderLoadException If no loader is found */ - private function resolve($resource, $type = null) + private function load($resource, $type = null) { if (null === $this->loader) { throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.'); } if ($this->loader->supports($resource, $type)) { - return $this->loader; + return $this->loader->load($resource, $type); } $loader = null === $this->loader->getResolver() ? false : $this->loader->getResolver()->resolve($resource, $type); @@ -363,6 +363,6 @@ private function resolve($resource, $type = null) throw new FileLoaderLoadException($resource); } - return $loader; + return $loader->load($resource, $type); } } From 7cb89967762e0c9fb855461c867968e3de639974 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 29 Sep 2015 22:50:45 -0400 Subject: [PATCH 30/34] fabbot --- src/Symfony/Component/Routing/RouteCollectionBuilder.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index 6f3ed4d6a4a84..f02c385e7e0de 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -14,8 +14,6 @@ use Symfony\Component\Config\Exception\FileLoaderLoadException; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Resource\ResourceInterface; -use Symfony\Component\Routing\RouteCollection; -use Symfony\Component\Routing\Route; /** * Helps add and import routes into a RouteCollection. @@ -51,7 +49,7 @@ public function __construct(LoaderInterface $loader = null) } /** - * Import an external routing resource and returns the RouteCollectionBuilder + * Import an external routing resource and returns the RouteCollectionBuilder. * * $routes->mount('/blog', $routes->import('blog.yml')); * From 573a7f149aa2f214c830fb02a313d5f726ed2946 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 30 Sep 2015 18:55:44 -0400 Subject: [PATCH 31/34] Small tweaks suggested by fabpot, including removal of addResource(), as it probably should not be needed by the end user --- .../Routing/RouteCollectionBuilder.php | 23 +------------------ .../Tests/RouteCollectionBuilderTest.php | 6 ----- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index f02c385e7e0de..5365f997ec480 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -23,8 +23,6 @@ class RouteCollectionBuilder { /** - * A mixture of different objects that hold routes. - * * @var Route[]|RouteCollectionBuilder[] */ private $routes = array(); @@ -38,7 +36,6 @@ class RouteCollectionBuilder private $options = array(); private $schemes; private $methods; - private $resources = array(); /** * @param LoaderInterface $loader @@ -101,9 +98,7 @@ public function add($path, $controller, $name = null) */ public function createBuilder() { - $builder = new self($this->loader); - - return $builder; + return new self($this->loader); } /** @@ -241,18 +236,6 @@ public function setMethods($methods) return $this; } - /** - * Adds a resource for this collection. - * - * @return $this - */ - public function addResource(ResourceInterface $resource) - { - $this->resources[] = $resource; - - return $this; - } - /** * Creates the final ArrayCollection, returns it, and clears everything. * @@ -309,10 +292,6 @@ public function build() } } - foreach ($this->resources as $resource) { - $routeCollection->addResource($resource); - } - return $routeCollection; } diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php index fb50c19bdc176..c84b39d573bea 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php @@ -106,8 +106,6 @@ public function testFlushOrdering() // set a default value $routes->setDefault('_locale', 'fr'); - // set an extra resource - $routes->addResource(new FileResource('foo_routing.xml')); $actualCollection = $routes->build(); @@ -126,10 +124,6 @@ public function testFlushOrdering() $defaults = $checkoutRoute->getDefaults(); $this->assertArrayHasKey('_locale', $defaults); $this->assertEquals('fr', $defaults['_locale']); - - // technically, we should expect 2 here (admin_routing.yml + foo_routing.xml) - // but, admin_routing.yml would be added to the collection via the loader, which is mocked - $this->assertCount(1, $actualCollection->getResources(), 'The added resource is included'); } public function testFlushSetsRouteNames() From 83f319472539a21758d0c8e3d9b75905bd14c014 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 30 Sep 2015 18:56:18 -0400 Subject: [PATCH 32/34] stretching out logic into multiple lines: there's some discussion about how to write this --- src/Symfony/Component/Routing/RouteCollectionBuilder.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index 5365f997ec480..b56d5d8ebca68 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -334,9 +334,11 @@ private function load($resource, $type = null) return $this->loader->load($resource, $type); } - $loader = null === $this->loader->getResolver() ? false : $this->loader->getResolver()->resolve($resource, $type); + if (null === $resolver = $this->loader->getResolver()) { + throw new FileLoaderLoadException($resource); + } - if (false === $loader) { + if (false === $loader = $resolver->resolve($resource, $type)) { throw new FileLoaderLoadException($resource); } From 26d656b314df72d77ed4c27b94b3c0235856e24f Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 30 Sep 2015 19:00:42 -0400 Subject: [PATCH 33/34] Fabbot --- src/Symfony/Component/Routing/RouteCollectionBuilder.php | 1 - .../Component/Routing/Tests/RouteCollectionBuilderTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index b56d5d8ebca68..7fa7054a6d60c 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -13,7 +13,6 @@ use Symfony\Component\Config\Exception\FileLoaderLoadException; use Symfony\Component\Config\Loader\LoaderInterface; -use Symfony\Component\Config\Resource\ResourceInterface; /** * Helps add and import routes into a RouteCollection. diff --git a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php index c84b39d573bea..1ac0d77d13bdb 100644 --- a/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php +++ b/src/Symfony/Component/Routing/Tests/RouteCollectionBuilderTest.php @@ -14,7 +14,6 @@ use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollectionBuilder; -use Symfony\Component\Config\Resource\FileResource; class RouteCollectionBuilderTest extends \PHPUnit_Framework_TestCase { From 42e73a27383ac11376fd0d3d0af9ea337546b4ec Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 1 Oct 2015 15:24:41 -0400 Subject: [PATCH 34/34] Adding throws --- src/Symfony/Component/Routing/RouteCollectionBuilder.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Component/Routing/RouteCollectionBuilder.php b/src/Symfony/Component/Routing/RouteCollectionBuilder.php index 7fa7054a6d60c..884a9bc1b490a 100644 --- a/src/Symfony/Component/Routing/RouteCollectionBuilder.php +++ b/src/Symfony/Component/Routing/RouteCollectionBuilder.php @@ -53,6 +53,8 @@ public function __construct(LoaderInterface $loader = null) * @param string $type * * @return RouteCollectionBuilder + * + * @throws FileLoaderLoadException */ public function import($resource, $type = null) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy