Documentação do Symfony - versão 3.1
Renderizada do repositório symfony-docs-pt-BR no Github
Se você fosse usar o nosso framework agora, provavelmente teria que adicionar suporte para mensagens de erro personalizadas. Nesse momento, temos suporte aos erros 404 e 500, mas, as respostas estão fixas no próprio framework. Torná-las personalizáveis é bastante fácil: basta despachar um novo evento e ouvi-lo. Fazê-lo corretamente significa que o listener precisa chamar um controlador regular. Entretanto, e se o controlador de erro gerar uma exceção? Você vai acabar em um loop infinito. Deve haver uma maneira mais fácil, certo?
Conheça a classe HttpKernel
. Em vez de resolver o mesmo problema
mais uma vez e, em vez de reinventar a roda toda vez, a classe
HttpKernel
é uma implementação genérica, extensível e flexível da
HttpKernelInterface
.
Essa classe é muito semelhante a classe framework que escrevemos até agora: ela despacha eventos em alguns pontos estratégicos durante o manuseio do pedido, ela usa um resolvedor de controlador para escolher o controlador para o qual irá despachar o pedido, e, como bônus, ela cuida de edge cases e fornece um ótimo feedback quando surge um problema.
Aqui está o novo código do framework:
// example.com/src/Simplex/Framework.php
namespace Simplex;
use Symfony\Component\HttpKernel\HttpKernel;
class Framework extends HttpKernel
{
}
E o novo front controller:
// 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;
use Symfony\Component\EventDispatcher\EventDispatcher;
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
$resolver = new HttpKernel\Controller\ControllerResolver();
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher));
$framework = new Simplex\Framework($dispatcher, $resolver);
$response = $framework->handle($request);
$response->send();
O RouterListener
é uma implementação da mesma lógica que tínhamos em nosso
framework: ele verifica o pedido de entrada corresponde e popula os atributos
do pedido com parâmetros da rota.
Nosso código está agora muito mais conciso e surpreendentemente mais robusto e poderoso
do que nunca. Por exemplo, utilize o ExceptionListener
embutido para tornar
o seu gerenciamento de erros configurável:
$errorHandler = function (HttpKernel\Exception\FlattenException $exception) {
$msg = 'Something went wrong! ('.$exception->getMessage().')';
return new Response($msg, $exception->getStatusCode());
};
$dispatcher->addSubscriber(new HttpKernel\EventListener\ExceptionListener($errorHandler));
O ExceptionListener
lhe fornece uma instância FlattenException
ao invés da
instância Exception
gerada para facilitar a manipulação de exceção e a exibição. Ele
pode usar qualquer controlador válido como um manipulador de exceção, assim, você pode
criar uma classe ErrorController em vez de usar uma Closure:
$listener = new HttpKernel\EventListener\ExceptionListener('Calendar\\Controller\\ErrorController::exceptionAction');
$dispatcher->addSubscriber($listener);
O controlador de erro é o seguinte:
// example.com/src/Calendar/Controller/ErrorController.php
namespace Calendar\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\FlattenException;
class ErrorController
{
public function exceptionAction(FlattenException $exception)
{
$msg = 'Something went wrong! ('.$exception->getMessage().')';
return new Response($msg, $exception->getStatusCode());
}
}
Voilà! Gerenciamento de erro limpo e personalizável, sem esforços. E, é claro, no caso do seu controlador gerar uma exceção, o HttpKernel vai lidar com isso muito bem.
No capítulo dois, falamos sobre o método Response::prepare()
, que
garante que a Resposta é compatível com a especificação HTTP. É
provavelmente uma boa idéia sempre chamá-lo antes de enviar a resposta
ao cliente, e é exatamente isso o que o ResponseListener
faz:
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
Isto foi fácil demais! Vamos para outra: você quer suporte pronto para
uso para stream das respostas? Basta fazer o subscribe no
StreamedResponseListener
:
$dispatcher->addSubscriber(new HttpKernel\EventListener\StreamedResponseListener());
E, em seu controlador, retornar uma instância StreamedResponse
em vez de uma
instância Response
.
Tip
Leia o capítulo Internals na documentação do Symfony para saber mais sobre os eventos despachados pelo HttpKernel e como eles permitem que você altere o fluxo de um pedido.
Agora, vamos criar um listener, que permite a um controlador retornar uma string
ao invés de um objeto Response
completo:
class LeapYearController
{
public function indexAction(Request $request, $year)
{
$leapyear = new LeapYear();
if ($leapyear->isLeapYear($year)) {
return 'Yep, this is a leap year! ';
}
return 'Nope, this is not a leap year.';
}
}
Para implementar esse recurso, vamos ouvir o evento kernel.view
, que é
acionado imediatamente após o controlador ter sido chamado. Seu objetivo é converter
o valor de retorno do controlador para a instância Response
apropriada, mas,
apenas se necessário:
// example.com/src/Simplex/StringResponseListener.php
namespace Simplex;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\Response;
class StringResponseListener implements EventSubscriberInterface
{
public function onView(GetResponseForControllerResultEvent $event)
{
$response = $event->getControllerResult();
if (is_string($response)) {
$event->setResponse(new Response($response));
}
}
public static function getSubscribedEvents()
{
return array('kernel.view' => 'onView');
}
}
O código é simples, pois o evento kernel.view
só é acionado quando o valor
de retorno do controlador não é Response
e porque configurar a resposta
no evento pára a propagação do evento (o nosso listener não pode interferir com
outros view listeners).
Não se esqueça de registrá-lo no front controller:
$dispatcher->addSubscriber(new Simplex\StringResponseListener());
Note
Se você esquecer de registrar o subscriber, o HttpKernel irá lançar uma
exceção, com uma bela mensagem: The controller must return a response
(Nope, this is not a leap year. given).
.
Nesse ponto, todo o código do nosso framework é o mais compacto possível e é composto principalmente da montagem de bibliotecas existentes. Estender é apenas uma questão de registrar listeners/subscribers de eventos.
Esperamos que agora você tenha uma melhor compreensão do por que um simples olhar na
HttpKernelInterface
é tão poderoso. Sua implementação padrão, HttpKernel
,
fornece acesso a uma série de recursos interessantes, prontos para serem usados,
sem esforços. E, devido ao HttpKernel ser, na verdade, o código que alimenta os
frameworks Symfony e Silex, você tem o melhor de ambos os mundos: um framework personalizado,
adaptado às suas necessidades, mas com base em uma arquitetura de baixo nível sólida
e com boa manutenção que provou funcionar em muitos sites; um código
que foi auditado em questões relativas à segurança e que provou
ser bem escalável.