支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
眼尖的读者已经注意到,我们的框架写死了“特定代码”(即模板)运行的方式。对于简单的页面,像我们目前创建的这些,这样没什么问题,但如果你要添加更多逻辑,你只能被迫把逻辑部分放到模板本身,这可能不是好主意,特别是当你坚持“seperation of concerns”(关联分离)原则时。
通过添加一个新层(a new layer),我们把模板代码从逻辑中分离出来:控制器——控制器的任务,是基于客户端请求所传递的信息,生成一个响应。
把框架的模板渲染部分,改成下面这样:
1 2 3 4 5 6 7 8 9 10 11 | // example.com/web/front.php
// ...
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func('render_template', $request);
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
} |
由于渲染的完成依靠的是一个外部函数(这里是render_template()
),我们需要对它传入提取自URL的属性(attributes)。我们已经把它们作为render_template()
的一个附加参数(argument),但不同的是,我们使用了Request
类的另一个功能,被称之为attrubutes(属性):Request属性是添加“和请求有关、但不直接同HTTP请求数据相关”的附加信息的一种方式。
现在你可以创建render_template()
函数了,也就是一个通用的控制器,在没有特殊逻辑时渲染模板。保持模板同先前一样,request attributes在模板被渲染之前已经被提取出来:
1 2 3 4 5 6 7 8 | function render_template($request)
{
extract($request->attributes->all(), EXTR_SKIP);
ob_start();
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
return new Response(ob_get_clean());
} |
由于render_template
被用于PHP的call_user_func()
函数,我们可以用任何有效的PHP callbacks来替换它。这令我们可以把一个函数,一个匿名函数或类中的一个方法作为一个控制器...你想选哪个都行。
作为一个约定,对于每个路由来说,其关联的控制器被配置在_controller
这个路由属性中(route attribute):
1 2 3 4 5 6 7 8 9 10 11 12 13 | $routes->add('hello', new Routing\Route('/hello/{name}', array(
'name' => 'World',
'_controller' => 'render_template',
)));
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func($request->attributes->get('_controller'), $request);
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
} |
现在,一个路由可以与任何控制器进行关联,当然在控制器里,你还是可以使用render_template()
来渲染模板:
1 2 3 4 5 6 | $routes->add('hello', new Routing\Route('/hello/{name}', array(
'name' => 'World',
'_controller' => function ($request) {
return render_template($request);
}
))); |
这样一来灵活度更高,因为你可以在后面改变Response对象,甚至可以传递附加参数到模板中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $routes->add('hello', new Routing\Route('/hello/{name}', array(
'name' => 'World',
'_controller' => function ($request) {
// $foo will be available in the template
// $foo 可用于模板中
$request->attributes->set('foo', 'bar');
$response = render_template($request);
// change some header 修改头信息
$response->headers->set('Content-Type', 'text/plain');
return $response;
}
))); |
以下是我们更新过的改进版本框架:
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 28 29 30 31 32 33 | // 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;
function render_template($request)
{
extract($request->attributes->all(), EXTR_SKIP);
ob_start();
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
return new Response(ob_get_clean());
}
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';
$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$response = call_user_func($request->attributes->get('_controller'), $request);
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
}
$response->send(); |
为了庆祝新框架的诞生,我们再创建一个全新的程序,它需要一些逻辑。我们的程序有一个页面,可以告之一个给定的年份是否为闰年。当请求/is_leap_year
时,你可以得到当前年份的答案,但你也可以指定一个年份如/is_leap_year/2009
。基本上,框架不需要改变什么,直接创建一个新的app.php
文件即可:
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 | // example.com/src/app.php
use Symfony\Component\Routing;
use Symfony\Component\HttpFoundation\Response;
function is_leap_year($year = null) {
if (null === $year) {
$year = date('Y');
}
return 0 === $year % 400 || (0 === $year % 4 && 0 !== $year % 100);
}
$routes = new Routing\RouteCollection();
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
'year' => null,
'_controller' => function ($request) {
if (is_leap_year($request->attributes->get('year'))) {
return new Response('Yep, this is a leap year!');
}
return new Response('Nope, this is not a leap year.');
}
)));
return $routes; |
当给定年份是闰年时,is_leap_year()
函数返回true
,否则是false
。如果年份是null
,当前年份被使用。控制器是很简单的:它从request attributes中拿到年份,然后传给is_leap_year()
函数,再根据返回值来创建一个Response对象。
一如往常,你可以决定止步于此,来使用当前的框架:它差不多已经够你创建简单网站用了,像是那些精美的one-page单页website,乃至其他一些。
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。