路由组件
在我们开始深入研究路由组件之前,让我们稍微重构一下当前的框架,使模板更具可读性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$request = Request::createFromGlobals();
$map = [
'/hello' => 'hello',
'/bye' => 'bye',
];
$path = $request->getPathInfo();
if (isset($map[$path])) {
ob_start();
extract($request->query->all(), EXTR_SKIP);
include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]);
$response = new Response(ob_get_clean());
} else {
$response = new Response('Not Found', 404);
}
$response->send();
既然我们现在提取了请求查询参数,请按如下方式简化 hello.php
模板
1 2
<!-- example.com/src/pages/hello.php -->
Hello <?= htmlspecialchars($name ?? 'World', ENT_QUOTES, 'UTF-8') ?>
现在,我们已经准备好添加新功能了。
任何网站一个非常重要的方面是其 URL 的形式。 感谢 URL 映射,我们已经将 URL 与生成关联响应的代码解耦,但它还不够灵活。 例如,我们可能希望支持动态路径,以允许将数据直接嵌入到 URL 中(例如 /hello/Fabien
),而不是依赖查询字符串(例如 /hello?name=Fabien
)。
为了支持此功能,添加 Symfony 路由组件作为依赖项
1
$ composer require symfony/routing
路由组件依赖于 RouteCollection
实例,而不是使用数组作为 URL 映射
1 2 3
use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
让我们添加一个描述 /hello/SOMETHING
URL 的路由,并为简单的 /bye
URL 添加另一个路由
1 2 3 4
use Symfony\Component\Routing\Route;
$routes->add('hello', new Route('/hello/{name}', ['name' => 'World']));
$routes->add('bye', new Route('/bye'));
集合中的每个条目都由一个名称 (hello
) 和一个 Route
实例定义,Route
实例由路由模式 (/hello/{name}
) 和路由属性的默认值数组 (['name' => 'World']
) 定义。
注意
阅读 路由文档 以了解更多关于其众多功能的信息,例如 URL 生成、属性要求、HTTP 方法强制、YAML 或 XML 文件的加载器、PHP 或 Apache 重写规则的转储器以提高性能等等。
基于存储在 RouteCollection
实例中的信息,UrlMatcher
实例可以匹配 URL 路径
1 2 3 4 5 6 7 8
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
$context = new RequestContext();
$context->fromRequest($request);
$matcher = new UrlMatcher($routes, $context);
$attributes = $matcher->match($request->getPathInfo());
match()
方法接受请求路径并返回属性数组(请注意,匹配的路由会自动存储在特殊的 _route
属性下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
$matcher->match('/bye');
/* Result:
[
'_route' => 'bye',
];
*/
$matcher->match('/hello/Fabien');
/* Result:
[
'name' => 'Fabien',
'_route' => 'hello',
];
*/
$matcher->match('/hello');
/* Result:
[
'name' => 'World',
'_route' => 'hello',
];
*/
注意
即使在我们的示例中我们并不严格需要请求上下文,但在实际应用中会使用它来强制执行方法要求等。
当没有路由匹配时,URL 匹配器会抛出异常
1 2 3
$matcher->match('/not-found');
// throws a Symfony\Component\Routing\Exception\ResourceNotFoundException
考虑到这些知识,让我们编写新版本的框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing;
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';
$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
try {
extract($matcher->match($request->getPathInfo()), EXTR_SKIP);
ob_start();
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
$response = new Response(ob_get_clean());
} catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
} catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
$response->send();
代码中有一些新内容
- 路由名称用于模板名称;
- 现在可以正确管理
500
错误; - 提取请求属性以保持模板简洁
1 2
// example.com/src/pages/hello.php
Hello <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
路由配置已移至其自己的文件
1 2 3 4 5 6 7 8
// example.com/src/app.php use Symfony\Component\Routing; $routes = new Routing\RouteCollection(); $routes->add('hello', new Routing\Route('/hello/{name}', ['name' => 'World'])); $routes->add('bye', new Routing\Route('/bye')); return $routes;
现在,我们在配置(app.php
中特定于我们应用程序的所有内容)和框架(front.php
中为我们应用程序提供支持的通用代码)之间有了明确的分离。
使用少于 30 行代码,我们就拥有了一个新的框架,它比以前的框架更强大、更灵活。 尽情享用吧!
使用路由组件有一个很大的额外好处:能够基于路由定义生成 URL。 当在代码中同时使用 URL 匹配和 URL 生成时,更改 URL 模式应该没有其他影响。 您可以这样使用生成器
1 2 3 4 5 6
use Symfony\Component\Routing;
$generator = new Routing\Generator\UrlGenerator($routes, $context);
echo $generator->generate('hello', ['name' => 'Fabien']);
// outputs /hello/Fabien
代码应该是自解释的;并且由于上下文,您甚至可以生成绝对 URL
1 2 3 4 5 6 7 8
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
echo $generator->generate(
'hello',
['name' => 'Fabien'],
UrlGeneratorInterface::ABSOLUTE_URL
);
// outputs something like http://example.com/somewhere/hello/Fabien
提示
担心性能? 基于您的路由定义,创建一个高度优化的 URL 匹配器类,它可以替换默认的 UrlMatcher
1 2 3 4 5 6 7 8
use Symfony\Component\Routing\Matcher\CompiledUrlMatcher;
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper;
// $compiledRoutes is a plain PHP array that describes all routes in a performant data format
// you can (and should) cache it, typically by exporting it to a PHP file
$compiledRoutes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes();
$matcher = new CompiledUrlMatcher($compiledRoutes, $context);