El componente HttpKernel proporciona una forma estructurada de convertir un Request en un Response mediante el componente EventDispatcher. Es suficientemente flexible para crear un framework (Symfony), un micro-framework (Silex) o un sistema avanzado de CMS (Drupal).
namespace Symfony\Component\HttpKernel;
use Symfony\Component\HttpFoundation\Request;
interface HttpKernelInterface
{
// ...
/**
* @return Response A Response instance
*/
public function handle(
Request $request,
$type = self::MASTER_REQUEST,
$catch = true
);
}
Internamente, HttpKernel::handle() define un workflow que comienza en un Request y termina en un Response. Los detalles de este workflow son la clave para entender como funciona el kernel y Symfony.
El método HttpKernel::handle() funciona internamente lanzando eventos, por lo que todo el trabajo del framework y de una aplicación es a través de listeners.
El uso de HttpKernel es muy sencillo e implica crear un event dispatcher y un controller resolver. Para completas el kernel, tú mismo añadirás listeners a los eventos que ocurren durante el proceso.
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
// Crear el objeto Request
$request = Request::createFromGlobals();
$dispatcher = new EventDispatcher();
// ... Añadir algunos listeners
// Crear el controller resolver
$resolver = new ControllerResolver();
// Instanciar el kernel
$kernel = new HttpKernel($dispatcher, $resolver);
// Ejecutar el kernel, que transforma el request en response
// lanzando eventos, llamando a un controller, y devolviendo la respuesta
$response = $kernel->handle($request);
// Enviar los headers y mostrar el contenido
$response->send();
// Dispara el evento kernel.terminate
$kernel->terminate($request, $response);
Indice de contenido
1. El evento kernel.request
Uso típico: Añadir más información al request, iniciar partes del sistema o devolver un Response si es necesario.
Es el primer evento lanzado desde HttpKernel::handle, y puede tener diferentes listeners. Algunos de ellos (como alguno de seguridad) puede tener suficiente información como para crear un objeto Response inmediatamente. Si un listener de seguridad determina que un usuario no tiene acceso, el listener puede devolver un RedirectResponse a la página de login o responder con un código de estado 403 Access Denied response.
Si se devuelve una respuesta en esta etapa el proceso salta directamente al evento kernel.response:
Otros listeners simplemente inician procesos o añaden más información al request. Por ejemplo, un listener podría determinar y establecer el locale en el objeto Request.
Otro listener muy común es routing. Un router listener procesa el Request y determina el controller que ha de ser renderizado. El objeto Request tiene una bolsa de atributos donde se guardan datos específicos del request en la aplicación. Esto significa que si tu router listener determina el controlador, puede guardarlo en los atributos Request (que pueden ser usados por el controller resolver).
Resumiendo, el objetivo del evento kernel.request es o crear un Response directamente o añadir información al Request (añadiendo el locale u otra información en los atributos Request).
Cuando se establece una respuesta en el evento kernel.request se para la propagación, por lo que los listeners con menor prioridad nunca serán ejecutados.
El listener más importante del kernel.request en Symfony es el RouterListener. Esta clase ejecuta el routing layer, que devuelve un array de información sobre el request, incluyendo el controller y cualquier otro placeholder que esté en la route (por ejemplo, {slug}_). Este array de información se guarda en el array de atributos del objeto Request. Añadir la información de routing aquí no hará nada, pero se usa después cuando se resuelve el controller.
2. Resolver el Controller
Si ningún listener del kernel.request ha creado un objeto Response, el siguiente paso en el HttpKernel es determinar y preparar el controller. El controller es la parte de la aplicación que se encarga de crear y devolver un objeto Response para una página específica. El único requisito es que tiene que ser un callable PHP (función, método de un objeto o Closure).
Cómo determinar el controller exacto para un request depende totalmente de tu aplicación. Esta es la tarea del "controller resolver", una clase que implementa ControllerResolverInterface y que forma parte de los argumentos constructores del HttpKernel.
Tu tarea es crear una clase que implemente la interface y cumpla con sus dos métodos: getController y getArguments. De hecho ya existe una implementación por defecto, que puedes usar directamente o utilizarla para ver cómo funciona: ControllerResolver.
namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
interface ControllerResolverInterface
{
public function getController(Request $request);
public function getArguments(Request $request, $controller);
}
Internamente, el método HttpKernel::handle primero llama a getController() en el controller resolver. A este método se le pasa el Request y es responsable de alguna forma de determinar y devolver un callable PHP (el controller) basándose en la información del request.
El segundo método, getArguments(), será llamado después de que otro evento, kernel.controller, sea lanzado.
Resolver el Controller en Symfony
El framework de Symfony utiliza la clase ControllerResolver (bueno realmente utiliza una subclase con alguna funcionalidad extra). Esta clase trata la información de la propiedad attributes del objeto Request durante el RouterListener.
El ControllerResolver busca una key _controller en la propiedad attributes del objeto Request. Este string es entonces transformado en un callable PHP a través de los siguientes pasos:
- El formato AcmeDemoBundle:Default:index en el key _controller se cambia a otro string que contiene la clase entera y el nombre del método del controller siguiendo las convenciones usadad en Symfony: Acme\DemoBundle\Controller\DefaultController::indexAction. Esta transformación es específica a la subclase ControllerResolver utilizada en el framework.
- Una nueva instancia de la clase de tu controller es instanciada sin argumentos constructores.
- Si el controller implementa ControllerAwareInterface, se llama a setContainer en el objeto controller y se pasa el contenedor. Este paso es específico de la subclase ControllerResolver utilizada en el framework Symfony.
Existen otras variantes del proceso anterior (por ejemplo si registras los controllers como services).
3. El evento kernel.controller
Uso típico: iniciar procesos o cambiar el controller justo antes de que el controller sea ejecutado.
Después de que se haya determinado el controller callable, HttpKernel::handle lanza el evento kernel.controller. Los listeners de este evento pueden iniciar alguna parte del sistema que necesite ser iniciada después de que se hayan determinado algunas cosas (como el controller o la información del routing) pero antes de que el controller sea ejecutado.
Los listeners de este evento pueden tambien cambiar el controller callable completamente llamando al método FilterControllerEvent::setController en el objeto del evento que se pasa a los listeners de este evento.
Hay unos pocos listeners del evento kernel.controller en Symfony, y muchos de ellos están relacionados con la información del profiler cuando está activado.
Un listener a destacar viene del bundle SensioFrameworkExtraBundle, incluído en la Symfony Standard Edition. La funcionalidad del @ParamConverter de este listener permite pasar un objeto (por ejemplo, un objeto Post) al controller en lugar de un valor escalar (por ejemplo, un parámetro id que estaba en la route). El listener es ParamConverterListener, y utiliza reflection para ver cada uno de los argumentos del controller e intenta usar diferentes métodos para convertirlos a objetos, los cuales son entonces guardados en la propiedad attributes del objeto Request.
4. Obtener los argumentos del Controller
Después, HttpKernel::handle llama al método getArguments. Hay que recordar que el controller devuelto en getController es un callable. El objetivo de getArguments es devolver un array de argumentos que deberían pasarse al controller. Como se hace esto exactamente depende del desarrollador, aunque el ControllerResolver que viene incorporado es un buen ejemplo.
En este punto el kernel tiene un callable (el controller) y un array de argumentos que deberían pasarse cuando se ejecute ese callable.
Ahora que ya sabemos lo que es el controller callable (normalmente un método dentro de un objeto controller), el ControllerResolver utiliza reflection en el callable para devolver un array con los nombres de cada uno de los argumentos. Es entonces cuando itera sobre cada uno de los argumentos y utiliza lo siguiente para determinar qué valores deben pasarse para cada argumento:
- Si el array de atributos del Request contiene un key que coincide con el nombre del argumento, se utiliza ese valor. Por ejemplo, si el primer argumento de un controller es $slug, y hay un key slug en el array de atributos, se usa ese valor (y normalmente este valor viene del RouterListener).
- Si el argumento en el controller es type-hinted con el objeto Request, entonces el Request se pasa como el valor.
5. Llamar al Controller
El siguiente paso es sencillo: HttpKernel::handle ejecuta el controlador.
La tarea del controller es construir una respuesta para el resource dado. Esto puede ser una página HTML, un string JSON o cualquier otra cosa. Al contrario que con los procesos que se han dado hasta ahora, este paso se implementa por el desarrollador, para cada página que se construya.
Normalmente el controller devolverá un objeto Response. Si es true, el trabajo del kernel está casi terminado, el siguiente paso es el evento kernel.response.
Pero si el controller devuelve cualquier otra cosa que no sea el Response, el kernel tiene algo más de trabajo que hacer, el evento kernel.view (ya que el objetivo siempre es generar un objeto Response). Un controller siempre tiene que devolver algo. Si devuelve null, se lanzará una excepción.
6. El evento kernel.view
Uso típico: transformar un valor que no es Response de un controller en un Response.
Si el controller no devuelve un objeto Response, el kernel lanza otro evento, el kernel.view. La tarea de un listener de este evento es utilizar el valor de retorno del controller (como un array de datos) para crear un Response.
Esto puede resultar útil si quieres usar un view layer: en lugar de devolver un Response del controller, devuelves datos que representan la página. Un listener de este evento podría usar estos datos para crear un Response en el formato correcto (HTML, JSON, etc).
En esta etapa, si ningún listener responde al evento, se lanzará una excepción: ya sea el controller o uno de los view listeners deben siempre devolver un Response.
Cuando se establece una respuesta para el evento kernel.view la propagación se para, lo que significa que los listeners con menor priodidad no se ejecutarán.
No hay ningún listener por defecto para el evento kernel.view en Symfony. Sin embargo, el bundle incorporado SensioFrameworkExtraBundle añade un listener a este evento. Si tu controller devuelve un array y añades la anotación @Template encima del controller, este listener renderiza una template, pasa el array que generes en el controller a esa template y crea un Response conteniendo el contenido devuelto de esa template.
Además el popular bundle FOSRestBundle implementa un listener en este evento cuyo objeto es proporcionar un view layer capaz de usar un controller para devolver contenidos muy diferentes (HTML, JSON, XML, etc).
7. El evento kernel.response
Uso típico: modificar el objeto Response justo antes de ser enviado.
El objetivo final del kernel es transformar un Request en un Response. El Response puede ser creado durante el evento kernel.request, devuelto por el controller o devuelto por uno de los listeners del evento kernel.view.
Independientemente de quien genere el Response, otro evento, kernel.response, se lanza inmediatamente después. Un listener típico de este evento modificará el objeto Response de alguna forma, ya sean los headers, añadir cookies, o incluso cambiar el contenido de la respuesta (inyectar Javascript antes del final de la etiqueta