🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# URL路由 路由是URL和控制器操作之间的双向转换。 双向意味着我们可以确定控制器URL链接到什么,反之亦然:生成给定方法的URL。 本章包含学到几点: * 如何定义路由和创建链接 * 关于SEO重定向的几个注释 * 如何调试定义的路由 * 如何创建自己的路由器 # 什么是路由? 路由是URL和应用程序请求之间的双向转换。 * Nette\Http\IRequest (includes URL) → Nette\Application\Request * Nette\Application\Request → absolute URL 由于双向路由,您不必将网址硬编码到模板中,您只需链接到控制器的操作,框架会为您生成网址: ~~~ {* creates a link to presenter 'Product' and action 'detail' *} <a n:href="Product:detail $productId">product detail</a> ~~~ 这个以前实例已有说明了。可以详细了解如何创建链接(MVC应用程序和控制器这一单里)。 路由是一个单独的应用程序层。 这允许您非常有效地操作与URL结构,而不需要修改应用程序本身。 随时更改路线很简单,同时保留原始地址,并自动重定向到新版本。 ### SimpleRouter 所需的URL格式由路由器设置。 最简单的路由器实现是SimpleRouter。 它可以在不需要特定的URL格式时使用,当mod_rewrite(或替代)不可用或者我们根本不想为用户友好的URL打扰。 生成的地址将如下所示: ~~~ http://example.com/?presenter=Product&action=detail&id=123 ~~~ 这个是原来自动生成URL。 SimpleRouter构造函数的第一个参数是默认的Presenter方法,如果我们打开例如。 http://example.com/, 无需其他参数。 ~~~ // 默认为presenter'Homepage'和'default'方法 $router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); ~~~ 第二个构造函数参数是可选的,用于传递附加标志(对于单向路由只有SimpleRouter :: ONE_WAY)。 配置应用程序以使用SimpleRouter的推荐方法是使用配置文件(例如config.neon): ~~~ services: application.router: Nette\Application\Routers\SimpleRouter('Homepage:default') ~~~ 这一小节就是教你默认打开URL方法的设置。比如我有一个后台AdminPresenter.php控制器,里面有一个index方法,我想打开网址主页就是后台中的index方法,那你就要在config.neon配置里加入 ~~~ services: application.router: Nette\Application\Routers\SimpleRouter('Admin:index') ~~~ ## 路由:更漂亮的网址 人类友好的URL(也更酷和更漂亮)更容易记住,并帮助SEO。 Nette框架保持当前的趋势,并完全满足开发人员的愿望。 ~~~ 所有请求必须由index.php文件处理。 这可以通过例如 通过使用Apache模块mod_rewrite或Nginx的try_files指令(请参阅如何配置服务器的漂亮的URL)。 ~~~ Route类能够创建几乎任何格式的地址一个。 让我们从一个简单的例子开始,生成以下漂亮的URL中Product:default与id = 123: ~~~ http://example.com/product/detail/123 ~~~ Product:控制器前面这部份名称 detail:方法名称 123:参数 这个以前实例也说明了。 以下代码段创建一个Route对象,将路径掩码作为第一个参数,并在第二个参数中指定默认方法。 我们可以使用第三个参数传递额外的标志。 使用router/RouterFactory.php,这个文件就是设置路由的。 ~~~ // 默认为presenter控制器中的default方法。 $route = new Route('<presenter>/<action>[/<id>]', 'Homepage:default'); // 或者使用数组写入 $route = new Route('<presenter>/<action>[/<id>]', [ 'presenter' => 'Homepage', 'action' => 'default' ]); ~~~ 此路由可供所有控制器和方法使用。 接受诸如/ article / edit / 10或/ catalog / list的路径,因为id部分包含在方括号中,这表示它是可选的。 由于其他参数(控制器和方法)具有默认值(Homepage和default),因此它们也是可选的。 如果它们的值与默认值相同,则会在生成URL时跳过这些值。 链接到Product:default只生成http://example.com/product 并链接到Homepage:default只生成http://example.com/. 以上说的这个delault也是一种默认值。它也是可以不用写出来,比如:我有一个控制器DemoPresenter.php里面有一个default方法,打开这个方法链接时我们可以用 http://example.com/demo 后面的default可以不用写进去。 ### 路径掩码 最简单的路径掩码只包括静态URL和目标控制器操作。 ~~~ $route = new Route('products', 'Products:default'); ~~~ 然而,大多数真实面具包含一些参数。 参数括在尖括号中(例如<year>),并传递给目标控制器。 ~~~ $route = new Route('history/<year>', 'History:view'); ~~~ 掩码还可以包含传统的GET参数(在问号后查询)。 在路径掩码的这部分中既不支持验证表达式也不支持更复杂的结构,但可以设置什么键属于哪个变量: ~~~ // 在我们的应用程序中使用GET参数“cat”作为“categoryId” $route = new Route('<presenter>/<action> ? id=<productId> & cat=<categoryId>', ...); ~~~ ~~~ 问号前的参数称为路径参数,问号后的参数称为查询参数。 ~~~ 掩码不仅可以描述相对于应用程序文档根(web根)的路径,而且可以包含相对于服务器文档根(以单个斜杠开始)或具有域的绝对路径(以双斜线开始)的路径。 ~~~ // 相对于应用程序文档根目录(www目录) $route = new Route('<presenter>/<action>', ...); // 相对于服务器文档根 $route = new Route('/<presenter>/<action>', ...); // 绝对路径包括主机名 $route = new Route('//<subdomain>.example.com/<presenter>/<action>', ...); ~~~ 绝对路径掩码可以利用以下变量: * %tld%=顶级域名,例如 com或org * %sld%=第二级域,例如 在example.com返回示例 * %domain%=第二级域,例如 example.com * %basePath% ~~~ $route = new Route('//www.%domain%/%basePath%/<presenter>/<action>', ...); $route = new Route('//www.%sld%.%tld/%basePath%/<presenter>/<action', ...); ~~~ 路线收集 因为我们通常定义多个路由,我们将它们包装到一个RouteList中 ~~~ use Nette\Application\Routers\RouteList; use Nette\Application\Routers\Route; $router = new RouteList(); $router[] = new Route('rss.xml', 'Feed:rss'); $router[] = new Route('article/<id>', 'Article:view'); $router[] = new Route('<presenter>/<action>[/<id>]', 'Homepage:default'); ~~~ 与其他框架不同,Nette不需要命名路由。 ~~~ 重要的是,按照从上到下的顺序评估的路由顺序。 这里的经验法则是,路由从最顶部的特定位置到最底部的最模糊的位置。 ~~~ 请记住,大量的路由会对应用程序速度产生负面影响,主要是在生成链接时。 值得保持路线尽可能简单。 如果没有找到路由,将抛出BadRequestException,这对用户显示为404 Not Found。 ### 路由器工厂 推荐的配置应用路由器的方法是写一个工厂(例如位于app / router / RouterFactory.php中)并将其注册到配置文件(例如位于app / config / config.neon中)中的系统DI容器。 **File app/router/RouterFactory.php:** ~~~ <?php namespace App; use Nette\Application\Routers\RouteList, Nette\Application\Routers\Route; class RouterFactory { /** * @return \Nette\Application\IRouter */ public function createRouter() { $router = new RouteList(); $router[] = new Route('<presenter>/<action>', 'Homepage:default'); return $router; } } ~~~ @return注释是重要的,并且是让代码工作所必需的。 **File app/config/config.neon:** ~~~ services: routerFactory: App\RouterFactory router: @routerFactory::createRouter ~~~ ### 默认值 每个参数可能在掩码中定义了默认值: ~~~ $route = new Route('<presenter=Homepage>/<action=default>/<id=>'); ~~~ 或使用数组: ~~~ $route = new Route('<presenter>/<action>/<id>', [ 'presenter' => 'Homepage', 'action' => 'default', 'id' => NULL, ]); //等于以下复杂符号 $route = new Route('<presenter>/<action>/<id>', [ 'presenter' => [ Route::VALUE => 'Homepage', ], 'action' => [ Route::VALUE => 'default', ], 'id' => [ Route::VALUE => NULL, ], ]); ~~~ <presenter>和<action>的默认值也可以写为第二个构造函数参数中的字符串。 ~~~ $route = new Route('<presenter>/<action>/<id=>', 'Homepage:default'); ~~~ ## 正则表达式 每个参数可能已经定义了需要匹配的正则表达式。 在匹配和生成URL时检查此正则表达式。 例如,让我们将id设置为只有数字,使用\ d + regexp: ~~~ //regexp可以直接在路径掩码中定义参数名 $route = new Route('<presenter>/<action>[/<id \d+>]', 'Homepage:default'); // 上面代码也可以写成以下 $route = new Route('<presenter>/<action>[/<id>]', [ 'presenter' => 'Homepage', 'action' => 'default', 'id' => [ Route::PATTERN => '\d+', ], ]); ~~~ 意思这个URL后面的ID必须是数字,如果不是数字就会出错。这个ID是根据你这个正则表达式来确点。 ~~~ 路径参数的默认验证表达式为[^ /] +,表示除斜杠外的所有字符。 如果参数也应该与斜杠匹配,我们可以将正则表达式设置为.+。 ~~~ ## 可选序列 方括号表示掩码的可选部分。 隐藏的任何部分可以设置为可选,包括那些包含参数的部分: ~~~ $route = new Route('[<lang [a-z]{2}>/]<name>', 'Article:view'); 接受的URL: 参数: // Accepted URLs: Parameters: // /en/download action => view, lang => en, name => download // /download action => view, lang => NULL, name => download ~~~ 显然,如果参数在可选序列内,它也是可选的,默认为NULL。 序列应该定义它的周围环境,在这种情况下,必须跟随一个参数,如果设置的斜杠。 该技术可以用于例如可选的语言子域: ~~~ $route = new Route('//[<lang=en>.]%domain%/<presenter>/<action>', ...); ~~~ 序列可以自由嵌套和组合: ~~~ $route = new Route( '[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]', 'Homepage:default' ); //接受的网址: // /cs/hello // /en-us/hello // /hello // /hello/page-12 ~~~ 网址生成器尝试保持网址尽可能短。 这就是为什么index [.html]路由生成为/ index。 可以通过在表示相应可选序列的最左方括号之后写入感叹号来反转此行为: ~~~ // 可以 both /hello和 /hello.html, generates /hello $route = new Route('<name>[.html]'); // 可以 both /hello和 /hello.html, generates /hello.html $route = new Route('<name>[!.html]'); ~~~ 没有方括号的可选参数(即具有默认值的参数)的行为如下所示: ~~~ $route = new Route('<presenter=Homepage>/<action=default>/<id=>'); // 等于: $route = new Route('[<presenter=Homepage>/[<action=default>/[<id>]]]'); ~~~ 如果我们想改变最右边斜线的生成方式,那就是代替**/ homepage /** 得到** / homepage**,我们可以调整路线: ~~~ $route = new Route('[<presenter=Homepage>[/<action=default>[/<id>]]]'); ~~~ ## 标志 路由默认行为可以通过几个标志来改变,它们作为第三个构造函数参数传递。 ONE_WAY标记 - 单一路线通常用于保留旧网址功能,当应用程序被重写时。 Route :: ONE_WAY标记不用于生成URL的路由。 ~~~ // 旧URL /product-info?id=123,新 URL /product/123 $router[] = new Route('product-info', 'Product:detail', Route::ONE_WAY); $router[] = new Route('product/<id>', 'Product:detail'); ~~~ ### HTTPS 我们需要配置您的服务器以使用HTTPS协议。 通过使用代码为301的永久重定向,在我们的应用程序的根目录中使用.htaccess文件,可以实现为已经完善的应用程序转发所有地址。 ~~~ <IfModule mod_rewrite.c> RewriteEngine On ... RewriteCond %{HTTPS} off RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] ... </IfModule> ~~~ 路由生成具有加载页面的同一协议的URL。 如果我们需要,使某些地址使用其他协议,我们在路由的掩码中使用它们。 ~~~ // 转发到HTTP协议 $route = new Route('http://%host%/<presenter>/<action>','Homepage:default'); // 转发到HTTPS协议 $route = new Route('https://%host%/<presenter>/<action>','Admin:default'); ~~~ ## 过滤器和翻译 这是一个好的做法是用英语编写源代码,但如果你需要你的应用程序在不同的环境中运行怎么办? 简单的路线如: ~~~ $route = new Route('<presenter>/<action>/<id>', [ 'presenter' => 'Homepage', 'action' => 'default', 'id' => NULL, ]); ~~~ 将生成英语网址,例如/ product / detail / 123,/ cart或/ catalog / view。 如果我们想翻译这些URL,我们可以使用在Route :: FILTER_TABLE键下定义的字典。 所以: ~~~ $route = new Route('<presenter>/<action>/<id>', [ 'presenter' => [ Route::VALUE => 'Homepage', // default value Route::FILTER_TABLE => [ // translated string in URL => presenter 'produkt' => 'Product', 'einkaufswagen' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::VALUE => 'default', Route::FILTER_TABLE => [ 'sehen' => 'view', ], ], 'id' => NULL, ]); ~~~ Route :: FILTER_TABLE下的多个键可以具有相同的值。 这就是别名的创建方式。 最后一个值是规范的(用于生成链接)。 字典可以应用于任何路径参数。 如果未找到翻译,则使用原始(非翻译)值。 该路线默认接受翻译(例如/ einkaufswagen)和原始(例如/cart)网址。 如果您只想接受翻译的URL,您需要添加Route :: FILTER_STRICT => TRUE到路由定义。 除了将字典设置为数组之外,还可以设置输入和输出过滤器。 输入和输出过滤器分别是在Route :: FILTER_IN和Route :: FILTER_OUT键下定义的回调。 ~~~ $route = new Route('<presenter=Homepage>/<action=default>', [ 'action' => [ Route::FILTER_IN => function($action) { return strrev($action); }, Route::FILTER_OUT => function($action) { return strrev($action); }, ], ]); ~~~ 输入过滤器接受来自URL的值,并应返回将传递给控制器的值。 输出过滤器接受来自presenter的值,并应返回将在URL中使用的值。 如果任何这些过滤器无法过滤值(通常是因为它无效),它应该返回NULL。 ## 全局过滤器 除了特定参数的过滤器之外,还可以定义全局过滤器,它接受具有所有参数的关联数组,并返回具有过滤参数的数组。 全局过滤器在NULL键下定义。 ~~~ $route = new Route('<presenter>/<action>', [ 'presenter' => 'Homepage', 'action' => 'default', NULL => [ Route::FILTER_IN => function(array $params) { // ... return $params; }, Route::FILTER_OUT => function(array $params) { // ... return $params; }, ], ]); ~~~ 您可以使用全局过滤器根据另一个参数的值过滤某些参数,例如 根据<lang>翻译<presenter>和<action>。 ## Foo参数 Foo参数基本上是未命名的参数,允许您匹配正则表达式。 以下路由匹配/ index,/index.html,/index.htm和/index.php: ~~~ $route = new Route('index<? \.html?|\.php|>', 'Homepage:default'); ~~~ 还可以显式定义将用于URL生成的字符串(类似于为实参设置默认值)。 字符串必须直接放在问号后面。 以下路由类似于上一个,但生成/index.html而不是/ index,因为字符串.html被设置为“默认值”。 ~~~ $route = new Route('index<?.html \.html?|\.php|>', 'Homepage:default'); ~~~ ## 模块 在Nette中,我们可以将控制器分成模块。 因此,我们需要在路由中使用这些模块。 我们可以在Route类中使用module参数: ~~~ new Route('admin/<presenter>/<action>', [ 'module' => 'Admin' ]); ~~~ 如果我们有更多的路由,我们想在一个模块中组合在一起,我们可以使用RouteList与第一个参数: ~~~ $adminRouter = new RouteList('Admin'); $adminRouter[] = new Route('admin/<presenter>/<action>'); ~~~ ### 路由调试器 首先使用路线似乎有点神奇。 这就是为什么你会欣赏路由调试器的价值。 这是一个调试器栏面板,它提供了路由器获得的所有参数的列表和所有定义的路由的列表。 它还显示了您当前在哪个控制器和方法。 ![](https://box.kancloud.cn/d168ecaa22903c2a66fe965341da90d1_613x332.png) 如果应用程序在调试模式下运行,默认情况下将启用路由调试器。 不过,您可以在配置文件中禁用它: ~~~ routing: debugger: off # on by default ~~~ ### SEO和规范化 框架增加SEO(搜索引擎优化),因为它阻止多个网址链接到不同的内容(没有适当的重定向)。 如果多个地址链接到同一目标(/ index和/index.html),框架选择第一个(它是规范的),并重定向另一个与它的HTTP代码301.感谢你的页面不会 在搜索引擎上有重复,它们的排名不会分裂。 这整个过程称为规范化。 默认(规范)URL是一个路由器生成的,即集合中的第一个路由不返回NULL并且没有ONE_WAY标志。 规范化由Presenter完成,默认情况下它已打开。 您可以通过将Presenter :: $ autoCanonicalize设置为FALSE来禁用它,例如 在startup()。 Ajax和POST请求不会重定向,因为用户将遭受数据丢失,或者它不会产生额外的SEO值。 ### 模块化 如果我们想在我们的应用程序中添加具有自己的路由的单独的模块,例如讨论论坛,我们可以在其他地方定义路由,可能在createRoutes()方法中: ~~~ class Forum { static function createRoutes($router, $prefix) { $router[] = new Route($prefix . 'index.php', 'Forum:Homepage:default'); $router[] = new Route($prefix . 'admin.php', 'Forum:Admin:default'); ... } } ~~~ “Routing-in”的论坛到现有的应用程序可以像调用这个方法一样简单:(bootstrap.php) ~~~ $router = new RouteList; //我们的路线 ... // 添加论坛模块 Forum::createRoutes($router, '//forum.example.com/'); $container->addService('router', $router); ~~~ ### 自定义路由器 如果这些提供的路由不符合您的需要,您可以创建自己的路由器,并将其添加到路由器集合。 路由器只是一个IRouter的实现,它有两种方法: ~~~ use Nette\Application\Request as AppRequest; use Nette\Http\IRequest as HttpRequest; use Nette\Http\Url; class MyRouter implements Nette\Application\IRouter { function match(HttpRequest $httpRequest) { // ... } function constructUrl(AppRequest $appRequest, Url $refUrl) { // ... } } ~~~ 方法匹配处理一个HttpRequest(它不仅仅提供一个Url)到一个内部Nette \ Application \ Request,它包含演示者名称及其参数。 如果无法处理HTTP请求,则应返回NULL。 方法constructUrl从应用程序请求生成绝对URL,可能使用来自$ refUrl参数的信息。 定制路由器的可能性是无限的,例如可以实现基于数据库记录的路由器。