Routing

  • class Cake\Routing\Router

Routing provides you tools that map URLs to controller actions. By definingroutes, you can separate how your application is implemented from how its URL’sare structured.

Routing in CakePHP also encompasses the idea of reverse routing, where an arrayof parameters can be transformed into a URL string. By using reverse routing,you can re-factor your application’s URL structure without having to update allyour code.

Quick Tour

This section will teach you by example the most common uses of the CakePHPRouter. Typically you want to display something as a landing page, so you addthis to your config/routes.php file:

  1. use Cake\Routing\RouteBuilder;
  2. use Cake\Routing\Router;
  3.  
  4. // Using a scoped route builder.
  5. Router::scope('/', function (RouteBuilder $routes) {
  6. $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
  7. });
  8.  
  9. // Using the static method.
  10. Router::connect('/', ['controller' => 'Articles', 'action' => 'index']);

Router provides two interfaces for connecting routes. The static method isa backwards compatible interface, while the scoped builders offer more tersesyntax when building multiple routes, and better performance.

This will execute the index method in the ArticlesController when thehomepage of your site is visited. Sometimes you need dynamic routes that willaccept multiple parameters, this would be the case, for example of a route forviewing an article’s content:

  1. $routes->connect('/articles/*', ['controller' => 'Articles', 'action' => 'view']);

The above route will accept any URL looking like /articles/15 and invoke themethod view(15) in the ArticlesController. This will not, though,prevent people from trying to access URLs looking like /articles/foobar. Ifyou wish, you can restrict some parameters to conform to a regular expression:

  1. $routes->connect(
  2. '/articles/{id}',
  3. ['controller' => 'Articles', 'action' => 'view'],
  4. )
  5. ->setPatterns(['id' => '\d+'])
  6. ->setPass(['id']);
  7.  
  8. $routes->connect(
  9. '/articles/{id}',
  10. ['controller' => 'Articles', 'action' => 'view'],
  11. ['id' => '\d+', 'pass' => ['id']]
  12. );

The previous example changed the star matcher by a new placeholder {id}.Using placeholders allows us to validate parts of the URL, in this case we usedthe \d+ regular expression so that only digits are matched. Finally, we toldthe Router to treat the id placeholder as a function argument to theview() function by specifying the pass option. More on using thisoption later.

The CakePHP Router can also reverse match routes. That means that from anarray containing matching parameters, it is capable of generating a URL string:

  1. use Cake\Routing\Router;
  2.  
  3. echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]);
  4. // Will output
  5. /articles/15

Routes can also be labelled with a unique name, this allows you to quicklyreference them when building links instead of specifying each of the routingparameters:

  1. // In routes.php
  2. $routes->connect(
  3. '/login',
  4. ['controller' => 'Users', 'action' => 'login'],
  5. ['_name' => 'login']
  6. );
  7.  
  8. use Cake\Routing\Router;
  9.  
  10. echo Router::url(['_name' => 'login']);
  11. // Will output
  12. /login

To help keep your routing code DRY, the Router has the concept of ‘scopes’.A scope defines a common path segment, and optionally route defaults. Any routesconnected inside a scope will inherit the path/defaults from their wrappingscopes:

  1. Router::scope('/blog', ['plugin' => 'Blog'], function (RouteBuilder $routes) {
  2. $routes->connect('/', ['controller' => 'Articles']);
  3. });

The above route would match /blog/ and send it toBlog\Controller\ArticlesController::index().

The application skeleton comes with a few routes to get you started. Once you’veadded your own routes, you can remove the default routes if you don’t need them.

Connecting Routes

To keep your code DRY you should use ‘routing scopes’. Routingscopes not only let you keep your code DRY, they also help Router optimize itsoperation. This method defaults to the / scope. To create a scope and connectsome routes we’ll use the scope() method:

  1. // In config/routes.php
  2. use Cake\Routing\Route\DashedRoute;
  3.  
  4. Router::scope('/', function (RouteBuilder $routes) {
  5. // Connect the generic fallback routes.
  6. $routes->fallbacks(DashedRoute::class);
  7. });

The connect() method takes up to three parameters: the URL template you wishto match, the default values for your route elements, and the options for theroute. Options frequently include regular expression rules to help the routermatch elements in the URL.

The basic format for a route definition is:

  1. $routes->connect(
  2. '/url/template',
  3. ['targetKey' => 'targetValue'],
  4. ['option' => 'matchingRegex']
  5. );

The first parameter is used to tell the router what sort of URL you’re trying tocontrol. The URL is a normal slash delimited string, but can also containa wildcard () or Route Elements. Using a wildcard tells the routerthat you are willing to accept any additional arguments supplied. Routes withouta only match the exact template pattern supplied.

Once you’ve specified a URL, you use the last two parameters of connect() totell CakePHP what to do with a request once it has been matched. The secondparameter defines the route ‘target’. This can be defined either as an array, oras a destination string. A few examples of route targets are:

  1. // Array target to an application controller
  2. $routes->connect(
  3. '/users/view/*',
  4. ['controller' => 'Users', 'action' => 'view']
  5. );
  6. $routes->connect('/users/view/*', 'Users::view');
  7.  
  8. // Array target to a prefixed plugin controller
  9. $routes->connect(
  10. '/admin/cms/articles',
  11. ['prefix' => 'Admin', 'plugin' => 'Cms', 'controller' => 'Articles', 'action' => 'index']
  12. );
  13. $routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index');

The first route we connect matches URLs starting with /users/view and mapsthose requests to the UsersController->view(). The trailing /* tells therouter to pass any additional segments as method arguments. For example,/users/view/123 would map to UsersController->view(123).

The above example also illustrates string targets. String targets providea compact way to define a route’s destination. String targets have the followingsyntax:

  1. [Plugin].[Prefix]/[Controller]::[action]

Some example string targets are:

  1. // Application controller
  2. 'Bookmarks::view'
  3.  
  4. // Application controller with prefix
  5. Admin/Bookmarks::view
  6.  
  7. // Plugin controller
  8. Cms.Articles::edit
  9.  
  10. // Prefixed plugin controller
  11. Vendor/Cms.Management/Admin/Articles::view

Earlier we used the greedy star (/) to capture additional path segments,there is also the trailing star (/*). Using a trailing double star,will capture the remainder of a URL as a single passed argument. This is usefulwhen you want to use an argument that included a / in it:

  1. $routes->connect(
  2. '/pages/**',
  3. ['controller' => 'Pages', 'action' => 'show']
  4. );

The incoming URL of /pages/the-example-/-and-proof would result in a singlepassed argument of the-example-/-and-proof.

The second parameter of connect() can define any parameters thatcompose the default route parameters:

  1. $routes->connect(
  2. '/government',
  3. ['controller' => 'Pages', 'action' => 'display', 5]
  4. );

This example uses the second parameter of connect() todefine default parameters. If you built an application that features products fordifferent categories of customers, you might consider creating a route. Thisallows you to link to /government rather than /pages/display/5.

A common use for routing is to rename controllers and their actions. Instead ofaccessing our users controller at /users/some-action/5, we’d like to be ableto access it through /cooks/some-action/5. The following route takes care ofthat:

  1. $routes->connect(
  2. '/cooks/{action}/*', ['controller' => 'Users']
  3. );

This is telling the Router that any URL beginning with /cooks/ should besent to the UsersController. The action called will depend on the value ofthe {action} parameter. By using Route Elements, you can createvariable routes, that accept user input or variables. The above route also usesthe greedy star. The greedy star indicates that this route should accept anyadditional positional arguments given. These arguments will be made available inthe Passed Arguments array.

When generating URLs, routes are used too. Using['controller' => 'Users', 'action' => 'some-action', 5] asa URL will output /cooks/some-action/5 if the above route is thefirst match found.

The routes we’ve connected so far will match any HTTP verb. If you are buildinga REST API you’ll often want to map HTTP actions to different controller methods.The RouteBuilder provides helper methods that make defining routes forspecific HTTP verbs simpler:

  1. // Create a route that only responds to GET requests.
  2. $routes->get(
  3. '/cooks/{id}',
  4. ['controller' => 'Users', 'action' => 'view'],
  5. 'users:view'
  6. );
  7.  
  8. // Create a route that only responds to PUT requests
  9. $routes->put(
  10. '/cooks/{id}',
  11. ['controller' => 'Users', 'action' => 'update'],
  12. 'users:update'
  13. );

The above routes map the same URL to different controller actions based on theHTTP verb used. GET requests will go to the ‘view’ action, while PUT requestswill go to the ‘update’ action. There are HTTP helper methods for:

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE
  • OPTIONS
  • HEAD

All of these methods return the route instance allowing you to leverage thefluent setters to further configure your route.

Route Elements

You can specify your own route elements and doing so gives you thepower to define places in the URL where parameters for controlleractions should lie. When a request is made, the values for theseroute elements are found in $this->request->getParam() in the controller.When you define a custom route element, you can optionally specify a regularexpression - this tells CakePHP how to know if the URL is correctly formed ornot. If you choose to not provide a regular expression, any non / characterwill be treated as part of the parameter:

  1. $routes->connect(
  2. '/{controller}/{id}',
  3. ['action' => 'view']
  4. )->setPatterns(['id' => '[0-9]+']);
  5.  
  6. $routes->connect(
  7. '/{controller}/{id}',
  8. ['action' => 'view'],
  9. ['id' => '[0-9]+']
  10. );

The above example illustrates how to create a quick way to viewmodels from any controller by crafting a URL that looks like/controllername/{id}. The URL provided to connect() specifies tworoute elements: {controller} and {id}. The {controller} elementis a CakePHP default route element, so the router knows how to match andidentify controller names in URLs. The {id} element is a customroute element, and must be further clarified by specifying amatching regular expression in the third parameter of connect().

CakePHP does not automatically produce lowercased and dashed URLs when using the{controller} parameter. If you need this, the above example could berewritten like so:

  1. use Cake\Routing\Route\DashedRoute;
  2.  
  3. // Create a builder with a different route class.
  4. $routes->scope('/', function (RouteBuilder $routes) {
  5. $routes->setRouteClass(DashedRoute::class);
  6. $routes->connect('/{controller}/{id}', ['action' => 'view'])
  7. ->setPatterns(['id' => '[0-9]+']);
  8.  
  9. $routes->connect(
  10. '/{controller}/{id}',
  11. ['action' => 'view'],
  12. ['id' => '[0-9]+']
  13. );
  14. });

The DashedRoute class will make sure that the {controller} and{plugin} parameters are correctly lowercased and dashed.

If you need lowercased and underscored URLs while migrating from a CakePHP2.x application, you can instead use the InflectedRoute class.

Note

Patterns used for route elements must not contain any capturinggroups. If they do, Router will not function correctly.

Once this route has been defined, requesting /apples/5 would call the view()method of the ApplesController. Inside the view() method, you would need toaccess the passed ID at $this->request->getParam('id').

If you have a single controller in your application and you do not want thecontroller name to appear in the URL, you can map all URLs to actions in yourcontroller. For example, to map all URLs to actions of the home controller,e.g have URLs like /demo instead of /home/demo, you can do thefollowing:

  1. $routes->connect('/{action}', ['controller' => 'Home']);

If you would like to provide a case insensitive URL, you can use regularexpression inline modifiers:

  1. $routes->connect(
  2. '/{userShortcut}',
  3. ['controller' => 'Teachers', 'action' => 'profile', 1],
  4. )->setPatterns(['userShortcut' => '(?i:principal)']);

One more example, and you’ll be a routing pro:

  1. $routes->connect(
  2. '/{controller}/{year}/{month}/{day}',
  3. ['action' => 'index']
  4. )->setPatterns([
  5. 'year' => '[12][0-9]{3}',
  6. 'month' => '0[1-9]|1[012]',
  7. 'day' => '0[1-9]|[12][0-9]|3[01]'
  8. ]);

This is rather involved, but shows how powerful routes can be. The URL suppliedhas four route elements. The first is familiar to us: it’s a default routeelement that tells CakePHP to expect a controller name.

Next, we specify some default values. Regardless of the controller,we want the index() action to be called.

Finally, we specify some regular expressions that will match years,months and days in numerical form. Note that parenthesis (capturing groups)are not supported in the regular expressions. You can still specifyalternates, as above, but not grouped with parenthesis.

Once defined, this route will match /articles/2007/02/01,/articles/2004/11/16, handing the requests tothe index() actions of their respective controllers, with the dateparameters in $this->request->getParam().

Reserved Route Elements

There are several route elements that have special meaning inCakePHP, and should not be used unless you want the special meaning

  • controller Used to name the controller for a route.
  • action Used to name the controller action for a route.
  • plugin Used to name the plugin a controller is located in.
  • prefix Used for Prefix Routing
  • _ext Used for File extentions routing.
  • _base Set to false to remove the base path from the generated URL. Ifyour application is not in the root directory, this can be used to generateURLs that are ‘cake relative’.
  • scheme Set to create links on different schemes like _webcal or ftp.Defaults to the current scheme.
  • _host Set the host to use for the link. Defaults to the current host.
  • _port Set the port if you need to create links on non-standard ports.
  • full If true the _FULL_BASE_URL constant will be prepended togenerated URLs.
  • # Allows you to set URL hash fragments.
  • _ssl Set to true to convert the generated URL to https or falseto force http.
  • _method Define the HTTP verb/method to use. Useful when working withRESTful Routing.
  • _name Name of route. If you have setup named routes, you can use this keyto specify it.

Configuring Route Options

There are a number of route options that can be set on each route. Afterconnecting a route you can use its fluent builder methods to further configurethe route. These methods replace many of the keys in the $options parameterof connect():

  1. $routes->connect(
  2. '/{lang}/articles/{slug}',
  3. ['controller' => 'Articles', 'action' => 'view'],
  4. )
  5. // Allow GET and POST requests.
  6. ->setMethods(['GET', 'POST'])
  7.  
  8. // Only match on the blog subdomain.
  9. ->setHost('blog.example.com')
  10.  
  11. // Set the route elements that should be converted to passed arguments
  12. ->setPass(['slug'])
  13.  
  14. // Set the matching patterns for route elements
  15. ->setPatterns([
  16. 'slug' => '[a-z0-9-_]+',
  17. 'lang' => 'en|fr|es',
  18. ])
  19.  
  20. // Also allow JSON file extensions
  21. ->setExtensions(['json'])
  22.  
  23. // Set lang to be a persistent parameter
  24. ->setPersist(['lang']);

Passing Parameters to Action

When connecting routes using Route Elements you may want to have routedelements be passed arguments instead. The pass option whitelists which routeelements should also be made available as arguments passed into the controllerfunctions:

  1. // src/Controller/BlogsController.php
  2. public function view($articleId = null, $slug = null)
  3. {
  4. // Some code here...
  5. }
  6.  
  7. // routes.php
  8. Router::scope('/', function (RouteBuilder $routes) {
  9. $routes->connect(
  10. '/blog/{id}-{slug}', // E.g. /blog/3-CakePHP_Rocks
  11. ['controller' => 'Blogs', 'action' => 'view']
  12. )
  13. // Define the route elements in the route template
  14. // to pass as function arguments. Order matters since this
  15. // will simply map "{id}" to $articleId in your action
  16. ->setPass(['id', 'slug'])
  17. // Define a pattern that `id` must match.
  18. ->setPatterns([
  19. 'id' => '[0-9]+',
  20. ]);
  21. });

Now thanks to the reverse routing capabilities, you can pass in the URL arraylike below and CakePHP will know how to form the URL as defined in the routes:

  1. // view.php
  2. // This will return a link to /blog/3-CakePHP_Rocks
  3. echo $this->Html->link('CakePHP Rocks', [
  4. 'controller' => 'Blog',
  5. 'action' => 'view',
  6. 'id' => 3,
  7. 'slug' => 'CakePHP_Rocks'
  8. ]);
  9.  
  10. // You can also used numerically indexed parameters.
  11. echo $this->Html->link('CakePHP Rocks', [
  12. 'controller' => 'Blog',
  13. 'action' => 'view',
  14. 3,
  15. 'CakePHP_Rocks'
  16. ]);

Using Named Routes

Sometimes you’ll find typing out all the URL parameters for a route too verbose,or you’d like to take advantage of the performance improvements that namedroutes have. When connecting routes you can specifiy a _name option, thisoption can be used in reverse routing to identify the route you want to use:

  1. // Connect a route with a name.
  2. $routes->connect(
  3. '/login',
  4. ['controller' => 'Users', 'action' => 'login'],
  5. ['_name' => 'login']
  6. );
  7.  
  8. // Name a verb specific route
  9. $routes->post(
  10. '/logout',
  11. ['controller' => 'Users', 'action' => 'logout'],
  12. 'logout'
  13. );
  14.  
  15. // Generate a URL using a named route.
  16. $url = Router::url(['_name' => 'logout']);
  17.  
  18. // Generate a URL using a named route,
  19. // with some query string args.
  20. $url = Router::url(['_name' => 'login', 'username' => 'jimmy']);

If your route template contains any route elements like {controller} you’llneed to supply those as part of the options to Router::url().

Note

Route names must be unique across your entire application. The same_name cannot be used twice, even if the names occur inside a differentrouting scope.

When building named routes, you will probably want to stick to some conventionsfor the route names. CakePHP makes building up route names easier by allowingyou to define name prefixes in each scope:

  1. Router::scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) {
  2. // This route's name will be `api:ping`
  3. $routes->get('/ping', ['controller' => 'Pings'], 'ping');
  4. });
  5. // Generate a URL for the ping route
  6. Router::url(['_name' => 'api:ping']);
  7.  
  8. // Use namePrefix with plugin()
  9. Router::plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) {
  10. // Connect routes.
  11. });
  12.  
  13. // Or with prefix()
  14. Router::prefix('Admin', ['_namePrefix' => 'admin:'], function (RouteBuilder $routes) {
  15. // Connect routes.
  16. });

You can also use the _namePrefix option inside nested scopes and it works asyou’d expect:

  1. Router::plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) {
  2. $routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) {
  3. // This route's name will be `contacts:api:ping`
  4. $routes->get('/ping', ['controller' => 'Pings'], 'ping');
  5. });
  6. });
  7.  
  8. // Generate a URL for the ping route
  9. Router::url(['_name' => 'contacts:api:ping']);

Routes connected in named scopes will only have names added if the route is alsonamed. Nameless routes will not have the _namePrefix applied to them.

Prefix Routing

  • static Cake\Routing\Router::prefix($name, $callback)

Many applications require an administration section whereprivileged users can make changes. This is often done through aspecial URL such as /admin/users/edit/5. In CakePHP, prefix routingcan be enabled by using the prefix scope method:

  1. use Cake\Routing\Route\DashedRoute;
  2.  
  3. Router::prefix('admin', function (RouteBuilder $routes) {
  4. // All routes here will be prefixed with `/admin`
  5. // And have the prefix => Admin route element added.
  6. $routes->fallbacks(DashedRoute::class);
  7. });

Prefixes are mapped to sub-namespaces in your application’s Controllernamespace. By having prefixes as separate controllers you can create smaller andsimpler controllers. Behavior that is common to the prefixed and non-prefixedcontrollers can be encapsulated using inheritance,Components, or traits. Using our users example, accessingthe URL /admin/users/edit/5 would call the edit() method of oursrc/Controller/Admin/UsersController.php passing 5 as the first parameter.The view file used would be templates/Admin/Users/edit.php

You can map the URL /admin to your index() action of pages controller usingfollowing route:

  1. Router::prefix('admin', function (RouteBuilder $routes) {
  2. // Because you are in the admin scope,
  3. // you do not need to include the /admin prefix
  4. // or the Admin route element.
  5. $routes->connect('/', ['controller' => 'Pages', 'action' => 'index']);
  6. });

When creating prefix routes, you can set additional route parameters usingthe $options argument:

  1. Router::prefix('admin', ['param' => 'value'], function (RouteBuilder $routes) {
  2. // Routes connected here are prefixed with '/admin' and
  3. // have the 'param' routing key set.
  4. $routes->connect('/{controller}');
  5. });

Make sure to set a path for multi word prefixes to keep the desired inflection:

  1. Router::prefix('my_prefix', ['path' => '/my-prefix'], function (RouteBuilder $routes) {
  2. // Routes connected here are prefixed with '/my-prefix'
  3. $routes->connect('/:controller');
  4. });

You can define prefixes inside plugin scopes as well:

  1. Router::plugin('DebugKit', function (RouteBuilder $routes) {
  2. $routes->prefix('admin', function (RouteBuilder $routes) {
  3. $routes->connect('/{controller}');
  4. });
  5. });

The above would create a route template like /debug-kit/admin/{controller}.The connected route would have the plugin and prefix route elements set.

When defining prefixes, you can nest multiple prefixes if necessary:

  1. Router::prefix('manager', function (RouteBuilder $routes) {
  2. $routes->prefix('admin', function (RouteBuilder $routes) {
  3. $routes->connect('/{controller}');
  4. });
  5. });

The above would create a route template like /manager/admin/{controller}.The connected route would have the prefix route element set toManager/Admin.

The current prefix will be available from the controller methods through$this->request->getParam('prefix')

When using prefix routes it’s important to set the prefix option. Here’s how tobuild this link using the HTML helper:

  1. // Go into a prefixed route.
  2. echo $this->Html->link(
  3. 'Manage articles',
  4. ['prefix' => 'Manager', 'controller' => 'Articles', 'action' => 'add']
  5. );
  6.  
  7. // Leave a prefix
  8. echo $this->Html->link(
  9. 'View Post',
  10. ['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5]
  11. );

Note

You should connect prefix routes before you connect fallback routes.

You can create links that point to a prefix, by adding the prefix key to yourURL array:

  1. echo $this->Html->link(
  2. 'New admin todo',
  3. ['prefix' => 'admin', 'controller' => 'TodoItems', 'action' => 'create']
  4. );

When using nesting, you need to chain them together:

  1. echo $this->Html->link(
  2. 'New todo',
  3. ['prefix' => 'admin/my_prefix', 'controller' => 'TodoItems', 'action' => 'create']
  4. );

This would link to a controller with the namespace App\Controller\Admin\MyPrefix and the file pathsrc/Controller/Admin/MyPrefix/TodoItemsController.php.

Note

The prefix is always under_scored here, even if the routing result is dashed.The route itself will do the inflection if necessary.

Plugin Routing

  • static Cake\Routing\Router::plugin($name, $options = [], $callback)

Routes for Plugins should be created using the plugin()method. This method creates a new routing scope for the plugin’s routes:

  1. Router::plugin('DebugKit', function (RouteBuilder $routes) {
  2. // Routes connected here are prefixed with '/debug-kit' and
  3. // have the plugin route element set to 'DebugKit'.
  4. $routes->connect('/{controller}');
  5. });

When creating plugin scopes, you can customize the path element used with thepath option:

  1. Router::plugin('DebugKit', ['path' => '/debugger'], function (RouteBuilder $routes) {
  2. // Routes connected here are prefixed with '/debugger' and
  3. // have the plugin route element set to 'DebugKit'.
  4. $routes->connect('/{controller}');
  5. });

When using scopes you can nest plugin scopes within prefix scopes:

  1. Router::prefix('admin', function (RouteBuilder $routes) {
  2. $routes->plugin('DebugKit', function (RouteBuilder $routes) {
  3. $routes->connect('/{controller}');
  4. });
  5. });

The above would create a route that looks like /admin/debug-kit/{controller}.It would have the prefix, and plugin route elements set. ThePlugin Routes section has more information on building plugin routes.

You can create links that point to a plugin, by adding the plugin key to yourURL array:

  1. echo $this->Html->link(
  2. 'New todo',
  3. ['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create']
  4. );

Conversely if the active request is a plugin request and you want to createa link that has no plugin you can do the following:

  1. echo $this->Html->link(
  2. 'New todo',
  3. ['plugin' => null, 'controller' => 'Users', 'action' => 'profile']
  4. );

By setting 'plugin' => null you tell the Router that you want tocreate a link that is not part of a plugin.

SEO-Friendly Routing

Some developers prefer to use dashes in URLs, as it’s perceived to givebetter search engine rankings. The DashedRoute class can be used in yourapplication with the ability to route plugin, controller, and camelized actionnames to a dashed URL.

For example, if we had a ToDo plugin, with a TodoItems controller, and ashowItems() action, it could be accessed at /to-do/todo-items/show-itemswith the following router connection:

  1. use Cake\Routing\Route\DashedRoute;
  2.  
  3. Router::plugin('ToDo', ['path' => 'to-do'], function (RouteBuilder $routes) {
  4. $routes->fallbacks(DashedRoute::class);
  5. });

Matching Specific HTTP Methods

Routes can match specific HTTP methods using the HTTP verb helper methods:

  1. Router::scope('/', function (RouteBuilder $routes) {
  2. // This route only matches on POST requests.
  3. $routes->post(
  4. '/reviews/start',
  5. ['controller' => 'Reviews', 'action' => 'start']
  6. );
  7.  
  8. // Match multiple verbs
  9. $routes->connect(
  10. '/reviews/start',
  11. [
  12. 'controller' => 'Reviews',
  13. 'action' => 'start',
  14. ]
  15. )->setMethods(['POST', 'PUT']);
  16. });

You can match multiple HTTP methods by using an array. Because the _methodparameter is a routing key, it participates in both URL parsing and URLgeneration. To generate URLs for method specific routes you’ll need to includethe _method key when generating the URL:

  1. $url = Router::url([
  2. 'controller' => 'Reviews',
  3. 'action' => 'start',
  4. '_method' => 'POST',
  5. ]);

Matching Specific Hostnames

Routes can use the _host option to only match specific hosts. You can usethe *. wildcard to match any subdomain:

  1. Router::scope('/', function (RouteBuilder $routes) {
  2. // This route only matches on http://images.example.com
  3. $routes->connect(
  4. '/images/default-logo.png',
  5. ['controller' => 'Images', 'action' => 'default']
  6. )->setHost('images.example.com');
  7.  
  8. // This route only matches on http://*.example.com
  9. $routes->connect(
  10. '/images/old-log.png',
  11. ['controller' => 'Images', 'action' => 'oldLogo']
  12. )->setHost('*.example.com');
  13. });

The _host option is also used in URL generation. If your _host optionspecifies an exact domain, that domain will be included in the generated URL.However, if you use a wildcard, then you will need to provide the _hostparameter when generating URLs:

  1. // If you have this route
  2. $routes->connect(
  3. '/images/old-log.png',
  4. ['controller' => 'Images', 'action' => 'oldLogo']
  5. )->setHost('images.example.com');
  6.  
  7. // You need this to generate a url
  8. echo Router::url([
  9. 'controller' => 'Images',
  10. 'action' => 'oldLogo',
  11. '_host' => 'images.example.com',
  12. ]);

Routing File Extensions

  • static Cake\Routing\Router::extensions(string|array|null $extensions, $merge = true)

To handle different file extensions with your routes, you can define extensionson a global, as well as on a scoped level. Defining global extensions can beachieved via the routers static Router::extensions() method:

  1. Router::extensions(['json', 'xml']);
  2. // ...

This will affect all routes that are being connected afterwards, no mattertheir scope.

In order to restrict extensions to specific scopes, you can define them using theCake\Routing\RouteBuilder::setExtensions() method:

  1. Router::scope('/', function (RouteBuilder $routes) {
  2. $routes->setExtensions(['json', 'xml']);
  3. });

This will enable the named extensions for all routes that are being connected inthat scope after the setExtensions() call, including those that are beingconnected in nested scopes. Similar to the global Router::extensions()method, any routes connected prior to the call will not inherit the extensions.

Note

Setting the extensions should be the first thing you do in a scope, as theextensions will only be applied to routes connected after the extensionsare set.

Also be aware that re-opened scopes will not inherit extensions defined inpreviously opened scopes.

By using extensions, you tell the router to remove any matching file extensions,and then parse what remains. If you want to create a URL such as/page/title-of-page.html you would create your route using:

  1. Router::scope('/page', function (RouteBuilder $routes) {
  2. $routes->setExtensions(['json', 'xml', 'html']);
  3. $routes->connect(
  4. '/{title}',
  5. ['controller' => 'Pages', 'action' => 'view']
  6. )->setPass(['title']);
  7. });

Then to create links which map back to the routes simply use:

  1. $this->Html->link(
  2. 'Link title',
  3. ['controller' => 'Pages', 'action' => 'view', 'title' => 'super-article', '_ext' => 'html']
  4. );

File extensions are used by Request Handlingto do automatic view switching based on content types.

Connecting Scoped Middleware

While Middleware can be applied to your entire application, applying middlewareto specific routing scopes offers more flexibility, as you can apply middlewareonly where it is needed allowing your middleware to not concern itself withhow/where it is being applied.

Note

Applied scoped middleware will be run by RoutingMiddleware,normally at the end of your application’s middleware queue.

Before middleware can be applied to a scope, it needs to beregistered into the route collection:

  1. // in config/routes.php
  2. use Cake\Http\Middleware\CsrfProtectionMiddleware;
  3. use Cake\Http\Middleware\EncryptedCookieMiddleware;
  4.  
  5. Router::scope('/', function (RouteBuilder $routes) {
  6. $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
  7. $routes->registerMiddleware('cookies', new EncryptedCookieMiddleware());
  8. });

Once registered, scoped middleware can be applied to specificscopes:

  1. $routes->scope('/cms', function (RouteBuilder $routes) {
  2. // Enable CSRF & cookies middleware
  3. $routes->applyMiddleware('csrf', 'cookies');
  4. $routes->get('/articles/{action}/*', ['controller' => 'Articles'])
  5. });

In situations where you have nested scopes, inner scopes will inherit themiddleware applied in the containing scope:

  1. $routes->scope('/api', function (RouteBuilder $routes) {
  2. $routes->applyMiddleware('ratelimit', 'auth.api');
  3. $routes->scope('/v1', function (RouteBuilder $routes) {
  4. $routes->applyMiddleware('v1compat');
  5. // Define routes here.
  6. });
  7. });

In the above example, the routes defined in /v1 will have ‘ratelimit’,‘auth.api’, and ‘v1compat’ middleware applied. If you re-open a scope, themiddleware applied to routes in each scope will be isolated:

  1. $routes->scope('/blog', function (RouteBuilder $routes) {
  2. $routes->applyMiddleware('auth');
  3. // Connect the authenticated actions for the blog here.
  4. });
  5. $routes->scope('/blog', function (RouteBuilder $routes) {
  6. // Connect the public actions for the blog here.
  7. });

In the above example, the two uses of the /blog scope do not sharemiddleware. However, both of these scopes will inherit middleware defined intheir enclosing scopes.

Grouping Middleware

To help keep your route code DRY middleware canbe combined into groups. Once combined groups can be applied like middlewarecan:

  1. $routes->registerMiddleware('cookie', new EncryptedCookieMiddleware());
  2. $routes->registerMiddleware('auth', new AuthenticationMiddleware());
  3. $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
  4. $routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']);
  5.  
  6. // Apply the group
  7. $routes->applyMiddleware('web');

RESTful Routing

Router makes it easy to generate RESTful routes for your controllers. RESTfulroutes are helpful when you are creating API endpoints for your application. Ifwe wanted to allow REST access to a recipe controller, we’d do something likethis:

  1. // In config/routes.php...
  2.  
  3. Router::scope('/', function (RouteBuilder $routes) {
  4. $routes->setExtensions(['json']);
  5. $routes->resources('Recipes');
  6. });

The first line sets up a number of default routes for easy RESTaccess where method specifies the desired result format (e.g. xml,json, rss). These routes are HTTP Request Method sensitive.

HTTP formatURL.formatController action invoked
GET/recipes.formatRecipesController::index()
GET/recipes/123.formatRecipesController::view(123)
POST/recipes.formatRecipesController::add()
PUT/recipes/123.formatRecipesController::edit(123)
PATCH/recipes/123.formatRecipesController::edit(123)
DELETE/recipes/123.formatRecipesController::delete(123)

The HTTP method being used is detected from a few different sources.The sources in order of preference are:

  • The _method POST variable
  • The X_HTTP_METHOD_OVERRIDE header.
  • The REQUEST_METHOD headerThe _method POST variable is helpful in using a browser as aREST client (or anything else that can do POST). Just setthe value of _method to the name of the HTTP request method youwish to emulate.

Creating Nested Resource Routes

Once you have connected resources in a scope, you can connect routes forsub-resources as well. Sub-resource routes will be prepended by the originalresource name and a id parameter. For example:

  1. Router::scope('/api', function (RouteBuilder $routes) {
  2. $routes->resources('Articles', function (RouteBuilder $routes) {
  3. $routes->resources('Comments');
  4. });
  5. });

Will generate resource routes for both articles and comments. Thecomments routes will look like:

  1. /api/articles/{article_id}/comments
  2. /api/articles/{article_id}/comments/{id}

You can get the article_id in CommentsController by:

  1. $this->request->getParam('article_id');

By default resource routes map to the same prefix as the containing scope. Ifyou have both nested and non-nested resource controllers you can use a differentcontroller in each context by using prefixes:

  1. Router::scope('/api', function (RouteBuilder $routes) {
  2. $routes->resources('Articles', function (RouteBuilder $routes) {
  3. $routes->resources('Comments', ['prefix' => 'articles']);
  4. });
  5. });

The above would map the ‘Comments’ resource to theApp\Controller\Articles\CommentsController. Having separate controllers letsyou keep your controller logic simpler. The prefixes created this way arecompatible with Prefix Routing.

Note

While you can nest resources as deeply as you require, it is not recommendedto nest more than 2 resources together.

Limiting the Routes Created

By default CakePHP will connect 6 routes for each resource. If you’d like toonly connect specific resource routes you can use the only option:

  1. $routes->resources('Articles', [
  2. 'only' => ['index', 'view']
  3. ]);

Would create read only resource routes. The route names are create,update, view, index, and delete.

Changing the Controller Actions Used

You may need to change the controller action names that are used when connectingroutes. For example, if your edit() action is called put() you canuse the actions key to rename the actions used:

  1. $routes->resources('Articles', [
  2. 'actions' => ['update' => 'put', 'create' => 'add']
  3. ]);

The above would use put() for the edit() action, and add()instead of create().

Mapping Additional Resource Routes

You can map additional resource methods using the map option:

  1. $routes->resources('Articles', [
  2. 'map' => [
  3. 'deleteAll' => [
  4. 'action' => 'deleteAll',
  5. 'method' => 'DELETE'
  6. ]
  7. ]
  8. ]);
  9. // This would connect /articles/deleteAll

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. Youcan use the ‘path’ key inside the resource definition to customize the pathname:

  1. $routes->resources('Articles', [
  2. 'map' => [
  3. 'updateAll' => [
  4. 'action' => 'updateAll',
  5. 'method' => 'DELETE',
  6. 'path' => '/update-many'
  7. ],
  8. ]
  9. ]);
  10. // This would connect /articles/update-many

If you define ‘only’ and ‘map’, make sure that your mapped methods are also inthe ‘only’ list.

Custom Route Classes for Resource Routes

You can provide connectOptions key in the $options array forresources() to provide custom setting used by connect():

  1. Router::scope('/', function (RouteBuilder $routes) {
  2. $routes->resources('Books', [
  3. 'connectOptions' => [
  4. 'routeClass' => 'ApiRoute',
  5. ]
  6. ];
  7. });

URL Inflection for Resource Routes

By default, multi-worded controllers’ URL fragments are the underscoredform of the controller’s name. E.g., BlogPostsController’s URL fragmentwould be /blog-posts.

You can specify an alternative inflection type using the inflect option:

  1. Router::scope('/', function (RouteBuilder $routes) {
  2. $routes->resources('BlogPosts', [
  3. 'inflect' => 'underscore' // Will use ``Inflector::underscore()``
  4. ]);
  5. });

The above will generate URLs styled like: /blog-posts.

Changing the Path Element

By default resource routes use an inflected form of the resource name for theURL segment. You can set a custom URL segment with the path option:

  1. Router::scope('/', function (RouteBuilder $routes) {
  2. $routes->resources('BlogPosts', ['path' => 'posts']);
  3. });

Passed Arguments

Passed arguments are additional arguments or path segments that areused when making a request. They are often used to pass parametersto your controller methods.

  1. http://localhost/calendars/view/recent/mark

In the above example, both recent and mark are passed arguments toCalendarsController::view(). Passed arguments are given to your controllersin three ways. First as arguments to the action method called, and secondly theyare available in $this->request->getParam('pass') as a numerically indexedarray. When using custom routes you can force particular parameters to go intothe passed arguments as well.

If you were to visit the previously mentioned URL, and youhad a controller action that looked like:

  1. class CalendarsController extends AppController
  2. {
  3. public function view($arg1, $arg2)
  4. {
  5. debug(func_get_args());
  6. }
  7. }

You would get the following output:

  1. Array
  2. (
  3. [0] => recent
  4. [1] => mark
  5. )

This same data is also available at $this->request->getParam('pass') in yourcontrollers, views, and helpers. The values in the pass array are numericallyindexed based on the order they appear in the called URL:

  1. debug($this->request->getParam('pass'));

Either of the above would output:

  1. Array
  2. (
  3. [0] => recent
  4. [1] => mark
  5. )

When generating URLs, using a routing array you add passedarguments as values without string keys in the array:

  1. ['controller' => 'Articles', 'action' => 'view', 5]

Since 5 has a numeric key, it is treated as a passed argument.

Generating URLs

  • static Cake\Routing\Router::url($url = null, $full = false)

Generating URLs or Reverse routing is a feature in CakePHP that is used toallow you to change your URL structure without having to modify all yourcode. By using routing arrays to define your URLs, youcan later configure routes and the generated URLs will automatically update.

If you create URLs using strings like:

  1. $this->Html->link('View', '/articles/view/' . $id);

And then later decide that /articles should really be called‘posts’ instead, you would have to go through your entireapplication renaming URLs. However, if you defined your link like:

  1. $this->Html->link(
  2. 'View',
  3. ['controller' => 'Articles', 'action' => 'view', $id]
  4. );

Then when you decided to change your URLs, you could do so by defining aroute. This would change both the incoming URL mapping, as well as thegenerated URLs.

When using array URLs, you can define both query string parameters anddocument fragments using special keys:

  1. Router::url([
  2. 'controller' => 'Articles',
  3. 'action' => 'index',
  4. '?' => ['page' => 1],
  5. '#' => 'top'
  6. ]);
  7.  
  8. // Will generate a URL like.
  9. /articles/index?page=1#top

Router will also convert any unknown parameters in a routing array toquerystring parameters. The ? is offered for backwards compatibility witholder versions of CakePHP.

You can also use any of the special route elements when generating URLs:

  • _ext Used for Routing File Extensions routing.
  • _base Set to false to remove the base path from the generated URL. Ifyour application is not in the root directory, this can be used to generateURLs that are ‘cake relative’.
  • _scheme Set to create links on different schemes like webcal orftp. Defaults to the current scheme.
  • _host Set the host to use for the link. Defaults to the current host.
  • _port Set the port if you need to create links on non-standard ports.
  • _method Define the HTTP verb the URL is for.
  • _full If true the FULL_BASE_URL constant will be prepended togenerated URLs.
  • _ssl Set to true to convert the generated URL to https or falseto force http.
  • _name Name of route. If you have setup named routes, you can use this keyto specify it.

Generating Asset URLs

The Asset class provides methods for generating URLs to your application’scss, javascript, images and other static asset files:

  1. use Cake\Routing\Asset;
  2.  
  3. // Generate a URL to APP/webroot/js/app.js
  4. $js = Asset::scriptUrl('app.js');
  5.  
  6. // Generate a URL to APP/webroot/css/app.css
  7. $css = Asset::cssUrl('app.css');
  8.  
  9. // Generate a URL to APP/webroot/image/logo.png
  10. $img = Asset::imageUrl('logo.png');
  11.  
  12. // Generate a URL to APP/webroot/files/upload/photo.png
  13. $file = Asset::url('files/upload/photo.png');

The above methods also accept an array of options as their second parameter:

  • fullBase Append the full URL with domain name.
  • pathPrefix Path prefix for relative URLs.
  • plugin</code> You can provide <code>false to prevent paths from being treated asa plugin asset.
  • timestamp Overrides the value of Asset.timestamp in Configure. Set tofalse to skip timestamp generation. Set to true to apply timestampswhen debug is true. Set to 'force' to always enable timestampingregardless of debug value.
  1. // Generates http://example.org/img/logo.png
  2. $img = Asset::url('logo.png', ['fullBase' => true]);
  3.  
  4. // Generates /img/logo.png?1568563625
  5. // Where the timestamp is the last modified time of the file.
  6. $img = Asset::url('logo.png', ['timestamp' => true]);

To generate asset URLs for files in plugins use plugin syntax:

  1. // Generates `/debug_kit/img/cake.png`
  2. $img = Asset::imageUrl('DebugKit.cake.png');

Redirect Routing

Redirect routing allows you to issue HTTP status 30x redirects forincoming routes, and point them at different URLs. This is usefulwhen you want to inform client applications that a resource has movedand you don’t want to expose two URLs for the same content.

Redirection routes are different from normal routes as they perform an actualheader redirection if a match is found. The redirection can occur toa destination within your application or an outside location:

  1. Router::scope('/', function (RouteBuilder $routes) {
  2. $routes->redirect(
  3. '/home/*',
  4. ['controller' => 'Articles', 'action' => 'view'],
  5. ['persist' => true]
  6. // Or ['persist'=>['id']] for default routing where the
  7. // view action expects $id as an argument.
  8. );
  9. })

Redirects /home/* to /articles/view and passes the parameters to/articles/view. Using an array as the redirect destination allowsyou to use other routes to define where a URL string should beredirected to. You can redirect to external locations usingstring URLs as the destination:

  1. Router::scope('/', function (RouteBuilder $routes) {
  2. $routes->redirect('/articles/*', 'http://google.com', ['status' => 302]);
  3. });

This would redirect /articles/* to http://google.com with aHTTP status of 302.

Entity Routing

Entity routing allows you to use an entity, an array or object implementArrayAccess as the source of routing parameters. This allows you to refactorroutes more easily, and generate URLs with less code. For example, if you startoff with a route that looks like:

  1. $routes->get(
  2. '/view/{id}',
  3. ['controller' => 'Articles', 'action' => 'view'],
  4. 'articles:view'
  5. );

You can generate URLs to this route using:

  1. // $article is an entity in the local scope.
  2. Router::url(['_name' => 'articles:view', 'id' => $article->id]);

Later on, you may want to expose the article slug in the URL for SEO purposes.In order to do this you would need to update everywhere you generate a URL tothe articles:view route, which could take some time. If we use entity routeswe pass the entire article entity into URL generation allowing us to skip anyrework when URLs require more parameters:

  1. use Cake\Routing\Route\EntityRoute;
  2.  
  3. // Create entity routes for the rest of this scope.
  4. $routes->setRouteClass(EntityRoute::class);
  5.  
  6. // Create the route just like before.
  7. $routes->get(
  8. '/view/{id}',
  9. ['controller' => 'Articles', 'action' => 'view'],
  10. 'articles:view'
  11. );

Now we can generate URLs using the _entity key:

  1. Router::url(['_name' => 'articles:view', '_entity' => $article]);

This will extract both the id property and the slug property out of theprovided entity.

Custom Route Classes

Custom route classes allow you to extend and change how individual routes parserequests and handle reverse routing. Route classes have a few conventions:

  • Route classes are expected to be found in the Routing\Route namespace ofyour application or plugin.
  • Route classes should extend Cake\Routing\Route.
  • Route classes should implement one or both of match() and/or parse().

The parse() method is used to parse an incoming URL. It should generate anarray of request parameters that can be resolved into a controller & action.Return false from this method to indicate a match failure.

The match() method is used to match an array of URL parameters and create astring URL. If the URL parameters do not match the route false should bereturned.

You can use a custom route class when making a route by using the routeClassoption:

  1. $routes->connect(
  2. '/{slug}',
  3. ['controller' => 'Articles', 'action' => 'view'],
  4. ['routeClass' => 'SlugRoute']
  5. );
  6.  
  7. // Or by setting the routeClass in your scope.
  8. $routes->scope('/', function (RouteBuilder $routes) {
  9. $routes->setRouteClass('SlugRoute');
  10. $routes->connect(
  11. '/{slug}',
  12. ['controller' => 'Articles', 'action' => 'view']
  13. );
  14. });

This route would create an instance of SlugRoute and allow youto implement custom parameter handling. You can use plugin route classes usingstandard plugin syntax.

Default Route Class

  • static Cake\Routing\Router::defaultRouteClass($routeClass = null)

If you want to use an alternate route class for all your routes besides thedefault Route, you can do so by calling Router::defaultRouteClass()before setting up any routes and avoid having to specify the routeClassoption for each route. For example using:

  1. use Cake\Routing\Route\InflectedRoute;
  2.  
  3. Router::defaultRouteClass(InflectedRoute::class);

will cause all routes connected after this to use the InflectedRoute route class.Calling the method without an argument will return current default route class.

Fallbacks Method

  • Cake\Routing\Router::fallbacks($routeClass = null)

The fallbacks method is a simple shortcut for defining default routes. Themethod uses the passed routing class for the defined rules or if no class isprovided the class returned by Router::defaultRouteClass() is used.

Calling fallbacks like so:

  1. use Cake\Routing\Route\DashedRoute;
  2.  
  3. $routes->fallbacks(DashedRoute::class);

Is equivalent to the following explicit calls:

  1. use Cake\Routing\Route\DashedRoute;
  2.  
  3. $routes->connect('/{controller}', ['action' => 'index'], ['routeClass' => DashedRoute::class]);
  4. $routes->connect('/{controller}/{action}/*', [], ['routeClass' => DashedRoute::class]);

Note

Using the default route class (Route) with fallbacks, or any routewith {plugin} and/or {controller} route elements will result ininconsistent URL case.

Creating Persistent URL Parameters

You can hook into the URL generation process using URL filter functions. Filterfunctions are called before the URLs are matched against the routes, thisallows you to prepare URLs before routing.

Callback filter functions should expect the following parameters:

  • $params The URL parameter array being processed.
  • $request The current request (Cake\Http\ServerRequest instance).

The URL filter function should always return the parameters even if unmodified.

URL filters allow you to implement features like persistent parameters:

  1. Router::addUrlFilter(function (array $params, ServerRequest $request) {
  2. if ($request->getParam('lang') && !isset($params['lang'])) {
  3. $params['lang'] = $request->getParam('lang');
  4. }
  5. return $params;
  6. });

Filter functions are applied in the order they are connected.

Another use case is changing a certain route on runtime (plugin routes forexample):

  1. Router::addUrlFilter(function (array $params, ServerRequest $request) {
  2. if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params['controller'])) {
  3. return $params;
  4. }
  5. if ($params['controller'] === 'Languages' && $params['action'] === 'view') {
  6. $params['controller'] = 'Locations';
  7. $params['action'] = 'index';
  8. $params['language'] = $params[0];
  9. unset($params[0]);
  10. }
  11. return $params;
  12. });

This will alter the following route:

  1. Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view', 'es']);

into this:

  1. Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' => 'index', 'language' => 'es']);

Warning

If you are using the caching features of Routing Middleware you mustdefine the URL filters in your application bootstrap() as filters arenot part of the cached data.