跳到内容

模板

编辑此页

敏锐的读者可能已经注意到,我们的框架硬编码了运行特定“代码”(模板)的方式。对于像我们目前创建的简单页面来说,这不是问题,但是如果您想添加更多逻辑,您将被迫将逻辑放入模板本身,这可能不是一个好主意,尤其是在您仍然牢记关注点分离原则的情况下。

让我们通过添加一个新层来将模板代码与逻辑分离:控制器:控制器的任务是根据客户端请求传递的信息生成响应。

更改框架的模板渲染部分,使其如下所示

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 对象。

与往常一样,您可以决定在此处停止并按原样使用该框架;这可能就是您创建简单网站(例如那些精美的单页 网站)以及其他一些网站所需的全部内容。

这项工作,包括代码示例,均根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本