The HttpKernel Component

The HttpKernel Component

The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatcher. It's flexible enough to create a full-stack framework (Symfony), a micro-framework (Silex) or an advanced CMS system (Drupal).

Installation

You can install the component in 2 different ways:

The Workflow of a Request

Every HTTP web interaction begins with a request and ends with a response. Your job as a developer is to create PHP code that reads the request information (e.g. the URL) and creates and returns a response (e.g. an HTML page or JSON string).

../../_images/request-response-flow.png

Typically, some sort of framework or system is built to handle all the repetitive tasks (e.g. routing, security, etc) so that a developer can easily build each page of the application. Exactly how these systems are built varies greatly. The HttpKernel component provides an interface that formalizes the process of starting with a request and creating the appropriate response. The component is meant to be the heart of any application or framework, no matter how varied the architecture of that system:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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
    );
}

Internally, HttpKernel::handle() - the concrete implementation of HttpKernelInterface::handle() - defines a workflow that starts with a Request and ends with a Response.

../../_images/01-workflow.png

The exact details of this workflow are the key to understanding how the kernel (and the Symfony Framework or any other library that uses the kernel) works.

HttpKernel: Driven by Events

The HttpKernel::handle() method works internally by dispatching events. This makes the method both flexible, but also a bit abstract, since all the "work" of a framework/application built with HttpKernel is actually done in event listeners.

To help explain this process, this document looks at each step of the process and talks about how one specific implementation of the HttpKernel - the Symfony Framework - works.

Initially, using the HttpKernel is really simple, and involves creating an EventDispatcher and a controller resolver (explained below). To complete your working kernel, you'll add more event listeners to the events discussed below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;

// create the Request object
$request = Request::createFromGlobals();

$dispatcher = new EventDispatcher();
// ... add some event listeners

// create your controller resolver
$resolver = new ControllerResolver();
// instantiate the kernel
$kernel = new HttpKernel($dispatcher, $resolver);

// actually execute the kernel, which turns the request into a response
// by dispatching events, calling a controller, and returning the response
$response = $kernel->handle($request);

// send the headers and echo the content
$response->send();

// triggers the kernel.terminate event
$kernel->terminate($request, $response);

See "A full Working Example" for a more concrete implementation.

For general information on adding listeners to the events below, see Creating an Event Listener.

Tip

Fabien Potencier also wrote a wonderful series on using the HttpKernel component and other Symfony2 components to create your own framework. See Create your own framework... on top of the Symfony2 Components.

1) The kernel.request Event

Typical Purposes: To add more information to the Request, initialize parts of the system, or return a Response if possible (e.g. a security layer that denies access).

Kernel Events Information Table

The first event that is dispatched inside HttpKernel::handle is kernel.request, which may have a variety of different listeners.

../../_images/02-kernel-request.png

Listeners of this event can be quite varied. Some listeners - such as a security listener - might have enough information to create a Response object immediately. For example, if a security listener determined that a user doesn't have access, that listener may return a RedirectResponse to the login page or a 403 Access Denied response.

If a Response is returned at this stage, the process skips directly to the kernel.response event.

../../_images/03-kernel-request-response.png

Other listeners simply initialize things or add more information to the request. For example, a listener might determine and set the locale on the Request object.

Another common listener is routing. A router listener may process the Request and determine the controller that should be rendered (see the next section). In fact, the Request object has an "attributes" bag which is a perfect spot to store this extra, application-specific data about the request. This means that if your router listener somehow determines the controller, it can store it on the Request attributes (which can be used by your controller resolver).

Overall, the purpose of the kernel.request event is either to create and return a Response directly, or to add information to the Request (e.g. setting the locale or setting some other information on the Request attributes).

The most important listener to kernel.request in the Symfony Framework is the RouterListener. This class executes the routing layer, which returns an array of information about the matched request, including the _controller and any placeholders that are in the route's pattern (e.g. {slug}). See Routing component.

This array of information is stored in the Request object's attributes array. Adding the routing information here doesn't do anything yet, but is used next when resolving the controller.

2) Resolve the Controller

Assuming that no kernel.request listener was able to create a Response, the next step in HttpKernel is to determine and prepare (i.e. resolve) the controller. The controller is the part of the end-application's code that is responsible for creating and returning the Response for a specific page. The only requirement is that it is a PHP callable - i.e. a function, method on an object, or a Closure.

But how you determine the exact controller for a request is entirely up to your application. This is the job of the "controller resolver" - a class that implements ControllerResolverInterface and is one of the constructor arguments to HttpKernel.

../../_images/04-resolve-controller.png

Your job is to create a class that implements the interface and fill in its two methods: getController and getArguments. In fact, one default implementation already exists, which you can use directly or learn from: ControllerResolver. This implementation is explained more in the sidebar below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
namespace Symfony\Component\HttpKernel\Controller;

use Symfony\Component\HttpFoundation\Request;

interface ControllerResolverInterface
{
    public function getController(Request $request);

    public function getArguments(Request $request, $controller);
}

Internally, the HttpKernel::handle method first calls getController() on the controller resolver. This method is passed the Request and is responsible for somehow determining and returning a PHP callable (the controller) based on the request's information.

The second method, getArguments(), will be called after another event - kernel.controller - is dispatched.

The Symfony Framework uses the built-in ControllerResolver class (actually, it uses a sub-class with some extra functionality mentioned below). This class leverages the information that was placed on the Request object's attributes property during the RouterListener.

getController

The ControllerResolver looks for a _controller key on the Request object's attributes property (recall that this information is typically placed on the Request via the RouterListener). This string is then transformed into a PHP callable by doing the following:

  1. The AcmeDemoBundle:Default:index format of the _controller key is changed to another string that contains the full class and method name of the controller by following the convention used in Symfony2 - e.g. Acme\DemoBundle\Controller\DefaultController::indexAction. This transformation is specific to the ControllerResolver sub-class used by the Symfony2 Framework.

  2. A new instance of your controller class is instantiated with no constructor arguments.

  3. If the controller implements ContainerAwareInterface, setContainer is called on the controller object and the container is passed to it. This step is also specific to the ControllerResolver sub-class used by the Symfony2 Framework.

    There are also a few other variations on the above process (e.g. if you're registering your controllers as services).

3) The kernel.controller Event

Typical Purposes: Initialize things or change the controller just before the controller is executed.

Kernel Events Information Table

After the controller callable has been determined, HttpKernel::handle dispatches the kernel.controller event. Listeners to this event might initialize some part of the system that needs to be initialized after certain things have been determined (e.g. the controller, routing information) but before the controller is executed. For some examples, see the Symfony2 section below.

../../_images/06-kernel-controller.png

Listeners to this event can also change the controller callable completely by calling FilterControllerEvent::setController on the event object that's passed to listeners on this event.

There are a few minor listeners to the kernel.controller event in the Symfony Framework, and many deal with collecting profiler data when the profiler is enabled.

One interesting listener comes from the SensioFrameworkExtraBundle, which is packaged with the Symfony Standard Edition. This listener's @ParamConverter functionality allows you to pass a full object (e.g. a Post object) to your controller instead of a scalar value (e.g. an id parameter that was on your route). The listener - ParamConverterListener - uses reflection to look at each of the arguments of the controller and tries to use different methods to convert those to objects, which are then stored in the attributes property of the Request object. Read the next section to see why this is important.

4) Getting the Controller Arguments

Next, HttpKernel::handle calls getArguments(). Remember that the controller returned in getController is a callable. The purpose of getArguments is to return the array of arguments that should be passed to that controller. Exactly how this is done is completely up to your design, though the built-in ControllerResolver is a good example.

../../_images/07-controller-arguments.png

At this point the kernel has a PHP callable (the controller) and an array of arguments that should be passed when executing that callable.

Now that you know exactly what the controller callable (usually a method inside a controller object) is, the ControllerResolver uses reflection on the callable to return an array of the names of each of the arguments. It then iterates over each of these arguments and uses the following tricks to determine which value should be passed for each argument:

  1. If the Request attributes bag contains a key that matches the name of the argument, that value is used. For example, if the first argument to a controller is $slug, and there is a slug key in the Request attributes bag, that value is used (and typically this value came from the RouterListener).
  2. If the argument in the controller is type-hinted with Symfony's Request object, then the Request is passed in as the value.

5) Calling the Controller

The next step is simple! HttpKernel::handle executes the controller.

../../_images/08-call-controller.png

The job of the controller is to build the response for the given resource. This could be an HTML page, a JSON string or anything else. Unlike every other part of the process so far, this step is implemented by the "end-developer", for each page that is built.

Usually, the controller will return a Response object. If this is true, then the work of the kernel is just about done! In this case, the next step is the kernel.response event.

../../_images/09-controller-returns-response.png

But if the controller returns anything besides a Response, then the kernel has a little bit more work to do - kernel.view (since the end goal is always to generate a Response object).

Note

A controller must return something. If a controller returns null, an exception will be thrown immediately.

6) The kernel.view Event

Typical Purposes: Transform a non-Response return value from a controller into a Response

Kernel Events Information Table

If the controller doesn't return a Response object, then the kernel dispatches another event - kernel.view. The job of a listener to this event is to use the return value of the controller (e.g. an array of data or an object) to create a Response.

../../_images/10-kernel-view.png

This can be useful if you want to use a "view" layer: instead of returning a Response from the controller, you return data that represents the page. A listener to this event could then use this data to create a Response that is in the correct format (e.g HTML, JSON, etc).

At this stage, if no listener sets a response on the event, then an exception is thrown: either the controller or one of the view listeners must always return a Response.

There is no default listener inside the Symfony Framework for the kernel.view event. However, one core bundle - SensioFrameworkExtraBundle - does add a listener to this event. If your controller returns an array, and you place the @Template annotation above the controller, then this listener renders a template, passes the array you returned from your controller to that template, and creates a Response containing the returned content from that template.

Additionally, a popular community bundle FOSRestBundle implements a listener on this event which aims to give you a robust view layer capable of using a single controller to return many different content-type responses (e.g. HTML, JSON, XML, etc).

7) The kernel.response Event

Typical Purposes: Modify the Response object just before it is sent

Kernel Events Information Table

The end goal of the kernel is to transform a Request into a Response. The Response might be created during the kernel.request event, returned from the controller, or returned by one of the listeners to the kernel.view event.

Regardless of who creates the Response, another event - kernel.response is dispatched directly afterwards. A typical listener to this event will modify the Response object in some way, such as modifying headers, adding cookies, or even changing the content of the Response itself (e.g. injecting some JavaScript before the end </body> tag of an HTML response).

After this event is dispatched, the final Response object is returned from handle(). In the most typical use-case, you can then call the send() method, which sends the headers and prints the Response content.

There are several minor listeners on this event inside the Symfony Framework, and most modify the response in some way. For example, the WebDebugToolbarListener injects some JavaScript at the bottom of your page in the dev environment which causes the web debug toolbar to be displayed. Another listener, ContextListener serializes the current user's information into the session so that it can be reloaded on the next request.

8) The kernel.terminate Event

Typical Purposes: To perform some "heavy" action after the response has been streamed to the user

Kernel Events Information Table

The final event of the HttpKernel process is kernel.terminate and is unique because it occurs after the HttpKernel::handle method, and after the response is sent to the user. Recall from above, then the code that uses the kernel, ends like this:

1
2
3
4
5
// send the headers and echo the content
$response->send();

// triggers the kernel.terminate event
$kernel->terminate($request, $response);

As you can see, by calling $kernel->terminate after sending the response, you will trigger the kernel.terminate event where you can perform certain actions that you may have delayed in order to return the response as quickly as possible to the client (e.g. sending emails).

Note

Using the kernel.terminate event is optional, and should only be called if your kernel implements TerminableInterface.

If you use the SwiftmailerBundle with Symfony2 and use memory spooling, then the EmailSenderListener is activated, which actually delivers any emails that you scheduled to send during the request.

Handling Exceptions: the kernel.exception Event

Typical Purposes: Handle some type of exception and create an appropriate Response to return for the exception

Kernel Events Information Table

If an exception is thrown at any point inside HttpKernel::handle, another event - kernel.exception is thrown. Internally, the body of the handle function is wrapped in a try-catch block. When any exception is thrown, the kernel.exception event is dispatched so that your system can somehow respond to the exception.

../../_images/11-kernel-exception.png

Each listener to this event is passed a GetResponseForExceptionEvent object, which you can use to access the original exception via the getException() method. A typical listener on this event will check for a certain type of exception and create an appropriate error Response.

For example, to generate a 404 page, you might throw a special type of exception and then add a listener on this event that looks for this exception and creates and returns a 404 Response. In fact, the HttpKernel component comes with an ExceptionListener, which if you choose to use, will do this and more by default (see the sidebar below for more details).

There are two main listeners to kernel.exception when using the Symfony Framework.

ExceptionListener in HttpKernel

The first comes core to the HttpKernel component and is called ExceptionListener. The listener has several goals:

  1. The thrown exception is converted into a FlattenException object, which contains all the information about the request, but which can be printed and serialized.
  2. If the original exception implements HttpExceptionInterface, then getStatusCode and getHeaders are called on the exception and used to populate the headers and status code of the FlattenException object. The idea is that these are used in the next step when creating the final response.
  3. A controller is executed and passed the flattened exception. The exact controller to render is passed as a constructor argument to this listener. This controller will return the final Response for this error page.

ExceptionListener in Security

The other important listener is the ExceptionListener. The goal of this listener is to handle security exceptions and, when appropriate, help the user to authenticate (e.g. redirect to the login page).

Creating an Event Listener

As you've seen, you can create and attach event listeners to any of the events dispatched during the HttpKernel::handle cycle. Typically a listener is a PHP class with a method that's executed, but it can be anything. For more information on creating and attaching event listeners, see The EventDispatcher Component.

The name of each of the "kernel" events is defined as a constant on the KernelEvents class. Additionally, each event listener is passed a single argument, which is some sub-class of KernelEvent. This object contains information about the current state of the system and each event has their own event object:

Name KernelEvents Constant Argument passed to the listener
kernel.request KernelEvents::REQUEST GetResponseEvent
kernel.controller KernelEvents::CONTROLLER FilterControllerEvent
kernel.view KernelEvents::VIEW GetResponseForControllerResultEvent
kernel.response KernelEvents::RESPONSE FilterResponseEvent
kernel.finish_request KernelEvents::FINISH_REQUEST FinishRequestEvent
kernel.terminate KernelEvents::TERMINATE PostResponseEvent
kernel.exception KernelEvents::EXCEPTION GetResponseForExceptionEvent

A full Working Example

When using the HttpKernel component, you're free to attach any listeners to the core events and use any controller resolver that implements the ControllerResolverInterface. However, the HttpKernel component comes with some built-in listeners and a built-in ControllerResolver that can be used to create a working example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;

$routes = new RouteCollection();
$routes->add('hello', new Route('/hello/{name}', array(
        '_controller' => function (Request $request) {
            return new Response(
                sprintf("Hello %s", $request->get('name'))
            );
        }
    )
));

$request = Request::createFromGlobals();

$matcher = new UrlMatcher($routes, new RequestContext());

$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new RouterListener($matcher));

$resolver = new ControllerResolver();
$kernel = new HttpKernel($dispatcher, $resolver);

$response = $kernel->handle($request);
$response->send();

$kernel->terminate($request, $response);

Sub Requests

In addition to the "main" request that's sent into HttpKernel::handle, you can also send so-called "sub request". A sub request looks and acts like any other request, but typically serves to render just one small portion of a page instead of a full page. You'll most commonly make sub-requests from your controller (or perhaps from inside a template, that's being rendered by your controller).

../../_images/sub-request.png

To execute a sub request, use HttpKernel::handle, but change the second argument as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;

// ...

// create some other request manually as needed
$request = new Request();
// for example, possibly set its _controller manually
$request->attributes->set('_controller', '...');

$response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST);
// do something with this response

2.4The isMasterRequest() method was introduced in Symfony 2.4. Prior, the getRequestType() method must be used.

This creates another full request-response cycle where this new Request is transformed into a Response. The only difference internally is that some listeners (e.g. security) may only act upon the master request. Each listener is passed some sub-class of KernelEvent, whose isMasterRequest() can be used to check if the current request is a "master" or "sub" request.

For example, a listener that only needs to act on the master request may look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use Symfony\Component\HttpKernel\HttpKernelInterface;
// ...

public function onKernelRequest(GetResponseEvent $event)
{
    if (!$event->isMasterRequest()) {
        return;
    }

    // ...
}

This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License .