Documentação do Symfony - versão 3.1
Renderizada do repositório symfony-docs-pt-BR no Github
Você pode pensar que o nosso framework já está bastante sólido e, provavelmente, está certo. Mas, vamos ver como podemos melhorá-lo mesmo assim.
Até agora, todos os nossos exemplos usaram código procedural, mas, lembre-se que os controladores podem ser qualquer callback PHP válido. Vamos converter nosso controlador para uma classe adequada:
class LeapYearController
{
public function indexAction($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.');
}
}
Atualize a definição de rota, de acordo:
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
'year' => null,
'_controller' => array(new LeapYearController(), 'indexAction'),
)));
A modificação é bastante simples e faz muito sentido, logo que você
criar mais páginas, mas, você deve ter notado um efeito colateral não-desejável...
A classe LeapYearController
é sempre instanciada, mesmo se a
URL solicitada não corresponder a rota leap_year
. Isso é ruim, devido a um motivo
principal: desempenho, todos os controladores para todas as rotas devem ser agora
instanciados para cada solicitação. Seria melhor se os controladores fossem
lazy-loaded de modo que, somente o controlador associado com a rota correspondente é
instanciado.
Para resolver esse problema e vários outros, vamos instalar e usar o componente HttpKernel:
.. code-block:: bash
$ composer require symfony/http-kernel
O componente HttpKernel possui muitas funcionalidades interessantes, mas, a que precisamos
agora é o resolvedor de controlador. Um resolvedor de controlador sabe como determinar
o controlador que deve ser executado e os argumentos que devem ser passados a ele, com base em
um objeto Request
. Todos os resolvedores de controlador implementam a seguinte interface:
namespace Symfony\Component\HttpKernel\Controller;
interface ControllerResolverInterface
{
function getController(Request $request);
function getArguments(Request $request, $controller);
}
O método getController()
baseia-se na mesma convenção que definimos
anteriormente: o atributo do pedido _controller
deve conter o controlador
associado ao Request
. Além dos callbacks PHP integrados, o getController()
também suporta strings compostas por um nome de classe seguido de dois dois-pontos
e um nome de método, como um callback válido, por exemplo ‘class::method’:
$routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array(
'year' => null,
'_controller' => 'LeapYearController::indexAction',
)));
Para fazer este código funcionar, modifique o código do framework para usar o resolvedor de controlador a partir do HttpKernel:
use Symfony\Component\HttpKernel;
$resolver = new HttpKernel\Controller\ControllerResolver();
$controller = $resolver->getController($request);
$arguments = $resolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
Note
Como um bônus adicional, o resolvedor de controlador manipula apropriadamente
o gerenciamento de erros para você: por exemplo, quando você esquecer de definir
um atributo _controller
para uma rota.
Agora, vamos ver como os argumentos do controlador são adivinhados. O getArguments()
examinará a assinatura do controlador para determinar quais argumentos deve passar para ele
usando o reflection nativo do PHP.
O método indexAction()
precisa do objeto Request
como um argumento.
O getArguments()
sabe quando injetá-lo adequadamente se sua indução de tipo
estiver correta:
public function indexAction(Request $request)
// não funciona
public function indexAction($request)
Mais interessante, o getArguments()
também é capaz de injetar qualquer atributo
do Request
; o argumento só precisa ter o mesmo nome do atributo
correspondente:
public function indexAction($year)
Você também pode injetar o Request
e alguns atributos ao mesmo tempo (como a
correspondência é feita utilizando o nome do argumento ou uma indução de tipo, a ordem dos argumentos
não importa):
public function indexAction(Request $request, $year)
public function indexAction($year, Request $request)
Finalmente, você também pode definir valores padrão para qualquer argumento que corresponda a um
atributo opcional do Request
:
public function indexAction($year = 2012)
Vamos apenas injetar o atributo $year
do pedido para o nosso controlador:
class LeapYearController
{
public function indexAction($year)
{
if (is_leap_year($year)) {
return new Response('Yep, this is a leap year!');
}
return new Response('Nope, this is not a leap year.');
}
}
O resolvedor de controlador também se encarrega de validar o callable
do controlador
e seus argumentos. No caso de um problema, ele gera uma exceção com uma agradável
mensagem explicando o problema (a classe do controlador não existe, o
método não está definido, um argumento não possui um atributo correspondente, ...).
Note
Com a grande flexibilidade do resolvedor de controlador padrão, você pode
perguntar por que alguém iria desejar criar outro (por que haveria uma
interface?). Dois exemplos: no Symfony2, o getController()
é
aprimorado para suportar controladores como serviços; e no
FrameworkExtraBundle, o getArguments()
é aprimorado para suportar
conversores de parâmetros, onde os atributos do pedido são convertidos
automaticamente em objetos.
Vamos concluir com a nova versão 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;
use Symfony\Component\HttpKernel;
function render_template(Request $request)
{
extract($request->attributes->all());
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);
$resolver = new HttpKernel\Controller\ControllerResolver();
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$controller = $resolver->getController($request);
$arguments = $resolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
}
$response->send();
Pense nisso mais uma vez: o nosso framework é mais robusto e mais flexível do que nunca e ele ainda tem menos de 40 linhas de código.