Crie o seu próprio framework... utilizando os Componentes do Symfony2
Tradução dos artigos Create your own framework... on top of the Symfony2 Components
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'),
)));
O movimento é 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 à um motivo principal: melhor 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 este problema, e mais um monte, vamos instalar e usar o componente HttpKernel:
{
"require": {
"symfony/class-loader": "2.1.*",
"symfony/http-foundation": "2.1.*",
"symfony/routing": "2.1.*",
"symfony/http-kernel": "2.1.*"
}
}
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 à 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 à 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 o seu tipo definido estiver correto:
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 um 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 à 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 dar suporte à controladores como serviços; e no FrameworkExtraBundle, o getArguments() é aprimorado para suportar conversores de parâmetros, onde os atributos do pedido são convertidos em objetos automaticamente.
Vamos concluir com a nova versão do nosso framework:
<?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;
use Symfony\Component\HttpKernel;
function render_template($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.