Documentação do Symfony - versão 3.1
Renderizada do repositório symfony-docs-pt-BR no Github

Templates

O leitor astuto percebeu que o nosso framework tem fixa no código a forma como um “código” específico (os templates) é executado. Para páginas simples, como as que criamos até agora, isso não é um problema, mas, se você quiser adicionar mais lógica, seria forçado a adicioná-la no próprio template, o que, provavelmente, não é uma boa idéia, especialmente se você ainda tem o princípio de separação de responsabilidades em mente.

Vamos separar o código do template da lógica adicionando uma nova camada: o controlador: A missão do controlador é gerar uma Resposta com base na informação transmitida pelo Pedido do cliente.

Altere a parte de renderização do template do framework para a seguinte:

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

Como a renderização é feita agora por uma função externa (aqui render_template() ), precisamos passar para ela os atributos extraídos da URL. Poderíamos passá-los como um argumento adicional para o render_template(), mas em vez disso, vamos usar uma outra funcionalidade da classe Request chamada attributes: os atributos do Request permitem anexar informações adicionais sobre o Pedido que não estão diretamente relacionadas aos dados da requisição HTTP.

Agora você pode criar a função render_template(), um controlador genérico que renderiza um template quando não existe uma lógica específica. Para manter o mesmo template de antes, os atributos do Request são extraídos antes do template ser renderizado:

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

Sendo a render_template usada como um argumento para a função PHP call_user_func(), podemos substituí-la por quaisquer callbacks PHP válidas. Isso nos permite usar uma função, uma função anônima, ou um método de uma classe como um controlador... a escolha é sua.

Como uma convenção, para cada rota, o controlador associado é configurado através do atributo de rota _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);
}

Uma rota pode ser associada agora a qualquer controlador e, claro, dentro de um controlador, você ainda pode usar o render_template() para renderizar um template:

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

Isto é bastante flexível pois, você pode alterar o objeto Response posteriormente, e, até mesmo passar argumentos adicionais para o template:

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

Aqui está a versão atualizada e melhorada do nosso framework:

// 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();

Para celebrar o nascimento do nosso novo framework, vamos criar uma nova aplicação que precisa de alguma lógica simples. Nossa aplicação possui uma página que diz se um determinado ano é bissexto ou não. Ao chamar /is_leap_year, você tem a resposta para o ano corrente, mas, você também pode especificar um ano, como em /is_leap_year/2009. Sendo genérico, o framework não precisa ser modificado de qualquer forma, basta criar um novo arquivo 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;

A função is_leap_year() retorna true quando o ano é bissexto, caso contrário, retorna false. Se o ano for nulo, o ano corrente é testado. O controlador é simples: ele obtêm o ano a partir dos atributos do pedido, passa eles para a função is_leap_year(), e, de acordo com o valor retornado é criado um novo objeto Response.

Como sempre, você pode decidir parar aqui e usar o framework como ele está; é provavelmente tudo o que você precisa para criar sites simples, como os websites fantasia de uma única página, e, esperamos que alguns outros.