路由组件
在我们开始学习路由组件前,让我们给当前框架做一点重构,提高模板的可读性:
// 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 = array(
'/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
:
<!-- example.com/src/pages/hello.php -->
Hello <?php echo htmlspecialchars(isset($name) ? $name : 'World', ENT_QUOTES, 'UTF-8') ?>
现在,我们做好的充足的准备去添加新的特性。
任何网站中,URL的格式都是一个非常重要的方面。感谢URL映射,我们将URL从生成关联相应的代码中解耦,但是这仍然不够灵活。例如,我们可能想要去支持可以直接嵌入数据的动态路径(比如/hello/Fabien
),来取代依赖查询字符串的模式(比如/hello?name=Fabien
)。
为了实现这个特性,添加一个新的依赖,Symfony 路由组件:
$ composer require symfony/routing
取代了URL映射的数组,路由组件依靠的是一个RouteCollection
实例:
use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
让我们添加2个路由,一个描述/hello/SOMETHING
的URL,另一个是简单的/bye
:
use Symfony\Component\Routing\Route;
$routes->add('hello', new Route('/hello/{name}', array('name' => 'World')));
$routes->add('bye', new Route('/bye'));
集合中的每个入口都由一个名称 (hello
)和一个Route
实例来定义,Route
实例由一个路由模式(/hello/{name}
) 以及一个路由属性的默认值数组 (array('name' => 'World')
)来定义。
阅读 Routing component documentation 来了解路由组件的其他特性,例如URL迭代,属性需求,HTTP模式执行,YAML 或者 XML文件加载器,转储到PHP或Apache重写规则以提高性能等等。
根据储存在RouteCollection
实例中的信息,一个UrlMatcher
实例可以匹配URL的路径:
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()
方法获取一个请求并返回一个属性的数组(注意,匹配到的路由会自动存储在特殊的_route
属性中):
print_r($matcher->match('/bye'));
/* Gives:
array (
'_route' => 'bye',
);
*/
print_r($matcher->match('/hello/Fabien'));
/* Gives:
array (
'name' => 'Fabien',
'_route' => 'hello',
);
*/
print_r($matcher->match('/hello'));
/* Gives:
array (
'name' => 'World',
'_route' => 'hello',
);
*/
尽管在我们的例子中没有严格意义上需要请求的上下文,但是在现实中的应用可以被用来实现方法需求等。
如果没有可以匹配的路由,URL匹配器抛出一个异常:
$matcher->match('/not-found');
// throws a Symfony\Component\Routing\Exception\ResourceNotFoundException
知道了这一点,我们可以写一个新版本的框架了:
// 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();
在上述代码中有几个要点:
路由的名称也被用做模板的名称;
500
的错误被正确的管理;请求的属性被提取出来,保证了模板的简易性:
<!-- example.com/src/pages/hello.php -->
Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-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行的代码,我们拥有了一个新的,更加强力,更加灵活的框架。尽情玩耍吧!
使用路由组件有一个的额外的好处:基于路由定义生成URL的能力。当在代码中同时使用URL匹配和URL迭代,改变URL模式不会有其他的影响。想要知道怎么使用迭代器?很简单:
use Symfony\Component\Routing;
$generator = new Routing\Generator\UrlGenerator($routes, $context);
echo $generator->generate('hello', array('name' => 'Fabien'));
// outputs /hello/Fabien
这段代码是很容易理解的;感谢上下文,你甚至能够生成抽象的URL:
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匹配器类可以取代默认的
UrlMatcher
:
$dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes);
echo $dumper->dump();