使用 Symfony 组件创建自己的 PHP 框架(第四部分:路由组件)

使用 Symfony 组件创建自己的 PHP 框架(第四部分:路由组件)

Chris Yue 5 comments
Posts

英文原文地址:https://symfony.com/doc/current/create_framework/routing.html

在开始我们今天的话题前,我们先重构一下我们的框架,让我们的模板文件更加易读:

<?php

// example.com/web/front.php

require_once __DIR__.'/../src/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();

由于我们将请求里面的 GET 参数解压(extract)出来了,我们就可以简化模板代码:

<!-- example.com/src/pages/hello.php -->

Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>

现在我们的代码将可以以更好的状态来添加新的功能了。

任何一个网站都有一个重要的要素,那就是他们的 URL 的形式。多亏了有 $map 变量这个映射表,我们将 URL 以及跟他关联的响应这两部分代码从我们的代码里面解耦出来了,但目前还是不够灵活。举个例子,如果你想动态生成 URL,并将 URL 中的一个部分来代替 GET 参数:

# 之前
/hello?name=Fabien

# 之后
/hello/Fabien

想添加这个功能?请先添加 sf 路由组件

$ composer require symfony/routing

用来代替之前$map这个映射关系数组的,是路由组件,它依赖于 RouteCollection 实例来描述映射关系:

use Symfony\Component\Routing\RouteCollection;

$routes = new RouteCollection();

让我添加两条路由规则,一条是 /hello/SOMETHING,另一条是简单的 /bye

use Symfony\Component\Routing\Route;

$routes->add('hello', new Route('/hello/{name}', array('name' => 'World')));
$routes->add('bye', new Route('/bye'));

每一条路由规则都由一个名字(hello)以及一个 Route 实例来定义,而一条路由实例又由一条路由规则(/hello/{name})以及默认值数组(array('name' => 'World'))来定义。

请阅读官方文档学会路由组件其他更多功能,比如 URL 生成器,属性限制,HTTP 方法限制,YAML,XML 配置载入器,规则转储为 PHP 文件甚至 Apache 的 URL 重写规则来获取性能上的提升,以及其他更多功能。

基于 RouteCollection 实例里存储的信息,UrlMatch 对象可实现对 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'));
/*array (
  '_route' => 'bye',
);*/

print_r($matcher->match('/hello/Fabien'));
/*array (
  'name' => 'Fabien',
  '_route' => 'hello',
);*/

print_r($matcher->match('/hello'));
/*array (
  'name' => 'World',
  '_route' => 'hello',
);*/

我们并不严格要求要使用 $context 参数,但在真正的项目中最好还是加上,因为如果你的路由规则除了匹配 URL 还需要匹配 HTTP 的某个方法(译者注:比如 GET、POST 方法),是必须要这个参数的

如果匹配器找不到任何一个匹配规则,他会抛出一个意外:

$matcher->match('/not-found');

// throws a Symfony\Component\Routing\Exception\ResourceNotFoundException

利用上面的知识点,我们将框架代码重写一下:

<?php

// example.com/web/front.php

require_once __DIR__.'/../vendor/.composer/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 名字作为模板的文件名
  • 500 错误也可以进行控制和管理了
  • 解压后的请求变量让我们的模板文件代码简单许多
    <!-- example.com/src/pages/hello.php -->
    
    Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
  • 路由管理被单独分配到一个文件里面
    <?php
    
    // 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;

    现在我们的框架(front.php 里的代码)和配置文件(所有都在 app.php 文件里配置)有了很明确的分工。

我们用不到 30 行的代码便写好了我们新的框架,比之前那个更灵活更强大了。

使用路由组件还有一个很大的好处:利用路由规则生成 URL。如果你使用路由组件来匹配你的 URL,又使用路由组件来生成你的URL(译者注:也就是传说中的『双向路由』),那么你想更换某个路由的规则,可毫无顾忌对系统的影响。想知道如何利用这个功能生成链接?小菜一碟:

use Symfony\Component\Routing;

$generator = new Routing\Generator\UrlGenerator($routes, $context);

echo $generator->generate('hello', array('name' => 'Fabien'));
// outputs /hello/Fabien

代码非常明了,根本不用再重新说明过程了;然后,多亏了有 context 你甚至可以生成全路径:

echo $generator->generate('hello', array('name' => 'Fabien'), true);
// outputs something like http://example.com/somewhere/hello/Fabien

译者注:任何前段控制器框架,或者说单点入口框架,都会面对路由器性能问题,这个问题甚至被 PHP 之父作为“反对使用框架”的论点之一。事实上,如果一个项目有几十个甚至上百个路由规则,路由器性能的确是一个头痛的问题。sf 的路由转存组件将路由转存为 Apache 的 URL 改写规则,将本来 PHP 就不擅长的路由工作交给特别擅长此工作的 web 服务器,的确是个很靠谱的创新。对性能要求较高的同学可以考虑尝试一下。另外我想既然 Apache 的改写能做,Nginx 的改写规则也应该不远了 2017-01-18 补充:在 Symfony3 里已经不推荐将路由规则转储为 Apache,官方的描述是『为了一点点的性能提升,而需要每次在路由规则有变化的时候去更新 Apache 的改写规则是不划算的』。可能 Symfony3 在路由性能这方面已经优化得足够好了,再加上 PHP7 的性能提升,已经没有必要这么做了

返回阅读第三部分 | 继续阅读第五部分

使用 Symfony 组件创建自己的 PHP 框架(第四部分:路由组件) by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

写作累,服务器还越来越贵
求分担,祝愿好人一生平安
天使打赏人

5 Comments

duqingnian

10月 2, 2012 在 5:18 下午

请教博主,为什么在替换autoload这步骤的时候require_once __DIR__.’/../vendor/.composer/autoload.php’; 我没有.composer 我只有composer 没有前面的小数点 而且里面没有autoload.php这个文件啊 , 何解?

 回复

    Chris Yue

    10月 12, 2012 在 2:04 下午

    开发环境以及composer版本说一下呢?

     

    Chris Yue

    11月 10, 2012 在 11:46 上午

    我今天看了一下,现在的autoloader.php的确是放在了vendor根目录下面

     

redfire.du

3月 18, 2012 在 9:33 下午

请教博主,为什么我这边老是提示Call to undefined method Symfony\Component\Routing\RequestContext::fromRequest()呢。

 回复

    Chris Yue

    3月 21, 2012 在 10:34 上午

    你能提供一下完整的代码吗?

     

发表评论

19 − 10 =

2021年4月
 1234
567891011
12131415161718
19202122232425
2627282930  
Composer CSS Firefox industrial metal JavaScript LeetCode Linux MySQL NGINX nu metal OAuth PHP PHP7 Shell Symfony Ubuntu Vim 全栈 到底系列 命令行 安全 教程 框架 算法 翻译