Browse Source

Add prefix support to resources.

This allows resources and more importantly nested resources to have
routing prefixes applied to them. This allows nested resource
controllers to be mapped onto namespaces/routing prefixes. I thought
this was a more robust/opinionated solution that allowing arbitrary
class names.

I've had to relax the duplicate key checking between scopes and their
routes to allow differences in prefixes to be created.

Refs #8063
Mark Story 10 years ago
parent
commit
5b7a7889e1
3 changed files with 51 additions and 3 deletions
  1. 15 1
      src/Routing/RouteBuilder.php
  2. 1 0
      src/Routing/Router.php
  3. 35 2
      tests/TestCase/Routing/RouteBuilderTest.php

+ 15 - 1
src/Routing/RouteBuilder.php

@@ -287,6 +287,8 @@ class RouteBuilder
      * - 'actions' - Override the method names used for connecting actions.
      * - 'map' - Additional resource routes that should be connected. If you define 'only' and 'map',
      *   make sure that your mapped methods are also in the 'only' list.
+     * - 'prefix' - Define a routing prefix for the resource controller. If the current scope
+     *   defines a prefix, this prefix will be appended to it.
      *
      * @param string $name A controller name to connect resource routes for.
      * @param array|callable $options Options to use when generating REST routes, or a callback.
@@ -307,6 +309,7 @@ class RouteBuilder
             'only' => [],
             'actions' => [],
             'map' => [],
+            'prefix' => null,
         ];
 
         foreach ($options['map'] as $k => $mapped) {
@@ -327,6 +330,14 @@ class RouteBuilder
             $only = array_keys($resourceMap);
         }
 
+        $prefix = '';
+        if ($options['prefix']) {
+            $prefix = $options['prefix'];
+        }
+        if (isset($this->_params['prefix']) && $prefix) {
+            $prefix = $this->_params['prefix'] . '/' . $prefix;
+        }
+
         foreach ($resourceMap as $method => $params) {
             if (!in_array($method, $only, true)) {
                 continue;
@@ -343,6 +354,9 @@ class RouteBuilder
                 'action' => $action,
                 '_method' => $params['method'],
             ];
+            if ($prefix) {
+                $params['prefix'] = $prefix;
+            }
             $routeOptions = $connectOptions + [
                 'id' => $options['id'],
                 'pass' => ['id'],
@@ -482,7 +496,7 @@ class RouteBuilder
             $route = $route === '/' ? $route : rtrim($route, '/');
 
             foreach ($this->_params as $param => $val) {
-                if (isset($defaults[$param]) && $defaults[$param] !== $val) {
+                if (isset($defaults[$param]) && $param !== 'prefix' && $defaults[$param] !== $val) {
                     $msg = 'You cannot define routes that conflict with the scope. ' .
                         'Scope had %s = %s, while route had %s = %s';
                     throw new BadMethodCallException(sprintf(

+ 1 - 0
src/Routing/Router.php

@@ -289,6 +289,7 @@ class Router
             $prefix = $pluginUrl = false;
             if (!empty($options['prefix'])) {
                 $prefix = $options['prefix'];
+                unset($options['prefix']);
             }
             if ($plugin) {
                 $pluginUrl = Inflector::underscore($plugin);

+ 35 - 2
tests/TestCase/Routing/RouteBuilderTest.php

@@ -252,8 +252,8 @@ class RouteBuilderTest extends TestCase
      */
     public function testConnectConflictingParameters()
     {
-        $routes = new RouteBuilder($this->collection, '/admin', ['prefix' => 'admin']);
-        $routes->connect('/', ['prefix' => 'manager', 'controller' => 'Dashboard', 'action' => 'view']);
+        $routes = new RouteBuilder($this->collection, '/admin', ['plugin' => 'TestPlugin']);
+        $routes->connect('/', ['plugin' => 'TestPlugin2', 'controller' => 'Dashboard', 'action' => 'view']);
     }
 
     /**
@@ -367,6 +367,39 @@ class RouteBuilderTest extends TestCase
     }
 
     /**
+     * Test connecting resources with a prefix
+     *
+     * @return void
+     */
+    public function testResourcesPrefix()
+    {
+        $routes = new RouteBuilder($this->collection, '/api');
+        $routes->resources('Articles', ['prefix' => 'rest']);
+        $all = $this->collection->routes();
+        $this->assertEquals('rest', $all[0]->defaults['prefix']);
+    }
+
+    /**
+     * Test that resource prefixes work within a prefixed scope.
+     *
+     * @return void
+     */
+    public function testResourcesNestedPrefix()
+    {
+        $routes = new RouteBuilder($this->collection, '/api', ['prefix' => 'api']);
+        $routes->resources('Articles', ['prefix' => 'rest']);
+
+        $all = $this->collection->routes();
+        $this->assertCount(5, $all);
+
+        $this->assertEquals('/api/articles', $all[0]->template);
+        foreach ($all as $route) {
+            $this->assertEquals('api/rest', $route->defaults['prefix']);
+            $this->assertEquals('Articles', $route->defaults['controller']);
+        }
+    }
+
+    /**
      * Test connecting resources with the inflection option
      *
      * @return void