路由

class Cake\Routing\RouterBuilder

路由为您提供将 URL 映射到控制器操作的工具。通过定义路由,您可以将应用程序的实现方式与 URL 的结构方式分开。

CakePHP 中的路由还包含反向路由的概念,其中参数数组可以转换为 URL 字符串。通过使用反向路由,您可以重构应用程序的 URL 结构,而无需更新所有代码。

快速浏览

本节将通过示例向您介绍 CakePHP 路由最常见的用途。通常您希望显示某个内容作为登录页面,因此您将其添加到 config/routes.php 文件中

/** @var \Cake\Routing\RouteBuilder $routes */
$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);

当访问您网站的首页时,这将执行 ArticlesController 中的 index 方法。有时您需要接受多个参数的动态路由,例如查看文章内容的路由

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

上面的路由将接受任何看起来像 /articles/15 的 URL 并调用 ArticlesController 中的 view(15) 方法。但是,这并不能阻止人们尝试访问看起来像 /articles/foobar 的 URL。如果您愿意,可以限制某些参数以符合正则表达式

// Using fluent interface
$routes->connect(
    '/articles/{id}',
    ['controller' => 'Articles', 'action' => 'view'],
)
->setPatterns(['id' => '\d+'])
->setPass(['id']);

// Using options array
$routes->connect(
    '/articles/{id}',
    ['controller' => 'Articles', 'action' => 'view'],
    ['id' => '\d+', 'pass' => ['id']]
);

前面的示例将星号匹配器更改为一个新的占位符 {id}。使用占位符允许我们验证 URL 的部分,在本例中我们使用了 \d+ 正则表达式,以便仅匹配数字。最后,我们通过指定 pass 选项告诉路由器将 id 占位符视为 view() 函数的函数参数。稍后将详细介绍如何使用此选项。

CakePHP 路由器还可以反向匹配路由。这意味着它能够从包含匹配参数的数组中生成 URL 字符串

use Cake\Routing\Router;

echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]);
// Will output
/articles/15

路由也可以用唯一的名称标记,这使您能够在构建链接时快速引用它们,而不是指定每个路由参数

// In routes.php
$routes->connect(
    '/upgrade',
    ['controller' => 'Subscriptions', 'action' => 'create'],
    ['_name' => 'upgrade']
);

use Cake\Routing\Router;

echo Router::url(['_name' => 'upgrade']);
// Will output
/upgrade

为了帮助使您的路由代码保持 DRY,路由器具有“范围”的概念。范围定义一个公共路径段,以及可选的路由默认值。在范围内连接的任何路由都将继承其包装范围的路径/默认值

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

上面的路由将匹配 /blog/ 并将其发送到 Blog\Controller\ArticlesController::index()

应用程序框架附带了一些路由,以帮助您入门。在添加了自己的路由后,如果不需要默认路由,可以将其删除。

连接路由

为了使您的代码保持 DRY,您应该使用“路由范围”。路由范围不仅可以让您保持代码 DRY,而且还有助于路由器优化其操作。此方法默认为 / 范围。要创建范围并连接一些路由,我们将使用 scope() 方法

// In config/routes.php
use Cake\Routing\RouteBuilder;
use Cake\Routing\Route\DashedRoute;

$routes->scope('/', function (RouteBuilder $routes) {
    // Connect the generic fallback routes.
    $routes->fallbacks(DashedRoute::class);
});

connect() 方法最多接受三个参数:您要匹配的 URL 模板、路由元素的默认值以及路由的选项。选项通常包括正则表达式规则,以帮助路由器匹配 URL 中的元素。

路由定义的基本格式为

$routes->connect(
    '/url/template',
    ['targetKey' => 'targetValue'],
    ['option' => 'matchingRegex']
);

第一个参数用于告诉路由器您要控制什么类型的 URL。URL 是一个普通的斜杠分隔字符串,但也可以包含通配符 (*) 或 路由元素。使用通配符告诉路由器您愿意接受提供的任何其他参数。没有 * 的路由仅匹配提供的精确模板模式。

指定 URL 后,您可以使用 connect() 的最后两个参数告诉 CakePHP 在匹配请求后该怎么做。第二个参数定义路由“目标”。这可以通过数组或目标字符串定义。以下是路由目标的几个示例

// Array target to an application controller
$routes->connect(
    '/users/view/*',
    ['controller' => 'Users', 'action' => 'view']
);
$routes->connect('/users/view/*', 'Users::view');

// Array target to a prefixed plugin controller
$routes->connect(
    '/admin/cms/articles',
    ['prefix' => 'Admin', 'plugin' => 'Cms', 'controller' => 'Articles', 'action' => 'index']
);
$routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index');

我们连接的第一个路由匹配以 /users/view 开头的 URL,并将这些请求映射到 UsersController->view()。后面的 /* 告诉路由器将任何其他段作为方法参数传递。例如,/users/view/123 将映射到 UsersController->view(123)

上面的示例还说明了字符串目标。字符串目标提供了一种紧凑的方式来定义路由的目标。字符串目标具有以下语法

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

以下是一些字符串目标示例

// Application controller
'Articles::view'

// Application controller with prefix
Admin/Articles::view

// Plugin controller
Cms.Articles::edit

// Prefixed plugin controller
Vendor/Cms.Management/Admin/Articles::view

之前我们使用贪婪星号 (/*) 来捕获其他路径段,还有尾随星号 (/**)。使用尾随双星号将捕获 URL 的其余部分作为单个传递参数。当您要使用包含 / 的参数时,这很有用

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

传入的 URL /pages/the-example-/-and-proof 将导致单个传递参数为 the-example-/-and-proof

connect() 的第二个参数可以定义构成默认路由参数的任何参数

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

此示例使用 connect() 的第二个参数来定义默认参数。如果您构建了一个为不同客户类别提供产品的应用程序,您可能会考虑创建一条路由。这使您可以链接到 /government 而不是 /pages/display/5

路由的一个常见用途是重命名控制器及其操作。我们不想在 /users/some-action/5 处访问我们的用户控制器,而是希望能够通过 /cooks/some-action/5 访问它。以下路由负责处理这个问题

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

这告诉路由器,任何以 /cooks/ 开头的 URL 都应该发送到 UsersController。调用的操作将取决于 {action} 参数的值。通过使用 路由元素,您可以创建可接受用户输入或变量的变量路由。上面的路由也使用了贪婪星号。贪婪星号表示此路由应该接受给定的任何其他位置参数。这些参数将在 传递参数 数组中可用。

在生成 URL 时,也会使用路由。使用 ['controller' => 'Users', 'action' => 'some-action', 5] 作为 URL 将输出 /cooks/some-action/5,如果上面的路由是找到的第一个匹配项。

到目前为止,我们连接的路由将匹配任何 HTTP 动词。如果您正在构建 REST API,您通常希望将 HTTP 操作映射到不同的控制器方法。 RouteBuilder 提供辅助方法,使为特定 HTTP 动词定义路由变得更简单。

// Create a route that only responds to GET requests.
$routes->get(
    '/cooks/{id}',
    ['controller' => 'Users', 'action' => 'view'],
    'users:view'
);

// Create a route that only responds to PUT requests
$routes->put(
    '/cooks/{id}',
    ['controller' => 'Users', 'action' => 'update'],
    'users:update'
);

上面的路由根据使用的 HTTP 动词将相同的 URL 映射到不同的控制器操作。GET 请求将转到 'view' 操作,而 PUT 请求将转到 'update' 操作。HTTP 辅助方法包括:

  • GET

  • POST

  • PUT

  • PATCH

  • DELETE

  • OPTIONS

  • HEAD

所有这些方法都返回路由实例,允许您利用 流畅设置器 来进一步配置您的路由。

路由元素

您可以指定自己的路由元素,这样做使您能够在 URL 中定义控制器操作参数应该所在的位置。当发出请求时,这些路由元素的值将在控制器中的 $this->request->getParam() 中找到。当您定义自定义路由元素时,您可以选择指定正则表达式——这告诉 CakePHP 如何知道 URL 是否格式正确。如果您选择不提供正则表达式,任何非 / 字符将被视为参数的一部分。

$routes->connect(
    '/{controller}/{id}',
    ['action' => 'view']
)->setPatterns(['id' => '[0-9]+']);

$routes->connect(
    '/{controller}/{id}',
    ['action' => 'view'],
    ['id' => '[0-9]+']
);

上面的示例说明了如何通过制作类似于 /controllername/{id} 的 URL 来快速查看任何控制器的模型。提供给 connect() 的 URL 指定了两个路由元素:{controller}{id}{controller} 元素是 CakePHP 默认路由元素,因此路由器知道如何在 URL 中匹配和识别控制器名称。 {id} 元素是一个自定义路由元素,必须通过在 connect() 的第三个参数中指定匹配正则表达式来进一步澄清。

CakePHP 在使用 {controller} 参数时不会自动生成小写和破折号 URL。如果您需要这样做,上面的示例可以改写成这样:

use Cake\Routing\Route\DashedRoute;

// Create a builder with a different route class.
$routes->scope('/', function (RouteBuilder $routes) {
    $routes->setRouteClass(DashedRoute::class);
    $routes->connect('/{controller}/{id}', ['action' => 'view'])
        ->setPatterns(['id' => '[0-9]+']);

    $routes->connect(
        '/{controller}/{id}',
        ['action' => 'view'],
        ['id' => '[0-9]+']
    );
});

DashedRoute 类将确保 {controller}{plugin} 参数被正确地小写化和破折号化。

注意

用于路由元素的模式不能包含任何捕获组。如果有,路由器将无法正常工作。

定义此路由后,请求 /apples/5 将调用 ApplesController 的 view() 方法。在 view() 方法中,您需要访问 $this->request->getParam('id') 中的传递 ID。

如果您在应用程序中只有一个控制器,并且您不希望控制器名称出现在 URL 中,您可以将所有 URL 映射到您控制器中的操作。例如,要将所有 URL 映射到 home 控制器的操作,例如拥有像 /demo 这样的 URL 而不是 /home/demo,您可以执行以下操作:

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

如果您想提供一个不区分大小写的 URL,您可以使用正则表达式内联修饰符。

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

再举一个例子,您将成为路由专家。

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

这相当复杂,但它展示了路由的强大功能。提供的 URL 有四个路由元素。第一个我们很熟悉:它是一个默认路由元素,告诉 CakePHP 期望一个控制器名称。

接下来,我们指定一些默认值。无论控制器是什么,我们都希望调用 index() 操作。

最后,我们指定了一些正则表达式,这些正则表达式将以数字形式匹配年份、月份和日期。请注意,圆括号(捕获组)在正则表达式中不受支持。您仍然可以指定备选方案,如上所示,但不能用圆括号分组。

定义后,此路由将匹配 /articles/2007/02/01/articles/2004/11/16,将请求传递给各自控制器的 index() 操作,日期参数在 $this->request->getParam() 中。

保留的路由元素

CakePHP 中有一些具有特殊含义的路由元素,除非您想要特殊的含义,否则不应使用它们。

  • controller 用于命名路由的控制器。

  • action 用于命名路由的控制器操作。

  • plugin 用于命名控制器所在的插件。

  • prefix 用于 前缀路由

  • _ext 用于 文件扩展名路由

  • _base 设置为 false 以从生成的 URL 中删除基本路径。如果您的应用程序不在根目录中,这可用于生成“Cake 相对”的 URL。

  • _scheme 设置为在不同的方案(如 webcalftp)上创建链接。默认为当前方案。

  • _host 设置要用于链接的主机。默认为当前主机。

  • _port 如果您需要在非标准端口上创建链接,请设置端口。

  • _full 如果为 true,则会在生成的 URL 前面加上 常规配置 中提到的 App.fullBaseUrl 的值。

  • # 允许您设置 URL 哈希片段。

  • _https 设置为 true 以将生成的 URL 转换为 https,或 false 以强制使用 http。在 4.5.0 之前,使用 _ssl

  • _method 定义要使用的 HTTP 动词/方法。在使用 RESTful 路由 时很有用。

  • _name 路由的名称。如果您已经设置了命名路由,您可以使用此键来指定它。

配置路由选项

每个路由都可以设置许多路由选项。连接路由后,您可以使用其流畅的构建器方法来进一步配置路由。这些方法替换了 connect()$options 参数中的许多键。

$routes->connect(
    '/{lang}/articles/{slug}',
    ['controller' => 'Articles', 'action' => 'view'],
)
// Allow GET and POST requests.
->setMethods(['GET', 'POST'])

// Only match on the blog subdomain.
->setHost('blog.example.com')

// Set the route elements that should be converted to passed arguments
->setPass(['slug'])

// Set the matching patterns for route elements
->setPatterns([
    'slug' => '[a-z0-9-_]+',
    'lang' => 'en|fr|es',
])

// Also allow JSON file extensions
->setExtensions(['json'])

// Set lang to be a persistent parameter
->setPersist(['lang']);

将参数传递给操作

在使用 路由元素 连接路由时,您可能希望将路由元素作为参数传递。 pass 选项指示哪些路由元素也应该作为传递到控制器函数的参数提供。

// src/Controller/BlogsController.php
public function view($articleId = null, $slug = null)
{
    // Some code here...
}

// routes.php
$routes->scope('/', function (RouteBuilder $routes) {
    $routes->connect(
        '/blog/{id}-{slug}', // For example, /blog/3-CakePHP_Rocks
        ['controller' => 'Blogs', 'action' => 'view']
    )
    // Define the route elements in the route template
    // to prepend as function arguments. Order matters as this
    // will pass the `$id` and `$slug` elements as the first and
    // second parameters. Any additional passed parameters in your
    // route will be added after the setPass() arguments.
    ->setPass(['id', 'slug'])
    // Define a pattern that `id` must match.
    ->setPatterns([
        'id' => '[0-9]+',
    ]);
});

现在由于反向路由功能,您可以像下面这样传入 URL 数组,CakePHP 将知道如何根据路由中定义的格式形成 URL。

// view.php
// This will return a link to /blog/3-CakePHP_Rocks
echo $this->Html->link('CakePHP Rocks', [
    'controller' => 'Blog',
    'action' => 'view',
    'id' => 3,
    'slug' => 'CakePHP_Rocks'
]);

// You can also used numerically indexed parameters.
echo $this->Html->link('CakePHP Rocks', [
    'controller' => 'Blog',
    'action' => 'view',
    3,
    'CakePHP_Rocks'
]);

使用路径路由

我们上面讨论了字符串目标。同样适用于使用 Router::pathUrl() 进行 URL 生成。

echo Router::pathUrl('Articles::index');
// outputs: /articles

echo Router::pathUrl('MyBackend.Admin/Articles::view', [3]);
// outputs: /admin/my-backend/articles/view/3

提示

可以使用 CakePHP IdeHelper 插件 启用路径路由自动完成的 IDE 支持。

使用命名路由

有时您会发现为路由键入所有 URL 参数太冗长,或者您想利用命名路由带来的性能改进。连接路由时,您可以指定 _name 选项,此选项可以在反向路由中使用来识别要使用的路由。

// Connect a route with a name.
$routes->connect(
    '/login',
    ['controller' => 'Users', 'action' => 'login'],
    ['_name' => 'login']
);

// Name a verb specific route
$routes->post(
    '/logout',
    ['controller' => 'Users', 'action' => 'logout'],
    'logout'
);

// Generate a URL using a named route.
$url = Router::url(['_name' => 'logout']);

// Generate a URL using a named route,
// with some query string args.
$url = Router::url(['_name' => 'login', 'username' => 'jimmy']);

如果您的路由模板包含任何路由元素,如 {controller},您需要将它们作为 Router::url() 选项的一部分提供。

注意

路由名称在整个应用程序中必须是唯一的。相同的 _name 不能使用两次,即使名称出现在不同的路由范围内。

在构建命名路由时,您可能希望坚持使用一些路由名称的约定。CakePHP 通过允许您在每个范围内定义名称前缀来简化路由名称的构建过程。

$routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) {
    // This route's name will be `api:ping`
    $routes->get('/ping', ['controller' => 'Pings'], 'ping');
});
// Generate a URL for the ping route
Router::url(['_name' => 'api:ping']);

// Use namePrefix with plugin()
$routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) {
    // Connect routes.
});

// Or with prefix()
$routes->prefix('Admin', ['_namePrefix' => 'admin:'], function (RouteBuilder $routes) {
    // Connect routes.
});

您也可以在嵌套作用域中使用 _namePrefix 选项,它按预期工作。

$routes->plugin('Contacts', ['_namePrefix' => 'contacts:'], function (RouteBuilder $routes) {
    $routes->scope('/api', ['_namePrefix' => 'api:'], function (RouteBuilder $routes) {
        // This route's name will be `contacts:api:ping`
        $routes->get('/ping', ['controller' => 'Pings'], 'ping');
    });
});

// Generate a URL for the ping route
Router::url(['_name' => 'contacts:api:ping']);

在命名作用域中连接的路由只有在路由也命名的情况下才会添加名称。无名路由不会应用 _namePrefix

前缀路由

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

许多应用程序需要一个管理部分,让特权用户可以进行更改。这通常通过特殊的 URL 完成,例如 /admin/users/edit/5。在 CakePHP 中,可以通过使用 prefix 作用域方法启用前缀路由。

use Cake\Routing\Route\DashedRoute;

$routes->prefix('Admin', function (RouteBuilder $routes) {
    // All routes here will be prefixed with `/admin`, and
    // have the `'prefix' => 'Admin'` route element added that
    // will be required when generating URLs for these routes
    $routes->fallbacks(DashedRoute::class);
});

前缀映射到应用程序 Controller 命名空间中的子命名空间。通过将前缀作为单独的控制器,您可以创建更小、更简单的控制器。对带前缀和不带前缀的控制器通用的行为可以使用继承、组件 或特征来封装。使用我们的用户示例,访问 URL /admin/users/edit/5 将调用我们 src/Controller/Admin/UsersController.php 中的 edit() 方法,并将 5 作为第一个参数传递。使用的视图文件将是 templates/Admin/Users/edit.php

您可以将 URL /admin 映射到页面控制器的 index() 操作,使用以下路由

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

在创建前缀路由时,您可以使用 $options 参数设置额外的路由参数。

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

注意,额外的路由参数将添加到前缀块中定义的所有连接路由。您需要在以后构建路由时使用 url 数组中的所有参数,如果未使用,您将收到 MissingRouteException 错误。

默认情况下,多词前缀使用破折号化变位词转换,例如 MyPrefix 将映射到 URL 中的 my-prefix。如果要使用不同的格式(例如下划线),请确保为这样的前缀设置路径。

$routes->prefix('MyPrefix', ['path' => '/my_prefix'], function (RouteBuilder $routes) {
    // Routes connected here are prefixed with '/my_prefix'
    $routes->connect('/{controller}');
});

您也可以在插件作用域中定义前缀。

$routes->plugin('DebugKit', function (RouteBuilder $routes) {
    $routes->prefix('Admin', function (RouteBuilder $routes) {
        $routes->connect('/{controller}');
    });
});

以上将创建一个类似于 /debug-kit/admin/{controller} 的路由模板。连接的路由将具有已设置的 pluginprefix 路由元素。

在定义前缀时,如果需要,您可以嵌套多个前缀。

$routes->prefix('Manager', function (RouteBuilder $routes) {
    $routes->prefix('Admin', function (RouteBuilder $routes) {
        $routes->connect('/{controller}/{action}');
    });
});

以上将创建一个类似于 /manager/admin/{controller}/{action} 的路由模板。连接的路由将具有设置为 Manager/Adminprefix 路由元素。

当前前缀可以通过控制器方法中的 $this->request->getParam('prefix') 获得。

在使用前缀路由时,设置 prefix 选项非常重要,并且使用与 prefix() 方法中使用的相同的驼峰格式。以下是使用 HTML 帮助程序构建此链接的方法

// Go into a prefixed route.
echo $this->Html->link(
    'Manage articles',
    ['prefix' => 'Manager/Admin', 'controller' => 'Articles', 'action' => 'add']
);

// Leave a prefix
echo $this->Html->link(
    'View Post',
    ['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5]
);

插件路由

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

用于 插件 的路由应使用 plugin() 方法创建。此方法为插件的路由创建一个新的路由作用域。

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

在创建插件作用域时,可以使用 path 选项自定义使用的路径元素。

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

在使用作用域时,您可以在前缀作用域中嵌套插件作用域。

$routes->prefix('Admin', function (RouteBuilder $routes) {
    $routes->plugin('DebugKit', function (RouteBuilder $routes) {
        $routes->connect('/{controller}');
    });
});

以上将创建一个类似于 /admin/debug-kit/{controller} 的路由。它将具有已设置的 prefixplugin 路由元素。插件路由 部分包含有关构建插件路由的更多信息。

SEO 友好路由

一些开发人员更喜欢在 URL 中使用破折号,因为它被认为可以获得更好的搜索引擎排名。DashedRoute 类可以在您的应用程序中使用,可以将插件、控制器和驼峰式操作名称路由到破折号 URL。

例如,如果我们有一个名为 ToDo 的插件,它有一个 TodoItems 控制器和一个 showItems() 操作,可以使用以下路由连接在 /to-do/todo-items/show-items 处访问它。

use Cake\Routing\Route\DashedRoute;

$routes->plugin('ToDo', ['path' => 'to-do'], function (RouteBuilder $routes) {
    $routes->fallbacks(DashedRoute::class);
});

匹配特定 HTTP 方法

路由可以使用 HTTP 动词帮助程序方法匹配特定 HTTP 方法。

$routes->scope('/', function (RouteBuilder $routes) {
    // This route only matches on POST requests.
    $routes->post(
        '/reviews/start',
        ['controller' => 'Reviews', 'action' => 'start']
    );

    // Match multiple verbs
    $routes->connect(
        '/reviews/start',
        [
            'controller' => 'Reviews',
            'action' => 'start',
        ]
    )->setMethods(['POST', 'PUT']);
});

您可以使用数组匹配多个 HTTP 方法。由于 _method 参数是路由键,因此它参与 URL 解析和 URL 生成。要为特定于方法的路由生成 URL,您需要在生成 URL 时包含 _method 键。

$url = Router::url([
    'controller' => 'Reviews',
    'action' => 'start',
    '_method' => 'POST',
]);

匹配特定主机名

路由可以使用 _host 选项仅匹配特定主机。您可以使用 *. 通配符匹配任何子域。

$routes->scope('/', function (RouteBuilder $routes) {
    // This route only matches on http://images.example.com
    $routes->connect(
        '/images/default-logo.png',
        ['controller' => 'Images', 'action' => 'default']
    )->setHost('images.example.com');

    // This route only matches on http://*.example.com
    $routes->connect(
        '/images/old-logo.png',
        ['controller' => 'Images', 'action' => 'oldLogo']
    )->setHost('*.example.com');
});

在 URL 生成中也使用 _host 选项。如果您的 _host 选项指定了确切的域,则该域将包含在生成的 URL 中。但是,如果您使用通配符,则需要在生成 URL 时提供 _host 参数。

// If you have this route
$routes->connect(
    '/images/old-logo.png',
    ['controller' => 'Images', 'action' => 'oldLogo']
)->setHost('images.example.com');

// You need this to generate a url
echo Router::url([
    'controller' => 'Images',
    'action' => 'oldLogo',
    '_host' => 'images.example.com',
]);

路由文件扩展名

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

要处理 URL 中的不同文件扩展名,可以使用 Cake\Routing\RouteBuilder::setExtensions() 方法定义扩展名。

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->setExtensions(['json', 'xml']);
});

这将为在该作用域中 之后 setExtensions() 调用连接的所有路由(包括在嵌套作用域中连接的路由)启用命名扩展名。

注意

设置扩展名应该是您在作用域中做的第一件事,因为扩展名仅应用于 之后 设置扩展名连接的路由。

还要注意,重新打开的作用域 不会 继承先前打开的作用域中定义的扩展名。

通过使用扩展,您可以告诉路由器从 URL 中删除所有匹配的文件扩展名,然后解析剩余部分。如果您想创建一个类似于 /page/title-of-page.html 的 URL,您可以使用以下路由创建:

$routes->scope('/page', function (RouteBuilder $routes) {
    $routes->setExtensions(['json', 'xml', 'html']);
    $routes->connect(
        '/{title}',
        ['controller' => 'Pages', 'action' => 'view']
    )->setPass(['title']);
});

然后,要创建映射回路由的链接,只需使用

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

路由范围的中间件

虽然中间件可以应用于整个应用程序,但将中间件应用于特定路由范围提供了更大的灵活性,因为您只需在需要的地方应用中间件,从而使您的中间件无需关注其应用方式/位置。

注意

应用范围的中间件将由 RoutingMiddleware 运行,通常在应用程序中间件队列的末尾运行。

在将中间件应用于范围之前,需要将其注册到路由集合中

// in config/routes.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Http\Middleware\EncryptedCookieMiddleware;

$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
$routes->registerMiddleware('cookies', new EncryptedCookieMiddleware());

注册后,范围中间件可以应用于特定范围

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

在您有嵌套范围的情况下,内部范围将继承包含范围中应用的中间件

$routes->scope('/api', function (RouteBuilder $routes) {
    $routes->applyMiddleware('ratelimit', 'auth.api');
    $routes->scope('/v1', function (RouteBuilder $routes) {
        $routes->applyMiddleware('v1compat');
        // Define routes here.
    });
});

在上面的示例中,在 /v1 中定义的路由将应用“ratelimit”、“auth.api”和“v1compat”中间件。如果您重新打开一个范围,则应用于每个范围中路由的中间件将被隔离

$routes->scope('/blog', function (RouteBuilder $routes) {
    $routes->applyMiddleware('auth');
    // Connect the authenticated actions for the blog here.
});
$routes->scope('/blog', function (RouteBuilder $routes) {
    // Connect the public actions for the blog here.
});

在上面的示例中,/blog 范围的两种使用方式不共享中间件。但是,这两个范围都将继承其封闭范围中定义的中间件。

中间件分组

为了帮助保持您的路由代码 DRY,中间件可以组合成组。组合后的组可以像中间件一样应用

$routes->registerMiddleware('cookie', new EncryptedCookieMiddleware());
$routes->registerMiddleware('auth', new AuthenticationMiddleware());
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
$routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']);

// Apply the group
$routes->applyMiddleware('web');

RESTful 路由

Router 帮助为您的控制器生成 RESTful 路由。RESTful 路由在您为应用程序创建 API 端点时很有用。如果我们想允许 REST 访问食谱控制器,我们会做类似的事情

// In config/routes.php...

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->setExtensions(['json']);
    $routes->resources('Recipes');
});

第一行设置了一些用于 REST 访问的默认路由,其中 method 指定了所需的结果格式,例如 xml、json 和 rss。这些路由对 HTTP 请求方法敏感。

HTTP 格式

URL.format

调用的控制器操作

GET

/recipes.format

RecipesController::index()

GET

/recipes/123.format

RecipesController::view(123)

POST

/recipes.format

RecipesController::add()

PUT

/recipes/123.format

RecipesController::edit(123)

PATCH

/recipes/123.format

RecipesController::edit(123)

DELETE

/recipes/123.format

RecipesController::delete(123)

注意

资源 ID 的默认模式只匹配整数或 UUID。如果您的 ID 不同,您需要通过 id 选项提供正则表达式模式,例如,$builder->resources('Recipes', ['id' => '.*'])

使用的 HTTP 方法是从几个不同的来源检测到的。这些来源按优先级排序如下:

  1. _method POST 变量

  2. X_HTTP_METHOD_OVERRIDE 标头。

  3. REQUEST_METHOD 标头

_method POST 变量在使用浏览器作为 REST 客户端(或任何其他可以执行 POST 的东西)时很有用。只需将 _method 的值设置为要模拟的 HTTP 请求方法的名称即可。

创建嵌套资源路由

在您将资源连接到一个范围内后,您还可以连接子资源的路由。子资源路由将在原始资源名称和 id 参数之前加上前缀。例如

$routes->scope('/api', function (RouteBuilder $routes) {
    $routes->resources('Articles', function (RouteBuilder $routes) {
        $routes->resources('Comments');
    });
});

将为 articlescomments 生成资源路由。comments 路由将如下所示

/api/articles/{article_id}/comments
/api/articles/{article_id}/comments/{id}

您可以在 CommentsController 中通过以下方式获取 article_id

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

默认情况下,资源路由映射到与包含范围相同的名称。如果您有嵌套和非嵌套资源控制器,您可以通过使用前缀在每个上下文中使用不同的控制器

$routes->scope('/api', function (RouteBuilder $routes) {
    $routes->resources('Articles', function (RouteBuilder $routes) {
        $routes->resources('Comments', ['prefix' => 'Articles']);
    });
});

以上将把‘Comments’ 资源映射到 App\Controller\Articles\CommentsController。拥有单独的控制器可以使您的控制器逻辑更简单。以这种方式创建的前缀与 Prefix Routing 兼容。

注意

虽然您可以根据需要深度嵌套资源,但不建议嵌套超过 2 个资源。

限制创建的路由

默认情况下,CakePHP 将为每个资源连接 6 个路由。如果您只想连接特定的资源路由,可以使用 only 选项

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

将创建只读资源路由。路由名称为 createupdateviewindexdelete

默认情况下,使用的路由名称和控制器操作如下所示

路由名称

使用的控制器操作

create

add

update

edit

view

view

index

index

delete

delete

更改使用的控制器操作

您可能需要更改连接路由时使用的控制器操作名称。例如,如果您的 edit() 操作称为 put(),您可以使用 actions 键重命名使用的操作

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

以上将使用 put() 用于 edit() 操作,以及 add() 而不是 create()

映射其他资源路由

您可以使用 map 选项映射其他资源方法

$routes->resources('Articles', [
   'map' => [
       'deleteAll' => [
           'action' => 'deleteAll',
           'method' => 'DELETE'
       ]
   ]
]);
// This would connect /articles/deleteAll

除了默认路由之外,这还会连接到 /articles/delete-all 的路由。默认情况下,路径段将匹配键名。您可以在资源定义中使用‘path’键来定制路径名称

$routes->resources('Articles', [
    'map' => [
        'updateAll' => [
            'action' => 'updateAll',
            'method' => 'PUT',
            'path' => '/update-many',
        ],
    ],
]);
// This would connect /articles/update-many

如果您定义了‘only’和‘map’,请确保您的映射方法也包含在‘only’列表中。

带前缀的资源路由

资源路由可以通过在带前缀的范围内连接路由或使用 prefix 选项来连接到路由前缀中的控制器

$routes->resources('Articles', [
    'prefix' => 'Api',
]);

资源路由的自定义路由类

您可以在 $options 数组中为 resources() 提供 connectOptions 键,以提供 connect() 使用的自定义设置

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->resources('Books', [
        'connectOptions' => [
            'routeClass' => 'ApiRoute',
        ]
    ];
});

资源路由的 URL 屈折

默认情况下,多词控制器的 URL 片段是控制器名称的连字符形式。例如,BlogPostsController 的 URL 片段将是 /blog-posts

您可以使用 inflect 选项指定替代屈折类型

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->resources('BlogPosts', [
        'inflect' => 'underscore' // Will use ``Inflector::underscore()``
    ]);
});

以上将生成类似于 /blog_posts 的 URL。

更改路径元素

默认情况下,资源路由使用资源名称的屈折形式作为 URL 段。您可以使用 path 选项设置自定义 URL 段

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->resources('BlogPosts', ['path' => 'posts']);
});

传递的参数

传递的参数是在进行请求时使用的其他参数或路径段。它们通常用于将参数传递给您的控制器方法。

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

在上面的示例中,recentmark 都是传递给 CalendarsController::view() 的参数。传递的参数通过三种方式传递给您的控制器。首先作为调用操作方法的参数,其次在 $this->request->getParam('pass') 中作为数字索引数组可用。在使用自定义路由时,您也可以强制将特定参数也放入传递的参数中。

如果您访问前面提到的 URL,并且您的控制器操作如下所示

class CalendarsController extends AppController
{
    public function view($arg1, $arg2)
    {
        debug(func_get_args());
    }
}

您将得到以下输出

Array
(
    [0] => recent
    [1] => mark
)

相同的数据也存在于控制器、视图和帮助器中的 $this->request->getParam('pass') 中。pass 数组中的值根据它们在调用 URL 中出现的顺序按数字索引

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

以上两种方式都将输出

Array
(
    [0] => recent
    [1] => mark
)

在生成 URL 时,使用 路由数组,您将传递的参数作为数组中没有字符串键的值添加

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

由于 5 具有数字键,因此它被视为传递的参数。

生成 URL

static Cake\Routing\RouterBuilder::url($url = null, $full = false)
static Cake\Routing\RouterBuilder::reverse($params, $full = false)

生成 URL 或反向路由是 CakePHP 中的一个功能,用于允许您更改 URL 结构,而无需修改所有代码。

如果您使用类似于以下内容的字符串创建 URL

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

然后您后来决定 /articles 应该真正称为“posts”,您将不得不遍历整个应用程序重命名 URL。但是,如果您将链接定义为

//`link()` uses Router::url() internally and accepts a routing array

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

或者

//'Router::reverse()' operates on the request parameters array
//and will produce a url string, valid input for `link()`

$requestParams = Router::getRequest()->getAttribute('params');
$this->Html->link('View', Router::reverse($requestParams));

然后,当您决定更改 URL 时,您可以通过定义路由来做到这一点。这将改变传入 URL 的映射,以及生成的 URL。

选择技术取决于您对路由数组元素的预测程度。

使用 Router::url()

Router::url() 允许您在所需数组元素固定或易于推断的情况下使用 路由数组

当目标 URL 定义明确时,它将提供反向路由。

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

当目标未知但遵循明确的模式时,它也很有用。

$this->Html->link(
    'View',
    ['controller' => $controller, 'action' => 'view', $id]
);

带有数字键的元素被视为 传递参数

使用路由数组时,您可以使用特殊键定义查询字符串参数和文档片段。

$routes->url([
    'controller' => 'Articles',
    'action' => 'index',
    '?' => ['page' => 1],
    '#' => 'top'
]);

// Will generate a URL like.
/articles/index?page=1#top

您还可以使用任何特殊路由元素生成 URL。

  • _ext 用于 路由文件扩展名 路由。

  • _base 设置为 false 以从生成的 URL 中删除基本路径。如果您的应用程序不在根目录中,这可用于生成“Cake 相对”的 URL。

  • _scheme 设置为在不同的方案(如 webcalftp)上创建链接。默认为当前方案。

  • _host 设置要用于链接的主机。默认为当前主机。

  • _port 如果您需要在非标准端口上创建链接,请设置端口。

  • _method 定义 URL 所属的 HTTP 动词。

  • _full 如果为 true,则会在生成的 URL 前面加上 常规配置 中提到的 App.fullBaseUrl 的值。

  • _https 设置为 true 以将生成的 URL 转换为 https,或设置为 false 强制使用 http。在 4.5.0 之前,使用 _ssl

  • _name 路由的名称。如果您已经设置了命名路由,您可以使用此键来指定它。

使用 Router::reverse()

Router::reverse() 允许您在当前 URL 经过一些修改后是目标的基础,并且当前 URL 的元素不可预测的情况下,使用 请求参数

例如,想象一个允许用户创建**文章**和**评论**,并将其标记为已发布或草稿的博客。索引页面 URL 可能会包含用户 ID。**评论** URL 也可能包含文章 ID,以标识评论所指的哪篇文章。

以下是此场景的 URL。

/articles/index/42
/comments/index/42/18

当作者使用这些页面时,最好包含允许页面显示所有结果、仅发布或仅草稿的链接。

为了保持代码 DRY,最好通过元素包含链接。

// element/filter_published.php

$params = $this->getRequest()->getAttribute('params');

/* prepare url for Draft */
$params = Hash::insert($params, '?.published', 0);
echo $this->Html->link(__('Draft'), Router::reverse($params));

/* Prepare url for Published */
$params = Hash::insert($params, '?.published', 1);
echo $this->Html->link(__('Published'), Router::reverse($params));

/* Prepare url for All */
$params = Hash::remove($params, '?.published');
echo $this->Html->link(__('All'), Router::reverse($params));

这些方法调用生成的链接将包含一个或两个传递参数,具体取决于当前 URL 的结构。代码将适用于任何未来的 URL,例如,如果您开始使用 pathPrefixes 或添加了更多传递参数。

路由数组与请求参数

这两个数组及其在这些反向路由方法中的使用之间的显着区别在于它们包含传递参数的方式。

路由数组将传递参数作为数组中的无键值包含。

$url = [
    'controller' => 'Articles',
    'action' => 'View',
    $id, //a pass parameter
    'page' => 3, //a query argument
];

请求参数在数组的“pass”键上包含传递参数。

$url = [
    'controller' => 'Articles',
    'action' => 'View',
    'pass' => [$id], //the pass parameters
    '?' => ['page' => 3], //the query arguments
];

因此,如果您愿意,可以将请求参数转换为路由数组,反之亦然。

生成资产 URL

Asset 类提供方法来生成指向应用程序的 css、javascript、图像和其他静态资产文件的 URL。

use Cake\Routing\Asset;

// Generate a URL to APP/webroot/js/app.js
$js = Asset::scriptUrl('app.js');

// Generate a URL to APP/webroot/css/app.css
$css = Asset::cssUrl('app.css');

// Generate a URL to APP/webroot/image/logo.png
$img = Asset::imageUrl('logo.png');

// Generate a URL to APP/webroot/files/upload/photo.png
$file = Asset::url('files/upload/photo.png');

上述方法还接受一个选项数组作为它们的第二个参数。

  • fullBase 使用域名附加完整的 URL。

  • pathPrefix 相对 URL 的路径前缀。

  • plugin` 您可以提供 false` 来防止将路径视为插件资产。

  • timestamp 覆盖 Configure 中 Asset.timestamp 的值。设置为 false 以跳过时间戳生成。设置为 true 以在调试为 true 时应用时间戳。设置为 'force' 以始终启用时间戳,无论调试值如何。

// Generates http://example.org/img/logo.png
$img = Asset::url('logo.png', ['fullBase' => true]);

// Generates /img/logo.png?1568563625
// Where the timestamp is the last modified time of the file.
$img = Asset::url('logo.png', ['timestamp' => true]);

要为插件中的文件生成资产 URL,请使用 插件语法

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

重定向路由

重定向路由允许您针对传入路由发出 HTTP 状态 30x 重定向,并将它们指向不同的 URL。当您想要通知客户端应用程序资源已移动,并且不想公开同一内容的两个 URL 时,这很有用。

重定向路由与普通路由不同,因为如果找到匹配项,它们会执行实际的标头重定向。重定向可以发生在应用程序内的目标或外部位置。

$routes->scope('/', function (RouteBuilder $routes) {
    $routes->redirect(
        '/home/*',
        ['controller' => 'Articles', 'action' => 'view'],
        ['persist' => true]
        // Or ['persist'=>['id']] for default routing where the
        // view action expects $id as an argument.
    );
})

/home/* 重定向到 /articles/view 并将参数传递给 /articles/view。使用数组作为重定向目标,您可以使用其他路由来定义 URL 字符串应该重定向到哪里。您可以使用字符串 URL 作为目标重定向到外部位置。

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

这将使用 HTTP 状态 302 将 /articles/* 重定向到 http://google.com

实体路由

实体路由允许您使用实现 ArrayAccess 的实体、数组或对象作为路由参数的来源。这使您可以更轻松地重构路由,并使用更少的代码生成 URL。例如,如果您从一个看起来像这样的路由开始:

$routes->get(
    '/view/{id}',
    ['controller' => 'Articles', 'action' => 'view'],
    'articles:view'
);

您可以使用以下方法生成指向此路由的 URL。

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

稍后,您可能希望在 URL 中公开文章 slug,以用于 SEO 目的。为了做到这一点,您需要更新所有生成指向 articles:view 路由的 URL 的地方,这可能需要一些时间。如果我们使用实体路由,我们将整个文章实体传递到 URL 生成,这使我们能够在 URL 需要更多参数时跳过任何返工。

use Cake\Routing\Route\EntityRoute;

// Create entity routes for the rest of this scope.
$routes->setRouteClass(EntityRoute::class);

// Create the route just like before.
$routes->get(
    '/view/{id}/{slug}',
    ['controller' => 'Articles', 'action' => 'view'],
    'articles:view'
);

现在,我们可以使用 _entity 键生成 URL。

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

这将从提供的实体中提取 id 属性和 slug 属性。

自定义路由类

自定义路由类允许您扩展和更改单个路由解析请求和处理反向路由的方式。路由类有一些约定。

  • 预期在应用程序或插件的 Routing\\Route 命名空间中找到路由类。

  • 路由类应扩展 Cake\Routing\Route\Route

  • 路由类应实现 match() 和/或 parse() 中的一个或两个。

parse() 方法用于解析传入的 URL。它应该生成一个请求参数数组,这些参数可以解析为控制器和操作。从此方法返回 null 以指示匹配失败。

match() 方法用于匹配 URL 参数数组并创建字符串 URL。如果 URL 参数与路由不匹配,则应返回 false

在制作路由时,您可以使用自定义路由类,方法是使用 routeClass 选项。

$routes->connect(
    '/{slug}',
    ['controller' => 'Articles', 'action' => 'view'],
    ['routeClass' => 'SlugRoute']
);

// Or by setting the routeClass in your scope.
$routes->scope('/', function (RouteBuilder $routes) {
    $routes->setRouteClass('SlugRoute');
    $routes->connect(
        '/{slug}',
        ['controller' => 'Articles', 'action' => 'view']
    );
});

此路由将创建 SlugRoute 的实例,并允许您实现自定义参数处理。您可以使用标准 插件语法 使用插件路由类。

默认路由类

static Cake\Routing\RouterBuilder::setRouteClass($routeClass = null)

如果您希望为您的路由使用除默认 Route 之外的其他路由类,您可以在设置任何路由之前调用 RouterBuilder::setRouteClass(),并避免为每个路由指定 routeClass 选项。例如,使用

use Cake\Routing\Route\DashedRoute;

$routes->setRouteClass(DashedRoute::class);

将导致此后连接的所有路由使用 DashedRoute 路由类。在没有参数的情况下调用该方法将返回当前默认路由类。

回调方法

Cake\Routing\RouterBuilder::fallbacks($routeClass = null)

回调方法是定义默认路由的简单快捷方式。该方法对定义的规则使用传递的路由类,或者如果没有提供类,则使用 RouterBuilder::setRouteClass() 返回的类。

像这样调用回调:

use Cake\Routing\Route\DashedRoute;

$routes->fallbacks(DashedRoute::class);

等同于以下显式调用。

use Cake\Routing\Route\DashedRoute;

$routes->connect('/{controller}', ['action' => 'index'], ['routeClass' => DashedRoute::class]);
$routes->connect('/{controller}/{action}/*', [], ['routeClass' => DashedRoute::class]);

注意

使用默认路由类(Route)和回调,或者任何带有 {plugin} 和/或 {controller} 路由元素的路由,将导致 URL 大小写不一致。

警告

回调路由模板非常通用,允许为不存在的控制器和操作生成和解析 URL。回调 URL 也可能在您的 URL 中引入歧义和重复。

随着应用程序的增长,建议您放弃回调 URL,并在应用程序中明确定义路由。

创建持久 URL 参数

您可以使用 URL 过滤器函数钩入 URL 生成过程。过滤器函数在 URL 与路由匹配之前被调用,这使您可以在路由之前准备 URL。

回调过滤器函数应期望以下参数。

  • $params 正在处理的 URL 参数数组。

  • $request 当前请求(Cake\Http\ServerRequest 实例)。

URL 过滤器函数应始终返回参数,即使未修改。

URL 过滤器允许您实现持久参数等功能。

Router::addUrlFilter(function (array $params, ServerRequest $request) {
    if ($request->getParam('lang') && !isset($params['lang'])) {
        $params['lang'] = $request->getParam('lang');
    }

    return $params;
});

过滤器函数按其连接顺序应用。

另一个用例是在运行时更改特定路由(例如插件路由)。

Router::addUrlFilter(function (array $params, ServerRequest $request) {
    if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params['controller'])) {
        return $params;
    }
    if ($params['controller'] === 'Languages' && $params['action'] === 'view') {
        $params['controller'] = 'Locations';
        $params['action'] = 'index';
        $params['language'] = $params[0];
        unset($params[0]);
    }

    return $params;
});

这将改变以下路由

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

为这个

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

警告

如果您使用 路由中间件 的缓存功能,您必须在应用程序的 bootstrap() 中定义 URL 过滤器,因为过滤器不属于缓存数据的一部分。