模板

聪明的读者已经注意到了,我们框架中具体代码(模板)的运行方式是硬编码的。对于至今我们创建过的那些简单页面来说,这是没有问题的,但是如果你想添加更多的逻辑,你只能强制将逻辑代码放进模板里面。特别是如果你的脑中仍然记得关注点分离的准则的话,你会知道这不是一个好主意。

让我们通过添加一个新的层来将模板的代码从逻辑中分离出来: 控制器的目的是基于来自客户端请求的信息生成一个响应。

像下面一样改变框架的模板渲染部分:

// 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的另一个被称作属性的特性:请求属性是一种获取请求的一些额外信息,这些信息与HTTP请求数据不直接相关。

你现在创建了 render_template() 函数,一个通过的控制器,可以去渲染一个没有具体逻辑的模板。为了保持和之前相同的模板,请求属性在模板渲染前就被提取了出来:

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回调函数来取代它。这允许我们去使用一个函数来当做一个控制器,匿名函数或者类的方法...你自己选择。

通常的, 对于每一个路由,它所关联的控制器由_controller路由属性配置:

$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);
    }
)));

这是相当灵活的,因为你现在既可以在之后改变响应的对象,甚至还可以传递给模板额外的参数:

$routes->add('hello', new Routing\Route('/hello/{name}', array(
    'name' => 'World',
    '_controller' => function ($request) {
        // $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;
    }
)));

下面就是我们框架更新后的增强版本:

// 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文件:

// 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, 会使用当前年份去判断。控制器很简单:它从请求属性中获取年份,将它创递给is_leap_year() 函数,然后根据返回值创建一个新的响应对象。

如往常一样,你可以决定就此止步,使用现在的这个框架;也许你需要的就是去创建一些简单的网站,就像这些很棒的网站或者其他的一些。

results matching ""

    No results matching ""