| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082 |
- <?php
- /**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- * @link https://cakephp.org CakePHP(tm) Project
- * @since 3.0.0
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Routing;
- use BadMethodCallException;
- use Cake\Core\App;
- use Cake\Core\Exception\MissingPluginException;
- use Cake\Core\Plugin;
- use Cake\Routing\Route\Route;
- use Cake\Utility\Inflector;
- use InvalidArgumentException;
- use RuntimeException;
- /**
- * Provides features for building routes inside scopes.
- *
- * Gives an easy to use way to build routes and append them
- * into a route collection.
- */
- class RouteBuilder
- {
- /**
- * Regular expression for auto increment IDs
- *
- * @var string
- */
- const ID = '[0-9]+';
- /**
- * Regular expression for UUIDs
- *
- * @var string
- */
- const UUID = '[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}';
- /**
- * Default HTTP request method => controller action map.
- *
- * @var array
- */
- protected static $_resourceMap = [
- 'index' => ['action' => 'index', 'method' => 'GET', 'path' => ''],
- 'create' => ['action' => 'add', 'method' => 'POST', 'path' => ''],
- 'view' => ['action' => 'view', 'method' => 'GET', 'path' => ':id'],
- 'update' => ['action' => 'edit', 'method' => ['PUT', 'PATCH'], 'path' => ':id'],
- 'delete' => ['action' => 'delete', 'method' => 'DELETE', 'path' => ':id'],
- ];
- /**
- * Default route class to use if none is provided in connect() options.
- *
- * @var string
- */
- protected $_routeClass = 'Cake\Routing\Route\Route';
- /**
- * The extensions that should be set into the routes connected.
- *
- * @var string[]
- */
- protected $_extensions = [];
- /**
- * The path prefix scope that this collection uses.
- *
- * @var string
- */
- protected $_path;
- /**
- * The scope parameters if there are any.
- *
- * @var array
- */
- protected $_params;
- /**
- * Name prefix for connected routes.
- *
- * @var string
- */
- protected $_namePrefix = '';
- /**
- * The route collection routes should be added to.
- *
- * @var \Cake\Routing\RouteCollection
- */
- protected $_collection;
- /**
- * The list of middleware that routes in this builder get
- * added during construction.
- *
- * @var string[]
- */
- protected $middleware = [];
- /**
- * Constructor
- *
- * ### Options
- *
- * - `routeClass` - The default route class to use when adding routes.
- * - `extensions` - The extensions to connect when adding routes.
- * - `namePrefix` - The prefix to prepend to all route names.
- * - `middleware` - The names of the middleware routes should have applied.
- *
- * @param \Cake\Routing\RouteCollection $collection The route collection to append routes into.
- * @param string $path The path prefix the scope is for.
- * @param array $params The scope's routing parameters.
- * @param array $options Options list.
- */
- public function __construct(RouteCollection $collection, $path, array $params = [], array $options = [])
- {
- $this->_collection = $collection;
- $this->_path = $path;
- $this->_params = $params;
- if (isset($options['routeClass'])) {
- $this->_routeClass = $options['routeClass'];
- }
- if (isset($options['extensions'])) {
- $this->_extensions = $options['extensions'];
- }
- if (isset($options['namePrefix'])) {
- $this->_namePrefix = $options['namePrefix'];
- }
- if (isset($options['middleware'])) {
- $this->middleware = (array)$options['middleware'];
- }
- }
- /**
- * Get or set default route class.
- *
- * @deprecated 3.5.0 Use getRouteClass/setRouteClass instead.
- * @param string|null $routeClass Class name.
- * @return string|null
- */
- public function routeClass($routeClass = null)
- {
- deprecationWarning(
- 'RouteBuilder::routeClass() is deprecated. ' .
- 'Use RouteBuilder::setRouteClass()/getRouteClass() instead.'
- );
- if ($routeClass === null) {
- return $this->getRouteClass();
- }
- $this->setRouteClass($routeClass);
- }
- /**
- * Set default route class.
- *
- * @param string $routeClass Class name.
- * @return $this
- */
- public function setRouteClass($routeClass)
- {
- $this->_routeClass = $routeClass;
- return $this;
- }
- /**
- * Get default route class.
- *
- * @return string
- */
- public function getRouteClass()
- {
- return $this->_routeClass;
- }
- /**
- * Get or set the extensions in this route builder's scope.
- *
- * Future routes connected in through this builder will have the connected
- * extensions applied. However, setting extensions does not modify existing routes.
- *
- * @deprecated 3.5.0 Use getExtensions/setExtensions instead.
- * @param string[]|string|null $extensions Either the extensions to use or null.
- * @return string[]|null
- */
- public function extensions($extensions = null)
- {
- deprecationWarning(
- 'RouteBuilder::extensions() is deprecated. ' .
- 'Use RouteBuilder::setExtensions()/getExtensions() instead.'
- );
- if ($extensions === null) {
- return $this->getExtensions();
- }
- $this->setExtensions($extensions);
- }
- /**
- * Set the extensions in this route builder's scope.
- *
- * Future routes connected in through this builder will have the connected
- * extensions applied. However, setting extensions does not modify existing routes.
- *
- * @param string|string[] $extensions The extensions to set.
- * @return $this
- */
- public function setExtensions($extensions)
- {
- $this->_extensions = (array)$extensions;
- return $this;
- }
- /**
- * Get the extensions in this route builder's scope.
- *
- * @return string[]
- */
- public function getExtensions()
- {
- return $this->_extensions;
- }
- /**
- * Add additional extensions to what is already in current scope
- *
- * @param string|string[] $extensions One or more extensions to add
- * @return void
- */
- public function addExtensions($extensions)
- {
- $extensions = array_merge($this->_extensions, (array)$extensions);
- $this->_extensions = array_unique($extensions);
- }
- /**
- * Get the path this scope is for.
- *
- * @return string
- */
- public function path()
- {
- $routeKey = strpos($this->_path, ':');
- if ($routeKey !== false) {
- return substr($this->_path, 0, $routeKey);
- }
- return $this->_path;
- }
- /**
- * Get the parameter names/values for this scope.
- *
- * @return array
- */
- public function params()
- {
- return $this->_params;
- }
- /**
- * Checks if there is already a route with a given name.
- *
- * @param string $name Name.
- * @return bool
- */
- public function nameExists($name)
- {
- return array_key_exists($name, $this->_collection->named());
- }
- /**
- * Get/set the name prefix for this scope.
- *
- * Modifying the name prefix will only change the prefix
- * used for routes connected after the prefix is changed.
- *
- * @param string|null $value Either the value to set or null.
- * @return string
- */
- public function namePrefix($value = null)
- {
- if ($value !== null) {
- $this->_namePrefix = $value;
- }
- return $this->_namePrefix;
- }
- /**
- * Generate REST resource routes for the given controller(s).
- *
- * A quick way to generate a default routes to a set of REST resources (controller(s)).
- *
- * ### Usage
- *
- * Connect resource routes for an app controller:
- *
- * ```
- * $routes->resources('Posts');
- * ```
- *
- * Connect resource routes for the Comments controller in the
- * Comments plugin:
- *
- * ```
- * Router::plugin('Comments', function ($routes) {
- * $routes->resources('Comments');
- * });
- * ```
- *
- * Plugins will create lower_case underscored resource routes. e.g
- * `/comments/comments`
- *
- * Connect resource routes for the Articles controller in the
- * Admin prefix:
- *
- * ```
- * Router::prefix('admin', function ($routes) {
- * $routes->resources('Articles');
- * });
- * ```
- *
- * Prefixes will create lower_case underscored resource routes. e.g
- * `/admin/posts`
- *
- * You can create nested resources by passing a callback in:
- *
- * ```
- * $routes->resources('Articles', function ($routes) {
- * $routes->resources('Comments');
- * });
- * ```
- *
- * 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.
- *
- * You can use the `inflect` option to change how path segments are generated:
- *
- * ```
- * $routes->resources('PaymentTypes', ['inflect' => 'dasherize']);
- * ```
- *
- * Will generate routes like `/payment-types` instead of `/payment_types`
- *
- * ### Options:
- *
- * - 'id' - The regular expression fragment to use when matching IDs. By default, matches
- * integer values and UUIDs.
- * - 'inflect' - Choose the inflection method used on the resource name. Defaults to 'underscore'.
- * - '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.
- * - 'prefix' - Define a routing prefix for the resource controller. If the current scope
- * defines a prefix, this prefix will be appended to it.
- * - 'connectOptions' - Custom options for connecting the routes.
- * - 'path' - Change the path so it doesn't match the resource name. E.g ArticlesController
- * is available at `/posts`
- *
- * @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.
- * @param callable|null $callback An optional callback to be executed in a nested scope. Nested
- * scopes inherit the existing path and 'id' parameter.
- * @return void
- */
- public function resources($name, $options = [], $callback = null)
- {
- if (is_callable($options)) {
- $callback = $options;
- $options = [];
- }
- $options += [
- 'connectOptions' => [],
- 'inflect' => 'underscore',
- 'id' => static::ID . '|' . static::UUID,
- 'only' => [],
- 'actions' => [],
- 'map' => [],
- 'prefix' => null,
- 'path' => null,
- ];
- foreach ($options['map'] as $k => $mapped) {
- $options['map'][$k] += ['method' => 'GET', 'path' => $k, 'action' => ''];
- }
- $ext = null;
- if (!empty($options['_ext'])) {
- $ext = $options['_ext'];
- }
- $connectOptions = $options['connectOptions'];
- if (empty($options['path'])) {
- $method = $options['inflect'];
- $options['path'] = Inflector::$method($name);
- }
- $resourceMap = array_merge(static::$_resourceMap, $options['map']);
- $only = (array)$options['only'];
- if (empty($only)) {
- $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;
- }
- $action = $params['action'];
- if (isset($options['actions'][$method])) {
- $action = $options['actions'][$method];
- }
- $url = '/' . implode('/', array_filter([$options['path'], $params['path']]));
- $params = [
- 'controller' => $name,
- 'action' => $action,
- '_method' => $params['method'],
- ];
- if ($prefix) {
- $params['prefix'] = $prefix;
- }
- $routeOptions = $connectOptions + [
- 'id' => $options['id'],
- 'pass' => ['id'],
- '_ext' => $ext,
- ];
- $this->connect($url, $params, $routeOptions);
- }
- if (is_callable($callback)) {
- $idName = Inflector::singularize(Inflector::underscore($name)) . '_id';
- $path = '/' . $options['path'] . '/:' . $idName;
- $this->scope($path, [], $callback);
- }
- }
- /**
- * Create a route that only responds to GET requests.
- *
- * @param string $template The URL template to use.
- * @param array $target An array describing the target route parameters. These parameters
- * should indicate the plugin, prefix, controller, and action that this route points to.
- * @param string $name The name of the route.
- * @return \Cake\Routing\Route\Route
- */
- public function get($template, $target, $name = null)
- {
- return $this->_methodRoute('GET', $template, $target, $name);
- }
- /**
- * Create a route that only responds to POST requests.
- *
- * @param string $template The URL template to use.
- * @param array $target An array describing the target route parameters. These parameters
- * should indicate the plugin, prefix, controller, and action that this route points to.
- * @param string $name The name of the route.
- * @return \Cake\Routing\Route\Route
- */
- public function post($template, $target, $name = null)
- {
- return $this->_methodRoute('POST', $template, $target, $name);
- }
- /**
- * Create a route that only responds to PUT requests.
- *
- * @param string $template The URL template to use.
- * @param array $target An array describing the target route parameters. These parameters
- * should indicate the plugin, prefix, controller, and action that this route points to.
- * @param string $name The name of the route.
- * @return \Cake\Routing\Route\Route
- */
- public function put($template, $target, $name = null)
- {
- return $this->_methodRoute('PUT', $template, $target, $name);
- }
- /**
- * Create a route that only responds to PATCH requests.
- *
- * @param string $template The URL template to use.
- * @param array $target An array describing the target route parameters. These parameters
- * should indicate the plugin, prefix, controller, and action that this route points to.
- * @param string $name The name of the route.
- * @return \Cake\Routing\Route\Route
- */
- public function patch($template, $target, $name = null)
- {
- return $this->_methodRoute('PATCH', $template, $target, $name);
- }
- /**
- * Create a route that only responds to DELETE requests.
- *
- * @param string $template The URL template to use.
- * @param array $target An array describing the target route parameters. These parameters
- * should indicate the plugin, prefix, controller, and action that this route points to.
- * @param string $name The name of the route.
- * @return \Cake\Routing\Route\Route
- */
- public function delete($template, $target, $name = null)
- {
- return $this->_methodRoute('DELETE', $template, $target, $name);
- }
- /**
- * Create a route that only responds to HEAD requests.
- *
- * @param string $template The URL template to use.
- * @param array $target An array describing the target route parameters. These parameters
- * should indicate the plugin, prefix, controller, and action that this route points to.
- * @param string $name The name of the route.
- * @return \Cake\Routing\Route\Route
- */
- public function head($template, $target, $name = null)
- {
- return $this->_methodRoute('HEAD', $template, $target, $name);
- }
- /**
- * Create a route that only responds to OPTIONS requests.
- *
- * @param string $template The URL template to use.
- * @param array $target An array describing the target route parameters. These parameters
- * should indicate the plugin, prefix, controller, and action that this route points to.
- * @param string $name The name of the route.
- * @return \Cake\Routing\Route\Route
- */
- public function options($template, $target, $name = null)
- {
- return $this->_methodRoute('OPTIONS', $template, $target, $name);
- }
- /**
- * Helper to create routes that only respond to a single HTTP method.
- *
- * @param string $method The HTTP method name to match.
- * @param string $template The URL template to use.
- * @param array $target An array describing the target route parameters. These parameters
- * should indicate the plugin, prefix, controller, and action that this route points to.
- * @param string $name The name of the route.
- * @return \Cake\Routing\Route\Route
- */
- protected function _methodRoute($method, $template, $target, $name)
- {
- if ($name !== null) {
- $name = $this->_namePrefix . $name;
- }
- $options = [
- '_name' => $name,
- '_ext' => $this->_extensions,
- '_middleware' => $this->middleware,
- 'routeClass' => $this->_routeClass,
- ];
- $target = $this->parseDefaults($target);
- $target['_method'] = $method;
- $route = $this->_makeRoute($template, $target, $options);
- $this->_collection->add($route, $options);
- return $route;
- }
- /**
- * Load routes from a plugin.
- *
- * The routes file will have a local variable named `$routes` made available which contains
- * the current RouteBuilder instance.
- *
- * @param string $name The plugin name
- * @param string $file The routes file to load. Defaults to `routes.php`. This parameter
- * is deprecated and will be removed in 4.0
- * @return void
- * @throws \Cake\Core\Exception\MissingPluginException When the plugin has not been loaded.
- * @throws \InvalidArgumentException When the plugin does not have a routes file.
- */
- public function loadPlugin($name, $file = 'routes.php')
- {
- $plugins = Plugin::getCollection();
- if (!$plugins->has($name)) {
- throw new MissingPluginException(['plugin' => $name]);
- }
- $plugin = $plugins->get($name);
- // @deprecated This block should be removed in 4.0
- if ($file !== 'routes.php') {
- deprecationWarning(
- 'Loading plugin routes now uses the routes() hook method on the plugin class. ' .
- 'Loading non-standard files will be removed in 4.0'
- );
- $path = $plugin->getConfigPath() . DIRECTORY_SEPARATOR . $file;
- if (!file_exists($path)) {
- throw new InvalidArgumentException(sprintf(
- 'Cannot load routes for the plugin named %s. The %s file does not exist.',
- $name,
- $path
- ));
- }
- $routes = $this;
- include $path;
- return;
- }
- $plugin->routes($this);
- // Disable the routes hook to prevent duplicate route issues.
- $plugin->disable('routes');
- }
- /**
- * Connects a new Route.
- *
- * Routes are a way of connecting request URLs to objects in your application.
- * At their core routes are a set or regular expressions that are used to
- * match requests to destinations.
- *
- * Examples:
- *
- * ```
- * $routes->connect('/:controller/:action/*');
- * ```
- *
- * The first parameter will be used as a controller name while the second is
- * used as the action name. The '/*' syntax makes this route greedy in that
- * it will match requests like `/posts/index` as well as requests
- * like `/posts/edit/1/foo/bar`.
- *
- * ```
- * $routes->connect('/home-page', ['controller' => 'Pages', 'action' => 'display', 'home']);
- * ```
- *
- * The above shows the use of route parameter defaults. And providing routing
- * parameters for a static route.
- *
- * ```
- * $routes->connect(
- * '/:lang/:controller/:action/:id',
- * [],
- * ['id' => '[0-9]+', 'lang' => '[a-z]{3}']
- * );
- * ```
- *
- * Shows connecting a route with custom route parameters as well as
- * providing patterns for those parameters. Patterns for routing parameters
- * do not need capturing groups, as one will be added for each route params.
- *
- * $options offers several 'special' keys that have special meaning
- * in the $options array.
- *
- * - `routeClass` is used to extend and change how individual routes parse requests
- * and handle reverse routing, via a custom routing class.
- * Ex. `'routeClass' => 'SlugRoute'`
- * - `pass` is used to define which of the routed parameters should be shifted
- * into the pass array. Adding a parameter to pass will remove it from the
- * regular route array. Ex. `'pass' => ['slug']`.
- * - `persist` is used to define which route parameters should be automatically
- * included when generating new URLs. You can override persistent parameters
- * by redefining them in a URL or remove them by setting the parameter to `false`.
- * Ex. `'persist' => ['lang']`
- * - `multibytePattern` Set to true to enable multibyte pattern support in route
- * parameter patterns.
- * - `_name` is used to define a specific name for routes. This can be used to optimize
- * reverse routing lookups. If undefined a name will be generated for each
- * connected route.
- * - `_ext` is an array of filename extensions that will be parsed out of the url if present.
- * See {@link \Cake\Routing\RouteCollection::setExtensions()}.
- * - `_method` Only match requests with specific HTTP verbs.
- *
- * Example of using the `_method` condition:
- *
- * ```
- * $routes->connect('/tasks', ['controller' => 'Tasks', 'action' => 'index', '_method' => 'GET']);
- * ```
- *
- * The above route will only be matched for GET requests. POST requests will fail to match this route.
- *
- * @param string $route A string describing the template of the route
- * @param array|string $defaults An array describing the default route parameters. These parameters will be used by default
- * and can supply routing parameters that are not dynamic. See above.
- * @param array $options An array matching the named elements in the route to regular expressions which that
- * element should match. Also contains additional parameters such as which routed parameters should be
- * shifted into the passed arguments, supplying patterns for routing parameters and supplying the name of a
- * custom routing class.
- * @return \Cake\Routing\Route\Route
- * @throws \InvalidArgumentException
- * @throws \BadMethodCallException
- */
- public function connect($route, $defaults = [], array $options = [])
- {
- $defaults = $this->parseDefaults($defaults);
- if (empty($options['_ext'])) {
- $options['_ext'] = $this->_extensions;
- }
- if (empty($options['routeClass'])) {
- $options['routeClass'] = $this->_routeClass;
- }
- if (isset($options['_name']) && $this->_namePrefix) {
- $options['_name'] = $this->_namePrefix . $options['_name'];
- }
- if (empty($options['_middleware'])) {
- $options['_middleware'] = $this->middleware;
- }
- $route = $this->_makeRoute($route, $defaults, $options);
- $this->_collection->add($route, $options);
- return $route;
- }
- /**
- * Parse the defaults if they're a string
- *
- * @param string|array $defaults Defaults array from the connect() method.
- * @return string|array
- */
- protected static function parseDefaults($defaults)
- {
- if (!is_string($defaults)) {
- return $defaults;
- }
- $regex = '/(?:(?<plugin>[a-zA-Z0-9\/]*)\.)?(?<prefix>[a-zA-Z0-9\/]*?)' .
- '(?:\/)?(?<controller>[a-zA-Z0-9]*):{2}(?<action>[a-zA-Z0-9_]*)/i';
- if (preg_match($regex, $defaults, $matches)) {
- foreach ($matches as $key => $value) {
- // Remove numeric keys and empty values.
- if (is_int($key) || $value === '' || $value === '::') {
- unset($matches[$key]);
- }
- }
- $length = count($matches);
- if (isset($matches['prefix'])) {
- $matches['prefix'] = strtolower($matches['prefix']);
- }
- if ($length >= 2 || $length <= 4) {
- return $matches;
- }
- }
- throw new RuntimeException("Could not parse `{$defaults}` route destination string.");
- }
- /**
- * Create a route object, or return the provided object.
- *
- * @param string|\Cake\Routing\Route\Route $route The route template or route object.
- * @param array $defaults Default parameters.
- * @param array $options Additional options parameters.
- * @return \Cake\Routing\Route\Route
- * @throws \InvalidArgumentException when route class or route object is invalid.
- * @throws \BadMethodCallException when the route to make conflicts with the current scope
- */
- protected function _makeRoute($route, $defaults, $options)
- {
- if (is_string($route)) {
- $routeClass = App::className($options['routeClass'], 'Routing/Route');
- if ($routeClass === false) {
- throw new InvalidArgumentException(sprintf(
- 'Cannot find route class %s',
- $options['routeClass']
- ));
- }
- $route = str_replace('//', '/', $this->_path . $route);
- if ($route !== '/') {
- $route = rtrim($route, '/');
- }
- foreach ($this->_params as $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(
- $msg,
- $param,
- $val,
- $param,
- $defaults[$param]
- ));
- }
- }
- $defaults += $this->_params + ['plugin' => null];
- if (!isset($defaults['action']) && !isset($options['action'])) {
- $defaults['action'] = 'index';
- }
- $route = new $routeClass($route, $defaults, $options);
- }
- if ($route instanceof Route) {
- return $route;
- }
- throw new InvalidArgumentException(
- 'Route class not found, or route class is not a subclass of Cake\Routing\Route\Route'
- );
- }
- /**
- * Connects a new redirection Route in the router.
- *
- * Redirection routes are different from normal routes as they perform an actual
- * header redirection if a match is found. The redirection can occur within your
- * application or redirect to an outside location.
- *
- * Examples:
- *
- * ```
- * $routes->redirect('/home/*', ['controller' => 'posts', 'action' => 'view']);
- * ```
- *
- * Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as the
- * redirect destination allows you to use other routes to define where a URL string should be redirected to.
- *
- * ```
- * $routes->redirect('/posts/*', 'http://google.com', ['status' => 302]);
- * ```
- *
- * Redirects /posts/* to http://google.com with a HTTP status of 302
- *
- * ### Options:
- *
- * - `status` Sets the HTTP status (default 301)
- * - `persist` Passes the params to the redirected route, if it can. This is useful with greedy routes,
- * routes that end in `*` are greedy. As you can remap URLs and not lose any passed args.
- *
- * @param string $route A string describing the template of the route
- * @param array|string $url A URL to redirect to. Can be a string or a Cake array-based URL
- * @param array $options An array matching the named elements in the route to regular expressions which that
- * element should match. Also contains additional parameters such as which routed parameters should be
- * shifted into the passed arguments. As well as supplying patterns for routing parameters.
- * @return \Cake\Routing\Route\Route|\Cake\Routing\Route\RedirectRoute
- */
- public function redirect($route, $url, array $options = [])
- {
- if (!isset($options['routeClass'])) {
- $options['routeClass'] = 'Cake\Routing\Route\RedirectRoute';
- }
- if (is_string($url)) {
- $url = ['redirect' => $url];
- }
- return $this->connect($route, $url, $options);
- }
- /**
- * Add prefixed routes.
- *
- * This method creates a scoped route collection that includes
- * relevant prefix information.
- *
- * The $name parameter is used to generate the routing parameter name.
- * For example a path of `admin` would result in `'prefix' => 'admin'` being
- * applied to all connected routes.
- *
- * You can re-open a prefix as many times as necessary, as well as nest prefixes.
- * Nested prefixes will result in prefix values like `admin/api` which translates
- * to the `Controller\Admin\Api\` namespace.
- *
- * If you need to have prefix with dots, eg: '/api/v1.0', use 'path' key
- * for $params argument:
- *
- * ```
- * $route->prefix('api', function($route) {
- * $route->prefix('v10', ['path' => '/v1.0'], function($route) {
- * // Translates to `Controller\Api\V10\` namespace
- * });
- * });
- * ```
- *
- * @param string $name The prefix name to use.
- * @param array|callable $params An array of routing defaults to add to each connected route.
- * If you have no parameters, this argument can be a callable.
- * @param callable|null $callback The callback to invoke that builds the prefixed routes.
- * @return void
- * @throws \InvalidArgumentException If a valid callback is not passed
- */
- public function prefix($name, $params = [], callable $callback = null)
- {
- if ($callback === null) {
- if (!is_callable($params)) {
- throw new InvalidArgumentException('A valid callback is expected');
- }
- $callback = $params;
- $params = [];
- }
- $name = Inflector::underscore($name);
- $path = '/' . $name;
- if (isset($params['path'])) {
- $path = $params['path'];
- unset($params['path']);
- }
- if (isset($this->_params['prefix'])) {
- $name = $this->_params['prefix'] . '/' . $name;
- }
- $params = array_merge($params, ['prefix' => $name]);
- $this->scope($path, $params, $callback);
- }
- /**
- * Add plugin routes.
- *
- * This method creates a new scoped route collection that includes
- * relevant plugin information.
- *
- * The plugin name will be inflected to the underscore version to create
- * the routing path. If you want a custom path name, use the `path` option.
- *
- * Routes connected in the scoped collection will have the correct path segment
- * prepended, and have a matching plugin routing key set.
- *
- * @param string $name The plugin name to build routes for
- * @param array|callable $options Either the options to use, or a callback
- * @param callable|null $callback The callback to invoke that builds the plugin routes
- * Only required when $options is defined.
- * @return void
- */
- public function plugin($name, $options = [], $callback = null)
- {
- if ($callback === null) {
- $callback = $options;
- $options = [];
- }
- $params = ['plugin' => $name] + $this->_params;
- if (empty($options['path'])) {
- $options['path'] = '/' . Inflector::underscore($name);
- }
- $this->scope($options['path'], $params, $callback);
- }
- /**
- * Create a new routing scope.
- *
- * Scopes created with this method will inherit the properties of the scope they are
- * added to. This means that both the current path and parameters will be appended
- * to the supplied parameters.
- *
- * @param string $path The path to create a scope for.
- * @param array|callable $params Either the parameters to add to routes, or a callback.
- * @param callable|null $callback The callback to invoke that builds the plugin routes.
- * Only required when $params is defined.
- * @return void
- * @throws \InvalidArgumentException when there is no callable parameter.
- */
- public function scope($path, $params, $callback = null)
- {
- if (is_callable($params)) {
- $callback = $params;
- $params = [];
- }
- if (!is_callable($callback)) {
- $msg = 'Need a callable function/object to connect routes.';
- throw new InvalidArgumentException($msg);
- }
- if ($this->_path !== '/') {
- $path = $this->_path . $path;
- }
- $namePrefix = $this->_namePrefix;
- if (isset($params['_namePrefix'])) {
- $namePrefix .= $params['_namePrefix'];
- }
- unset($params['_namePrefix']);
- $params += $this->_params;
- $builder = new static($this->_collection, $path, $params, [
- 'routeClass' => $this->_routeClass,
- 'extensions' => $this->_extensions,
- 'namePrefix' => $namePrefix,
- 'middleware' => $this->middleware,
- ]);
- $callback($builder);
- }
- /**
- * Connect the `/:controller` and `/:controller/:action/*` fallback routes.
- *
- * This is a shortcut method for connecting fallback routes in a given scope.
- *
- * @param string|null $routeClass the route class to use, uses the default routeClass
- * if not specified
- * @return void
- */
- public function fallbacks($routeClass = null)
- {
- $routeClass = $routeClass ?: $this->_routeClass;
- $this->connect('/:controller', ['action' => 'index'], compact('routeClass'));
- $this->connect('/:controller/:action/*', [], compact('routeClass'));
- }
- /**
- * Register a middleware with the RouteCollection.
- *
- * Once middleware has been registered, it can be applied to the current routing
- * scope or any child scopes that share the same RouteCollection.
- *
- * @param string $name The name of the middleware. Used when applying middleware to a scope.
- * @param callable|string $middleware The middleware callable or class name to register.
- * @return $this
- * @see \Cake\Routing\RouteCollection
- */
- public function registerMiddleware($name, $middleware)
- {
- $this->_collection->registerMiddleware($name, $middleware);
- return $this;
- }
- /**
- * Apply a middleware to the current route scope.
- *
- * Requires middleware to be registered via `registerMiddleware()`
- *
- * @param string ...$names The names of the middleware to apply to the current scope.
- * @return $this
- * @throws \RuntimeException
- * @see \Cake\Routing\RouteCollection::addMiddlewareToScope()
- */
- public function applyMiddleware(...$names)
- {
- foreach ($names as $name) {
- if (!$this->_collection->middlewareExists($name)) {
- $message = "Cannot apply '$name' middleware or middleware group. " .
- 'Use registerMiddleware() to register middleware.';
- throw new RuntimeException($message);
- }
- }
- $this->middleware = array_unique(array_merge($this->middleware, $names));
- return $this;
- }
- /**
- * Apply a set of middleware to a group
- *
- * @param string $name Name of the middleware group
- * @param string[] $middlewareNames Names of the middleware
- * @return $this
- */
- public function middlewareGroup($name, array $middlewareNames)
- {
- $this->_collection->middlewareGroup($name, $middlewareNames);
- return $this;
- }
- }
|