nette / routing
Nette Routing:双向URL转换
Requires
- php: 8.1 - 8.3
- nette/http: ^3.2 || ~4.0.0
- nette/utils: ^4.0
Requires (Dev)
- nette/tester: ^2.5
- phpstan/phpstan: ^1
- tracy/tracy: ^2.9
This package is auto-updated.
Last update: 2024-09-18 22:15:13 UTC
README
Nette Routing:双向URL转换
简介
路由器负责所有与URL相关的事务,因此您不再需要考虑它们。我们将展示
- 如何设置路由器,使URL看起来像您希望的那样
- 关于SEO重定向的一些注意事项
- 以及如何编写自己的路由器
需要PHP版本8.1,并支持PHP 8.4。
文档可以在网站上找到。
支持我
您喜欢Nette Routing吗?您期待新功能吗?
谢谢!
基础
更人性化的URL(或酷或美观的URL)更易用、更易记,并对SEO产生积极影响。Nette 框架牢记这一点,完全满足了开发者的需求。
让我们从技术上开始。路由器是一个实现了Nette\Routing\Router接口的对象,它可以分解URL为一个参数数组(方法match
)和反过来,从一个参数数组构建一个URL(方法constructUrl
)。因此,它也被说成是双向的。Nette 提供了一种非常优雅的方式来定义应用程序的URL看起来像什么。
您可以在完全不同的场景中使用它,例如 REST API、不使用控制器的应用程序等。
因此,路由是应用程序的一个独立而复杂的层,这使得在应用程序准备就绪时可以轻松设计或更改URL地址的外观,因为它可以在不修改代码或模板的情况下完成。这为开发者提供了巨大的自由度。
路由集合
在应用程序中定义URL地址最令人愉悦的方式是通过Nette\Routing\RouteList类。最大的优点是整个路由器在一个地方定义,而不是以注解的形式分散在所有控制器中。
定义由所谓的路由组成,即URL掩码及其关联的控制器和操作,使用简单的API。我们不必命名路由。
$router = new Nette\Routing\RouteList; $router->addRoute('rss.xml', [ 'controller' => 'RssFeedController', ]); $router->addRoute('article/<id \d+>', [ 'controller' => 'ArticleController', ]); ...
路由的顺序很重要,因为它们是按顺序从第一个到最后的顺序尝试的。基本规则是从最具体到最一般地声明路由。
现在我们必须让路由器开始工作
$params = $router->match($httpRequest); if ($params === null) { // no matching route found, we will send a 404 error exit; } // we process the received parameters $controller = $params['controller']; ...
相反,我们将使用路由器来创建链接
$params = ['controller' => 'ArticleController', 'id' => 123]; $url = $router->constructUrl($params, $httpRequest->getUrl());
掩码和参数
掩码描述了基于网站根目录的相对路径。最简单的掩码是静态URL
$router->addRoute('products', ...);
掩码通常包含所谓的参数。它们被括号包围(例如<year>
)。
$router->addRoute('chronicle/<year>', ...);
我们可以在掩码中直接指定参数的默认值,从而使它成为可选的
$router->addRoute('chronicle/<year=2020>', ...);
现在路由将接受URL https://any-domain.com/chronicle/
,它将再次显示参数year: 2020
。
掩码不仅可以描述基于网站根目录的相对路径,还可以描述以斜杠开始的绝对路径,甚至以两个斜杠开始的整个绝对URL
// relative path to application document root $router->addRoute('<controller>/<action>', ...); // absolute path, relative to server hostname $router->addRoute('/<controller>/<action>', ...); // absolute URL including hostname (but scheme-relative) $router->addRoute('//<lang>.example.com/<controller>/<action>', ...); // absolute URL including schema $router->addRoute('https://<lang>.example.com/<controller>/<action>', ...);
验证表达式
可以使用正则表达式为每个参数指定验证条件。例如,让我们将id
设置为仅数值,使用\d+
正则表达式
$router->addRoute('<controller>/<action>[/<id \d+>]', ...);
所有参数的默认正则表达式是 [^/]+
,即除了斜杠之外的所有内容。如果参数应该匹配斜杠,我们将正则表达式设置为 .+
。
// accepts https://example.com/a/b/c, path is 'a/b/c' $router->addRoute('<path .+>', ...);
可选序列
方括号表示掩码的可选部分。掩码的任何部分都可以设置为可选,包括包含参数的部分
$router->addRoute('[<lang [a-z]{2}>/]<name>', ...); // Accepted URLs: Parameters: // /en/download lang => en, name => download // /download lang => null, name => download
当然,当参数是可选序列的一部分时,它也变为可选。如果没有默认值,它将是 null。
可选部分也可以在域中
$router->addRoute('//[<lang=en>.]example.com/<controller>/<action>', ...);
序列可以自由嵌套和组合
$router->addRoute( '[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]', ... ); // Accepted URLs: // /cs/hello // /en-us/hello // /hello // /hello/page-12
URL生成器尝试使URL尽可能短,因此可以省略的内容将被省略。因此,例如,路由 index[.html]
生成路径 /index
。您可以通过在左方括号后写一个感叹号来反转此行为
// accepts both /hello and /hello.html, generates /hello $router->addRoute('<name>[.html]', ...); // accepts both /hello and /hello.html, generates /hello.html $router->addRoute('<name>[!.html]', ...);
没有方括号的可选参数(即具有默认值的参数)的行为类似于这样包装
$router->addRoute('<controller=Homepage>/<action=default>/<id=>', ...); // equals to: $router->addRoute('[<controller=Homepage>/[<action=default>/[<id>]]]', ...);
要更改最右侧斜杠的生成方式,即从 /homepage/
获取 /homepage
,请按这种方式调整路由
$router->addRoute('[<controller=Homepage>[/<action=default>[/<id>]]]', ...);
通配符
在绝对路径掩码中,我们可以使用以下通配符来避免,例如,写入掩码中可能在不同开发和生产环境中不同的域
%tld%
=顶级域名,例如com
或org
%sld%
=二级域名,例如example
%domain%
=不带子域的域名,例如example.com
%host%
=整个主机,例如www.example.com
%basePath%
=根目录的路径
$router->addRoute('//www.%domain%/%basePath%/<controller>/<action>', ...); $router->addRoute('//www.%sld%.%tld%/%basePath%/<controller>/<action', ...);
第二个参数
路由的第二个参数是各个参数默认值的数组
$router->addRoute('<controller>/<action>[/<id \d+>]', [ 'controller' => 'Homepage', 'action' => 'default', ]);
或者我们可以使用这种形式,注意验证正则表达式的重写
use Nette\Routing\Route; $router->addRoute('<controller>/<action>[/<id>]', [ 'controller' => [ Route::Value => 'Homepage', ], 'action' => [ Route::Value => 'default', ], 'id' => [ Route::Pattern => '\d+', ], ]);
这些更详细的形式对于添加其他元数据很有用。
过滤器与翻译
编写源代码时使用英语是一种好习惯,但如果您需要网站具有不同语言的翻译URL怎么办?简单的路由如下
$router->addRoute('<controller>/<action>', [...]);
将生成英语URL,如 /product/123
或 /cart
。如果我们希望URL中的控制器和操作翻译成德语(例如,/produkt/123
或 /einkaufswagen
),我们可以使用翻译字典。要添加它,我们需要第二个参数的“更详细”的变体
use Nette\Routing\Route; $router->addRoute('<controller>/<action>', [ 'controller' => [ Route::Value => 'Homepage', Route::FilterTable => [ // string in URL => controller 'produkt' => 'Product', 'einkaufswagen' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ 'liste' => 'list', ], ], ]);
可以使用多个字典键为同一个控制器。它们将为其创建各种别名。最后一个键被认为是规范变体(即将包含在生成的URL中的那个)。
可以通过这种方式将翻译表应用于此处的任何参数。但是,如果不存在翻译,则取原始值。我们可以通过添加 Router::FILTER_STRICT => true
来更改此行为,然后路由将拒绝不包含在字典中的值。
除了数组形式的翻译字典外,还可以设置自己的翻译函数
use Nette\Routing\Route; $router->addRoute('<controller>/<action>/<id>', [ 'controller' => [ Route::Value => 'Homepage', Route::FilterIn => function (string $s): string { ... }, Route::FilterOut => function (string $s): string { ... }, ], 'action' => 'default', 'id' => null, ]);
函数 Route::FILTER_IN
在URL中的参数和传递给控制器的字符串之间进行转换,然后函数 FILTER_OUT
确保反向转换。
全局过滤器
除了特定参数的过滤器之外,您还可以定义全局过滤器,它接收可以以任何方式修改的所有参数的关联数组,然后返回。全局过滤器在 null
键下定义。
use Nette\Routing\Route; $router->addRoute('<controller>/<action>', [ 'controller' => 'Homepage', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { ... }, Route::FilterOut => function (array $params): array { ... }, ], ]);
全局过滤器使您能够以任何方式调整路由的行为。例如,我们可以使用它们根据其他参数修改参数。例如,基于参数 <lang>
的当前值的翻译 <controller>
和 <action>
。
如果参数同时定义了自定义过滤器和一个全局过滤器,则先执行自定义的FILTER_IN
,然后是全局的,反之亦然,全局的FILTER_OUT
先于自定义执行。因此,在全局过滤器中,参数的值以PascalCase resp. camelCase风格写入controller
resp. action
。
单向标志
单向路由用于保留应用程序不再生成但仍接受的旧URL的功能。我们用oneWay
标记它们。
// old URL /product-info?id=123 $router->addRoute('product-info', [...], oneWay: true); // new URL /product/123 $router->addRoute('product/<id>', [...]);
访问旧URL时,控制器会自动重定向到新URL,这样搜索引擎就不会对这些页面进行两次索引(见 [#SEO 和规范化])。
子域名
路由集合可以按子域名分组
$router = new RouteList; $router->withDomain('example.com') ->addRoute('rss', [...]) ->addRoute('<controller>/<action>');
您还可以在域名中使用 [#通配符]
$router = new RouteList; $router->withDomain('example.%tld%') ...
路径前缀
路由集合可以按URL中的路径分组
$router = new RouteList; $router->withPath('/eshop') ->addRoute('rss', [...]) // matches URL /eshop/rss ->addRoute('<controller>/<action>'); // matches URL /eshop/<controller>/<action>
组合
上述用法可以组合使用
$router = (new RouteList) ->withDomain('admin.example.com') ->addRoute(...) ->addRoute(...) ->end() ->withDomain('example.com') ->withPath('export') ->addRoute(...) ...
查询参数
掩码还可以包含查询参数(URL中问号后面的参数)。它们不能定义验证表达式,但可以更改它们传递给控制器的名称
// use query parameter 'cat' as a 'categoryId' in application $router->addRoute('product ? id=<productId> & cat=<categoryId>', ...);
Foo 参数
现在我们将更深入地探讨。Foo参数基本上是无名的参数,允许匹配正则表达式。以下路由匹配/index
、/index.html
、/index.htm
和/index.php
$router->addRoute('index<? \.html?|\.php|>', ...);
也可以显式定义一个用于URL生成的字符串。该字符串必须直接放置在问号之后。以下路由与上一个类似,但生成/index.html
而不是/index
,因为字符串.html
被设置为“生成值”。
$router->addRoute('index<?.html \.html?|\.php|>', ...);
SimpleRouter
与路由集合相比,SimpleRouter要简单得多。SimpleRouter可以在不需要特定URL格式、没有mod_rewrite
(或替代方案)或我们尚未打算使用用户友好的URL时使用。
生成地址的大致形式
http://example.com/?controller=Product&action=detail&id=123
SimpleRouter
构造函数的参数是默认控制器 & 动作,即当我们打开例如http://example.com/
而不带额外参数时将执行的动作。
$router = new Nette\Application\Routers\SimpleRouter();
SEO 和规范化
该框架通过防止在不同URL上重复内容来提高SEO(搜索引擎优化)。如果有多个地址链接到相同的目的地,例如/index
和/index.html
,则框架确定第一个为主要的(规范化的),并使用HTTP代码301将其余的重定向到它。因此,搜索引擎不会对页面进行两次索引,也不会破坏它们的页面排名。
这个过程称为规范化。规范化的URL是路由器生成的URL,即集合中第一个匹配的路由,没有OneWay标志。因此,在集合中,我们首先列出主要路由。
规范化由控制器执行,更多内容请参阅规范化章节。
HTTPS
为了使用HTTPS协议,需要在托管服务上激活它并配置服务器。
整个站点重定向到HTTPS必须在服务器级别执行,例如使用应用程序根目录中的.htaccess文件,并使用HTTP代码301。设置可能因托管服务而异,看起来像这样
<IfModule mod_rewrite.c> RewriteEngine On ... RewriteCond %{HTTPS} off RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] ... </IfModule>
路由器生成的URL与页面加载时使用的协议相同,因此不需要设置其他内容。
但是,如果我们例外地需要不同路由在不同的协议下运行,我们将它在路由掩码中设置
// Will generate an HTTP address $router->addRoute('http://%host%/<controller>/<action>', ...); // Will generate an HTTPS address $router->addRoute('https://%host%/<controller>/<action>', ...);
路由调试器
我们不会隐瞒,一开始路由可能看起来有点神奇,在你深入之前,路由调试器将是一个很好的助手。这是一个在Tracy栏中显示的面板,它提供了一个清晰的路线列表以及路由器从URL中获得的参数。
带有✓符号的绿色栏表示与当前URL匹配的路线,带有≈符号的蓝色栏表示如果绿色没有超过它们,也会匹配URL的路线。我们还可以看到当前的控制器 & 操作。
自定义路由器
以下代码是为非常高级的用户准备的。你可以创建自己的路由器,并将其自然地添加到你的路线集合中。路由器是Router
接口的实现,包含两个方法
use Nette\Http\IRequest as HttpRequest; use Nette\Http\UrlScript; class MyRouter implements Nette\Routing\Router { public function match(HttpRequest $httpRequest): ?array { // ... } public function constructUrl(array $params, UrlScript $refUrl): ?string { // ... } }
方法match
处理当前请求(在$httpRequest
参数中,[http-request-response#HTTP request](http-request-response#HTTP request)中提供了不仅仅是URL)到包含控制器名称及其参数的数组中。如果它无法处理请求,则返回null。
另一方面,方法constructUrl
从参数数组生成一个绝对URL。它可以使用来自参数$refUrl
的信息,即当前URL。
要将自定义路由器添加到路线集合中,请使用add()
$router = new Nette\Application\Routers\RouteList; $router->add(new MyRouter); $router->addRoute(...); ...