路由

最后更新于:2022-04-02 05:15:20

[TOC] # 路由 路由器组件允许您定义映射到应接收请求的控制器或处理程序的路由。路由器只是解析URI以确定此信息。路由器有两种模式:MVC模式和仅匹配模式。第一种模式非常适合使用MVC应用程序。 ## 默认路由 `Phalcon\Mvc\Router` 提供高级路由功能。 在MVC模式下,您可以定义路由并将它们映射到您需要的控制器/操作。路由定义如下: ```php add( '/admin/users/my-profile', [ 'controller' => 'users', 'action' => 'profile', ] ); // Another route $router->add( '/admin/users/change-password', [ 'controller' => 'users', 'action' => 'changePassword', ] ); $router->handle(); ``` `add()` 方法的第一个参数是您要匹配的模式,可选地,第二个参数是一组路径。在这种情况下,如果URI是`/admin/users/my-profile`,则将执行`users`控制器的 `profile` 操作。重要的是要记住路由器不执行控制器和操作,它只收集此信息以通知正确的组件(即`Phalcon\Mvc\Dispatcher`)这是它应该执行的控制器/操作。 应用程序可以有许多路径,逐个定义路由可能是一项繁琐的任务。在这些情况下,我们可以创建更灵活的路由: ```php add( '/admin/:controller/a/:action/:params', [ 'controller' => 1, 'action' => 2, 'params' => 3, ] ); ``` 在上面的示例中,我们使用通配符来使路由对许多URI有效。例如,通过访问以下URL(`/admin/users/a/delete/dave/301`)将产生: | Controller | Action | Parameter | Parameter | |:----------:|:------:|:---------:|:---------:| | users | delete | dave | 301 | `add()`方法接收一个模式,该模式可以选择具有预定义的占位符和正则表达式修饰符。所有路由模式必须以正斜杠字符(`/`)开头。使用的正则表达式语法与[PCRE正则表达式](http://www.php.net/manual/en/book.pcre.php)相同。请注意,没有必要添加正则表达式分隔符。所有路由模式都不区分大小写。 第二个参数定义匹配的部分应如何绑定到控制器/动作/参数。匹配部分是由括号(圆括号)分隔的占位符或子图案。在上面给出的示例中,第一个子模式匹配(`:controller`)是路径的控制器部分,第二个是动作,依此类推。 这些占位符有助于编写对开发人员更具可读性且更易于理解的正则表达式。支持以下占位符: | 占位符 | 正则表达式 | 用法 | | -------------- | ------------------------ | ------------------------------------------------------------------------------------------------------ | | `/:module` | `/([a-zA-Z0-9\_\-]+)` | 仅使用字母数字字符匹配有效的模块名称 | | `/:controller` | `/([a-zA-Z0-9\_\-]+)` | 仅使用字母数字字符匹配有效的控制器名称 | | `/:action` | `/([a-zA-Z0-9_-]+)` | 仅使用字母数字字符匹配有效的操作名称 | | `/:params` | `(/.*)*` | 匹配由斜杠分隔的可选单词列表。仅在路由末尾使用此占位符 | | `/:namespace` | `/([a-zA-Z0-9\_\-]+)` | 匹配单级命名空间名称 | | `/:int` | `/([0-9]+)` | 匹配整数参数 | 控制器名称是驼峰,这意味着删除了字符( `-` )和(`_`),下一个字符是大写的。例如,some_controller被转换为SomeController。 Since you can add many routes as you need using the `add()` method, the order in which routes are added indicate their relevance, latest routes added have more relevance than first added. Internally, all defined routes are traversed in reverse order until `Phalcon\Mvc\Router` finds the one that matches the given URI and processes it, while ignoring the rest. 由于您可以使用 `add()` 方法根据需要添加许多路由,因此添加路由的顺序表明它们的相关性,添加的最新路由具有比首次添加的更多相关性。在内部,所有定义的路由都以相反的顺序遍历,直到`Phalcon\Mvc\Router` 找到与给定URI匹配并处理它的那个,同时忽略其余的路由。 ### 带名称的参数 下面的示例演示了如何定义路由参数的名称: ```php add( '/news/([0-9]{4})/([0-9]{2})/([0-9]{2})/:params', [ 'controller' => 'posts', 'action' => 'show', 'year' => 1, // ([0-9]{4}) 'month' => 2, // ([0-9]{2}) 'day' => 3, // ([0-9]{2}) 'params' => 4, // :params ] ); ``` 在上面的示例中,路径未定义`controller`或`action`部件。这些部件将替换为固定值(`posts`和`show`)。用户将不知道请求真正分发的控制器。在控制器内部,可以按如下方式访问这些命名参数: ```php dispatcher->getParam('year'); // Get 'month' parameter $month = $this->dispatcher->getParam('month'); // Get 'day' parameter $day = $this->dispatcher->getParam('day'); // ... } } ``` 请注意,参数的值是从调度程序获得的。发生这种情况是因为它是最终与应用程序驱动程序交互的组件。此外,还有另一种方法可以创建命名参数作为模式的一部分: ```php add( '/documentation/{chapter}/{name}.{type:[a-z]+}', [ 'controller' => 'documentation', 'action' => 'show', ] ); ``` 您可以像以前一样访问其值: ```php dispatcher->getParam('name'); // Get 'type' parameter $type = $this->dispatcher->getParam('type'); // ... } } ``` ### 短语法 如果您不喜欢使用数组来定义路径路径,则还可以使用替代语法。以下示例产生相同的结果: ```php add( '/posts/{year:[0-9]+}/{title:[a-z\-]+}', 'Posts::show' ); // Array form $router->add( '/posts/([0-9]+)/([a-z\-]+)', [ 'controller' => 'posts', 'action' => 'show', 'year' => 1, 'title' => 2, ] ); ``` ### 混合数组和短语法 可以混合使用数组和短语法来定义路由,在这种情况下请注意,命名参数会根据定义它们的位置自动添加到路径路径中: ```php add( '/news/{country:[a-z]{2}}/([a-z+])/([a-z\-+])', [ 'section' => 2, // Positions start with 2 'article' => 3, ] ); ``` ### 模块路由 您可以定义路径包含模块的路由。这特别适用于多模块应用。可以定义包含模块通配符的默认路由: ```php add( '/:module/:controller/:action/:params', [ 'module' => 1, 'controller' => 2, 'action' => 3, 'params' => 4, ] ); ``` 在这种情况下,路由始终必须将模块名称作为URL的一部分。例如,以下URL:`/admin/users/edit/sonny` 将被处理为: | Module | Controller | Action | Parameter | |:------:|:----------:|:------:|:---------:| | admin | users | edit | sonny | 或者您可以将特定路由绑定到特定模块: ```php add( '/login', [ 'module' => 'backend', 'controller' => 'login', 'action' => 'index', ] ); $router->add( '/products/:action', [ 'module' => 'frontend', 'controller' => 'products', 'action' => 1, ] ); ``` 或者将它们绑定到特定的名称空间: ```php add( '/:namespace/login', [ 'namespace' => 1, 'controller' => 'login', 'action' => 'index', ] ); ``` 命名空间/类名必须分开传递: ```php add( '/login', [ 'namespace' => 'Backend\Controllers', 'controller' => 'login', 'action' => 'index', ] ); ``` ### HTTP方法限制 使用简单的`add()`添加路由时,将为任何HTTP方法启用路由。有时我们可以将路由限制为特定方法,这在创建RESTful应用程序时特别有用: ```php addGet( '/products/edit/{id}', 'Products::edit' ); // This route only will be matched if the HTTP method is POST $router->addPost( '/products/save', 'Products::save' ); // This route will be matched if the HTTP method is POST or PUT $router->add( '/products/update', 'Products::update' )->via( [ 'POST', 'PUT', ] ); ``` ### 使用转换器 转换器允许您在将路由参数传递给调度程序之前自由转换路径的参数。以下示例显示了如何使用它们: ```php add( '/products/{slug:[a-z\-]+}', [ 'controller' => 'products', 'action' => 'show', ] ); $route->convert( 'slug', function ($slug) { // Transform the slug removing the dashes return str_replace('-', '', $slug); } ); ``` 转换器的另一个用例是将模型绑定到路由中。这允许模型直接传递到定义的操作: ```php add( '/products/{id}', [ 'controller' => 'products', 'action' => 'show', ] ); $route->convert( 'id', function ($id) { // Fetch the model return Product::findFirstById($id); } ); ``` ### 路由分组 如果一组路由具有公共路径,则可以对它们进行分组以轻松维护它们: ```php 'blog', 'controller' => 'index', ] ); // All the routes start with /blog $blog->setPrefix('/blog'); // Add a route to the group $blog->add( '/save', [ 'action' => 'save', ] ); // Add another route to the group $blog->add( '/edit/{id}', [ 'action' => 'edit', ] ); // This route maps to a controller different than the default $blog->add( '/blog', [ 'controller' => 'blog', 'action' => 'index', ] ); // Add the group to the router $router->mount($blog); ``` 您可以将路径组移动到单独的文件,以改善应用程序中的组织和代码重用: ```php setPaths( [ 'module' => 'blog', 'namespace' => 'Blog\Controllers', ] ); // All the routes start with /blog $this->setPrefix('/blog'); // Add a route to the group $this->add( '/save', [ 'action' => 'save', ] ); // Add another route to the group $this->add( '/edit/{id}', [ 'action' => 'edit', ] ); // This route maps to a controller different than the default $this->add( '/blog', [ 'controller' => 'blog', 'action' => 'index', ] ); } } ``` 然后将该组安装在路由器中: ```php mount( new BlogRoutes() ); ``` ## 匹配路由 必须将有效的URI传递给路由器,以便它可以处理它并找到匹配的路由。默认情况下,路由URI取自重写引擎模块创建的`$_GET['_url']`变量。与Phalcon一起使用的几个重写规则是: ```apacheconfig RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^((?s).*)$ index.php?_url=/$1 [QSA,L] ``` 在此配置中,对不存在的文件或文件夹的任何请求都将发送到`index.php`。以下示例显示如何在独立模式下使用此组件: ```php handle(); // Or Setting the URI value directly $router->handle('/employees/edit/17'); // Getting the processed controller echo $router->getControllerName(); // Getting the processed action echo $router->getActionName(); // Get the matched route $route = $router->getMatchedRoute(); ``` ## 路由命名 添加到路由器的每条路由都在内部存储为`Phalcon\Mvc\Router\Route` 对象。该类封装了每个路由的所有细节。例如,我们可以为路径命名,以便在我们的应用程序中唯一地标识它。如果要从中创建URL,这尤其有用。 ```php add( '/posts/{year}/{title}', 'Posts::show' ); $route->setName('show-posts'); ``` 然后,使用例如组件`Phalcon\Mvc\Url`,我们可以从其名称构建路由: ```php get( [ 'for' => 'show-posts', 'year' => '2012', 'title' => 'phalcon-1-0-released', ] ); ``` ## 用例 以下是自定义路由的示例: ```php add( '/system/:controller/a/:action/:params', [ 'controller' => 1, 'action' => 2, 'params' => 3, ] ); // Matches '/es/news' $router->add( '/([a-z]{2})/:controller', [ 'controller' => 2, 'action' => 'index', 'language' => 1, ] ); // Matches '/es/news' $router->add( '/{language:[a-z]{2}}/:controller', [ 'controller' => 2, 'action' => 'index', ] ); // Matches '/admin/posts/edit/100' $router->add( '/admin/:controller/:action/:int', [ 'controller' => 1, 'action' => 2, 'id' => 3, ] ); // Matches '/posts/2015/02/some-cool-content' $router->add( '/posts/([0-9]{4})/([0-9]{2})/([a-z\-]+)', [ 'controller' => 'posts', 'action' => 'show', 'year' => 1, 'month' => 2, 'title' => 3, ] ); // Matches '/manual/en/translate.adapter.html' $router->add( '/manual/([a-z]{2})/([a-z\.]+)\.html', [ 'controller' => 'manual', 'action' => 'show', 'language' => 1, 'file' => 2, ] ); // Matches /feed/fr/le-robots-hot-news.atom $router->add( '/feed/{lang:[a-z]+}/{blog:[a-z\-]+}\.{type:[a-z\-]+}', 'Feed::get' ); // Matches /api/v1/users/peter.json $router->add( '/api/(v1|v2)/{method:[a-z]+}/{param:[a-z]+}\.(json|xml)', [ 'controller' => 'api', 'version' => 1, 'format' => 4, ] ); ``` >[warning] 注意控制器和命名空间的正则表达式中允许的字符。由于它们成为类名,反过来它们通过文件系统可被攻击者用来读取未经授权的文件。一个安全的正则表达式是:`/([a-zA-Z0-9\_\-]+)` ## 默认行为 `Phalcon\Mvc\Router`有一个默认行为,它提供了一个非常简单的路由,总是需要一个匹配以下模式的URI:`/:controller/:action/:params` 例如,对于像`http://phalconphp.com/documentation/show/about.html`这样的URL,此路由器将按如下方式对其进行翻译: | Controller | Action | Parameter | |:-------------:|:------:|:----------:| | documentation | show | about.html | 如果您不希望路由器出现此行为,则必须创建路由器,并将`false`作为第一个参数: ```php add( '/', [ 'controller' => 'index', 'action' => 'index', ] ); ``` ## Not Found路径 如果路由器中指定的路由均未匹配,则可以定义要在此方案中使用的一组路径: ```php notFound( [ 'controller' => 'index', 'action' => 'route404', ] ); ``` 这通常用于错误404页面。 > 只有在没有默认路由的情况下创建路由器时,这才有效: `$router = Phalcon\Mvc\Router(FALSE);` ## 设置默认路径 可以为模块,控制器或操作定义默认值。当路由缺少任何这些路径时,它们可以由路由器自动填充: ```php setDefaultModule('backend'); $router->setDefaultNamespace('Backend\Controllers'); $router->setDefaultController('index'); $router->setDefaultAction('index'); // Using an array $router->setDefaults( [ 'controller' => 'index', 'action' => 'index', ] ); ``` ## 处理额外/尾随斜杠 有时可以使用额外/尾部斜杠访问路径。这些额外的斜杠将导致在调度器中产生未找到的状态。您可以设置路由器以自动从处理路径的末尾删除斜杠: ```php removeExtraSlashes(true); ``` 或者,您可以修改特定路由以选择性地接受尾部斜杠: ```php add( '/{language:[a-z]{2}}/:controller[/]{0,1}', [ 'controller' => 2, 'action' => 'index', ] ); ``` ## 匹配回调 有时,路由只有在满足特定条件时才能匹配。您可以使用`beforeMatch()` 回调向路由添加任意条件。如果此函数返回`false`,则路由将被视为不匹配: ```php add('/login', [ 'module' => 'admin', 'controller' => 'session', ] ); $route->beforeMatch( function ($uri, $route) { // Check if the request was made with Ajax if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') { return false; } return true; } ); ``` 您可以在类中重复使用这些额外条件: ```php add( '/get/info/{id}', [ 'controller' => 'products', 'action' => 'info', ] ); $route->beforeMatch( [ new AjaxFilter(), 'check' ] ); ``` 从Phalcon 3开始,还有另一种方法可以检查: ```php add( '/login', [ 'module' => 'admin', 'controller' => 'session', ] ); $route->beforeMatch( function ($uri, $route) { /** * @var string $uri * @var \Phalcon\Mvc\Router\Route $route * @var \Phalcon\DiInterface $this * @var \Phalcon\Http\Request $request */ $request = $this->getShared('request'); // Check if the request was made with Ajax return $request->isAjax(); } ); ``` ## 主机名约束 路由器允许您设置主机名约束,这意味着可以限制特定路由或一组路由仅在路由也满足主机名约束时匹配: ```php add( '/login', [ 'module' => 'admin', 'controller' => 'session', 'action' => 'login', ] ); $route->setHostName('admin.company.com'); ``` 主机名也可以作为正则表达式传递: ```php add( '/login', [ 'module' => 'admin', 'controller' => 'session', 'action' => 'login', ] ); $route->setHostName('([a-z]+).company.com'); ``` 在路由组中,您可以设置适用于组中每个路由的主机名约束: ```php 'blog', 'controller' => 'posts', ] ); // Hostname restriction $blog->setHostName('blog.mycompany.com'); // All the routes start with /blog $blog->setPrefix('/blog'); // Default route $blog->add( '/', [ 'action' => 'index', ] ); // Add a route to the group $blog->add( '/save', [ 'action' => 'save', ] ); // Add another route to the group $blog->add( '/edit/{id}', [ 'action' => 'edit', ] ); // Add the group to the router $router->mount($blog); ``` ## URI来源 默认情况下,URI信息是从`$_GET['_url']`变量获得的,这是由Rewrite-Engine传递给Phalcon的,如果需要,你也可以使用`$_SERVER['REQUEST_URI']`: ```php setUriSource( Router::URI_SOURCE_GET_URL ); // Use $_SERVER['REQUEST_URI'] $router->setUriSource( Router::URI_SOURCE_SERVER_REQUEST_URI ); ``` 或者您可以手动将URI传递给`handle()`方法: ```php handle('/some/route/to/handle'); ``` >[danger] 请注意,使用`Router::URI_SOURCE_GET_URL`会自动解码Uri,因为它基于`$_REQUEST`超全局。但是,目前使用`Router::URI_SOURCE_SERVER_REQUEST_URI`不会自动为您解码Uri。这将在下一个主版本中发生变化。 ## 测试你的路由 由于此组件没有依赖关系,您可以创建如下所示的文件来测试您的路由: ```php handle($testRoute); echo 'Testing ', $testRoute, '
'; // Check if some route was matched if ($router->wasMatched()) { echo 'Controller: ', $router->getControllerName(), '
'; echo 'Action: ', $router->getActionName(), '
'; } else { echo "The route wasn't matched by any route
"; } echo '
'; } ``` ## 事件 与许多其他组件一样,路由器也有事件。没有任何事件可以停止操作。以下是可用事件列表 | 事件 | 描述 | | -------------------------- | ------------------------------------ | | `router:beforeCheckRoutes` | 在检查所有加载的路由之前被触发 | | `router:beforeCheckRoute` | 在检查路由之前被触发 | | `router:matchedRoute` | 匹配路由时触发 | | `router:notMatchedRoute` | 任一路由匹配时触发 | | `router:afterCheckRoutes` | 检查完所有路由后触发 | | `router:beforeMount` | 在安装新路由之前触发 | ## 注解路由 此组件提供与注解服务集成的变体。使用此策略,您可以直接在控制器中编写路由,而不是在服务注册中添加它们: ```php addResource('Products', '/api/products'); return $router; }; ``` 注解可以通过以下方式定义: ```php addModuleResource('backend', 'Products', '/api/products'); return $router; }; ``` ## 注册路由器实例 您可以在服务注册期间使用Phalcon依赖注入器注册路由器,以使其在控制器内可用。 您需要在引导程序文件中添加以下代码(例如 `index.php`,如果使用[Phalcon Developer Tools](http://phalconphp.com/en/download/tools),则为`app/config/services.php`。 ```php set( 'router', function () { require __DIR__ . '/../app/config/routes.php'; return $router; } ); ``` 您需要创建 `app/config/routes.php`并添加路由器初始化代码,例如: ```php add( '/login', [ 'controller' => 'login', 'action' => 'index', ] ); $router->add( '/products/:action', [ 'controller' => 'products', 'action' => 1, ] ); return $router; ``` ## 实现自己的路由器 必须实现 `Phalcon\Mvc\RouterInterface` 接口才能创建自己的路由器,取代Phalcon提供的路由器。
';