From fbf10311a5a67cd9f5c4270cd369ac5829493071 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Fri, 20 Feb 2015 10:29:20 -0500 Subject: [PATCH] Implement a map option for resources(). Having a map option allows users to map additional resource methods into their routes. This is handy when apps have custom actions they want mapped onto resources. Refs #5929 --- src/Routing/RouteBuilder.php | 35 ++++++++++++++++---- src/Routing/Router.php | 5 +++ tests/TestCase/Routing/RouteBuilderTest.php | 36 +++++++++++++++++++++ 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/Routing/RouteBuilder.php b/src/Routing/RouteBuilder.php index c50d18750b4..7f0c7f9f758 100644 --- a/src/Routing/RouteBuilder.php +++ b/src/Routing/RouteBuilder.php @@ -216,6 +216,17 @@ public function params() * ``` * * The above would generate both resource routes for `/articles`, and `/articles/:article_id/comments`. + * You can use the `map` option to connect additional resource methods: + * + * ``` + * $routes->resources('Articles', [ + * 'map' => ['deleteAll' => ['action' => 'deleteAll', 'method' => 'DELETE']] + * ]); + * ``` + * + * In addition to the default routes, this would also connect a route for `/articles/delete_all`. + * By default the path segment will match the key name. You can use the 'path' key inside the resource + * definition to customize the path name. * * ### Options: * @@ -223,6 +234,8 @@ public function params() * integer values and UUIDs. * - 'only' - Only connect the specific list of actions. * - '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. * * @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. @@ -239,21 +252,31 @@ public function resources($name, $options = [], $callback = null) $options += [ 'connectOptions' => [], 'id' => static::ID . '|' . static::UUID, - 'only' => ['index', 'update', 'create', 'view', 'delete'], + 'only' => [], 'actions' => [], + 'map' => [], ]; - $options['only'] = (array)$options['only']; - $connectOptions = $options['connectOptions']; - $urlName = Inflector::underscore($name); + foreach ($options['map'] as $k => $mapped) { + $options['map'][$k] += ['method' => 'GET', 'path' => $k, 'action' => '']; + } $ext = null; if (!empty($options['_ext'])) { $ext = $options['_ext']; } - foreach (static::$_resourceMap as $method => $params) { - if (!in_array($method, $options['only'], true)) { + $connectOptions = $options['connectOptions']; + $urlName = Inflector::underscore($name); + $resourceMap = array_merge(static::$_resourceMap, $options['map']); + + $only = (array)$options['only']; + if (empty($only)) { + $only = array_keys($resourceMap); + } + + foreach ($resourceMap as $method => $params) { + if (!in_array($method, $only, true)) { continue; } diff --git a/src/Routing/Router.php b/src/Routing/Router.php index cd7f0ee3e23..f8d7ed2fd60 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -269,9 +269,14 @@ public static function redirect($route, $url, $options = []) * integer values and UUIDs. * - 'prefix' - Routing prefix to use for the generated routes. Defaults to ''. * Using this option will create prefixed routes, similar to using Routing.prefixes. + * - 'only' - Only connect the specific list of actions. + * - '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. * * @param string|array $controller A controller name or array of controller names (i.e. "Posts" or "ListItems") * @param array $options Options to use when generating REST routes + * @see \Cake\Routing\RouteBuilder::resources() * @return void */ public static function mapResources($controller, $options = []) diff --git a/tests/TestCase/Routing/RouteBuilderTest.php b/tests/TestCase/Routing/RouteBuilderTest.php index fd44a861442..381d5c8fec7 100644 --- a/tests/TestCase/Routing/RouteBuilderTest.php +++ b/tests/TestCase/Routing/RouteBuilderTest.php @@ -344,6 +344,42 @@ public function testResources() $this->assertEquals('Articles', $all[0]->defaults['controller']); } + /** + * Test connecting resources with additional mappings + * + * @return void + */ + public function testResourcesMappings() + { + $routes = new RouteBuilder($this->collection, '/api', ['prefix' => 'api']); + $routes->resources('Articles', [ + '_ext' => 'json', + 'map' => [ + 'delete_all' => ['action' => 'deleteAll', 'method' => 'DELETE'], + 'update_many' => ['action' => 'updateAll', 'method' => 'DELETE', 'path' => '/updateAll'], + ] + ]); + + $all = $this->collection->routes(); + $this->assertCount(7, $all); + + $this->assertEquals('/api/articles/delete_all', $all[5]->template, 'Path defaults to key name.'); + $this->assertEquals( + ['controller', 'action', '_method', 'prefix', 'plugin'], + array_keys($all[5]->defaults) + ); + $this->assertEquals('Articles', $all[5]->defaults['controller']); + $this->assertEquals('deleteAll', $all[5]->defaults['action']); + + $this->assertEquals('/api/articles/updateAll', $all[6]->template, 'Explicit path option'); + $this->assertEquals( + ['controller', 'action', '_method', 'prefix', 'plugin'], + array_keys($all[6]->defaults) + ); + $this->assertEquals('Articles', $all[6]->defaults['controller']); + $this->assertEquals('updateAll', $all[6]->defaults['action']); + } + /** * Test connecting resources. *