使用 Symfony 的组件创建自己的 PHP 框架(第五部分:模板)

使用 Symfony 的组件创建自己的 PHP 框架(第五部分:模板)

Chris Yue No Comment
Posts

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

细心的读者可能已经发现,之前我们“写死”了一些代码在模板文件里面(译者注:比如说之前的 $input = $request->get('name', 'World')这种代码)。对于目前我们写的这些小页面,问题倒也不大。但如果你想写更多的逻辑代码,那你只能把它们写在模板文件里面,这是非常不好的做法,特别是对于我们本来就是为了达到分工这个目的而做这个框架的,更不能这么搞。

为了把逻辑代码从模板文件里面分离出来,我们再加一个“控制器”层。控制器的主要作用是根据得到的客户端请求,生成相应的响应内容。

修改模板渲染代码如下:

<?php

// 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 里面带的参数解析出来,并作为参数传递给这个函数。我们可以这么做,但是我们可以利用 Request 类的另外一个叫属性的功能:Request 类的属性可以让你添加其他的信息,而且这些信息不一定非要跟请求的数据有直接的联系。

现在我们开始写 render_template() 函数了,让它作为一个没有其他任何逻辑代码的通用控制器。为了让我们之前写的模板代码还是能如之前一样工作,我们还是将请求对象的属性值都“解压”(译者注:利用 PHP 的 extract 函数)出来:

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 作为 call_user_func 函数的一个参数。它也完全可以由任何合法的 PHP 回调函数来代替。这便可以随意让我们使用函数,或者匿名函数,甚至类方法来做回调函数。

为了使用方便,我们把每个路由规则都加上控制器属性:

$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 方法:

$routes->add('hello', new Routing\Route('/hello/{name}', array(
    'name' => 'World',
    '_controller' => function ($request) {
        return render_template($request);
    }
)));

这样做会更加灵活,因为你可以在生成模板之前以及之后对 Response 对象进行修改,你甚至可以传更多的参数给模板:

$routes->add('hello', new Routing\Route('/hello/{name}', array(
    'name' => 'World',
    '_controller' => function ($request) {
        // $foo将在模板里可见
        $request->attributes->set('foo', 'bar');

        $response = render_template($request);

        // 改变一些头信息
        $response->headers->set('Content-Type', 'text/plain');

        return $response;
    }
)));

以下是更新改进过后的框架代码:

<?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;

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 文件稍加变动:

<?php

// 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() 函数会告知我们某一年是不是闰年,如果传入参数,那么就检查今年是不是闰年,然后根据它返回的结果,我们能分别创造出不同的响应结果。

一如往常,如果你在此打算停止继续往下阅读了并且打算开始使用当前的框架,那你最好能利用这框架创建一个简单的网站,比如那些有趣的“单页”网站(译者注:“单页”网站,one-page website,是指一种一个网站就一个页面的网站,读者们可以点击单页网站的链接参观参观,的确非常“有趣”,它们比起今天做的判断是否闰年的功能还要简单 n 倍……很蛋疼)。

返回阅读第四部分 | 继续阅读第六部分

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

微信赞赏码

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

发表评论

85 + = 86

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 全栈 到底系列 命令行 安全 教程 框架 算法 翻译