模板
敏锐的读者可能已经注意到,我们的框架硬编码了运行特定“代码”(模板)的方式。对于像我们目前创建的简单页面来说,这不是问题,但是如果您想添加更多逻辑,您将被迫将逻辑放入模板本身,这可能不是一个好主意,尤其是在您仍然牢记关注点分离原则的情况下。
让我们通过添加一个新层来将模板代码与逻辑分离:控制器:控制器的任务是根据客户端请求传递的信息生成响应。
更改框架的模板渲染部分,使其如下所示
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 $exception) {
$response = new Response('Not Found', 404);
} catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
由于渲染现在由外部函数(render_template()
此处为)完成,我们需要将从 URL 中提取的属性传递给它。我们可以将它们作为 render_template()
的附加参数传递,但相反,让我们使用 Request
类的另一个特性,称为属性:请求属性是一种附加关于请求的附加信息的方式,这些信息与 HTTP 请求数据没有直接关系。
您现在可以创建 render_template()
函数,这是一个通用控制器,在没有特定逻辑时渲染模板。为了保持与之前相同的模板,请求属性在模板渲染之前被提取。
1 2 3 4 5 6 7 8
function render_template(Request $request): Response
{
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 回调替换它。这允许我们使用函数、匿名函数或类的方法作为控制器... 由您选择。
按照惯例,对于每个路由,关联的控制器都通过 _controller
路由属性配置。
1 2 3 4 5 6 7 8 9 10 11 12 13
$routes->add('hello', new Routing\Route('/hello/{name}', [
'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 $exception) {
$response = new Response('Not Found', 404);
} catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
现在路由可以与任何控制器关联,并且在控制器中,您仍然可以使用 render_template()
来渲染模板。
1 2 3 4 5 6
$routes->add('hello', new Routing\Route('/hello/{name}', [
'name' => 'World',
'_controller' => function (Request $request): string {
return render_template($request);
}
]));
这相当灵活,因为您可以在之后更改 Response 对象,甚至可以向模板传递额外的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$routes->add('hello', new Routing\Route('/hello/{name}', [
'name' => 'World',
'_controller' => function (Request $request): Response {
// $foo will be available in the template
$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 $request): Response
{
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 $exception) {
$response = new Response('Not Found', 404);
} catch (Exception $exception) {
$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 26 27
// example.com/src/app.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing;
function is_leap_year(?int $year = null): bool
{
if (null === $year) {
$year = (int)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}', [
'year' => null,
'_controller' => function (Request $request): Response {
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
,则测试当前年份。控制器的工作很少:它从请求属性中获取年份,将其传递给 is_leap_year()
函数,并根据返回值创建一个新的 Response 对象。
与往常一样,您可以决定在此处停止并按原样使用该框架;这可能就是您创建简单网站(例如那些精美的单页 网站)以及其他一些网站所需的全部内容。