支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
在深入路由组件之前,先轻度重构一下我们的构架,以令模板更具可读性:
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(); |
由于extract了请求中的query parameters,hello.php
模板简化如下:
1 2 | <!-- example.com/src/pages/hello.php -->
Hello <?php echo htmlspecialchars(isset($name) ? $name : 'World', ENT_QUOTES, 'UTF-8') ?> |
现在,我们处在一个良好的(框架)形态中,可以添加新功能了。
任何网站都有一个非常重要的方面,也即它的URLs的组织形式。得益于URL映射(map),我们已经把URL从“负责生成响应的”代码解耦,但这仍然不够灵活。例如,我们可能想要支持动态路径(dynamite paths)来把data直接嵌入到URL中(如/hello/Fabien
),而不是依赖于一个query string(如/hello?name=Fabien
)。
为支持此功能,添加Symofny Routing组件作为依赖:
1 | $ composer require symfony/routing |
不同于使用一个URL映射数组,Routing组件依赖一个RoutingCollection
实例:
1 2 3 | use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection(); |
现在添加路由,描述/hello/SOMETHING
这种URL,再添加另外一个简单的/bye
:
1 2 3 4 | use Symfony\Component\Routing\Route;
$routes->add('hello', new Route('/hello/{name}', array('name' => 'World')));
$routes->add('bye', new Route('/bye')); |
路由集合中的每一个入口(entry),都是通过一个名字(name。如hello
)和一个Route
实例来定义,该实例则通过一个route pattern(路由匹配)和一个路由属性(route attributes)的“默认值”数组来定义(array('name' => 'World'
)。
参考中文版路由组件文档以了解更多功能,比如URL生成、属性条件(attribute requirements)、HTTP方法的强制指定、YAML或XML文件的loader(加载器)、通过PHP/Apache rewrite rules的dumpers(剥离器)来提升性能等等。
基于存放在RouteCollection
实例中的信息,一个UrlMatcher
实例能够匹配URL路径:
1 2 3 4 5 6 7 8 | use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;
$context = new RequestContext();
$context->fromRequest($request);
$matcher = new UrlMatcher($routes, $context);
$attributes = $matcher->match($request->getPathInfo()); |
match()
方法接收一个request路径,返回的是一个属性数组(attributes。注意被匹配的路由(之name)将自动地存在一个特殊的_route()
属性中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
就算我们在例程中不十分需要request context,它在真实世界的程序中,可以用作强制method requirements(HTTP方法的匹配)等功能。
URL matcher在匹配不到路由时会抛出一个异常:
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 $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
}
$response->send(); |
代码中有几个处新内容:
路由名称(route names)被使用在模板名称中;
500
错误现已被正确管理;
请求的属性被提取出来以令模板更加简单:
1 2 | <!-- example.com/src/pages/hello.php -->
Hello <?php echo 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}', array('name' => 'World')));
$routes->add('bye', new Routing\Route('/bye'));
return $routes; |
我们现在得到了配置(程序的每一个细节都在app.php
中)与框架(驱动我们程序的通用代码全在front.php
中)之间的清晰分离。
少于30行代码,我们却得到了一个全新框架,比之前的更加强大,更加灵活。Enjoy!
使用路由组件有一个很大的利益:基于路由定义来生成URLs。当你在代码中同时使用URL匹配和URL生成时,修改一个URL匹配条件时将不会带来其他冲击。想知道如何使用generator(生成器)?超级简单:
1 2 3 4 5 6 | use Symfony\Component\Routing;
$generator = new Routing\Generator\UrlGenerator($routes, $context);
echo $generator->generate('hello', array('name' => 'Fabien'));
// outputs /hello/Fabien |
以上代码能够自我解释。另外得益于context,你甚至可以生成绝对URLs:
1 2 3 4 5 6 7 8 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
echo $generator->generate(
'hello',
array('name' => 'Fabien'),
UrlGeneratorInterface::ABSOLUTE_URL
);
// outputs something like http://example.com/somewhere/hello/Fabien |
关心性能?基于你的路由定义,创建一个优化良好的URL matcher类,即可取代默认的UrlMatcher
:
1 2 3 | $dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes);
echo $dumper->dump(); |
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。