The HttpKernel component provides a structured process for converting a
Request
into aResponse
by making use of the EventDiser component. It's flexible enough to create a full-stack framework (Symfony) or an advanced CMS (Drupal).
1
$ composer require symfony/http-kernel
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). This is a simplified overview of the request-response lifecycle in Symfony applications:
- The user asks for a resource in a browser;
- The browser sends a request to the server;
- Symfony gives the application a Request object;
- The application generates a Response object using the data of the Request object;
- The server sends back the response to the browser;
- The browser displays the resource to the user.
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 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,
int $type = self::MAIN_REQUEST,
bool $catch = true
): Response;
}
Internally, HttpKernel::handle() - the concrete implementation of HttpKernelInterface::handle() - defines a lifecycle that starts with a Request and ends with a Response.
The exact details of this lifecycle are the key to understanding how the kernel (and the Symfony Framework or any other library that uses the kernel) works.
The HttpKernel::handle()
method works internally by dising 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 does not take many steps. You create an event diser and a controller and argument 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 26 27 28 29
use Symfony\Component\EventDiser\EventDiser;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\HttpKernel;
// create the Request object
$request = Request::createFromGlobals();
$diser = new EventDiser();
// ... add some event listeners
// create your controller and argument resolvers
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
// instantiate the kernel
$kernel = new HttpKernel($diser, $controllerResolver, new RequestStack(), $argumentResolver);
// actually execute the kernel, which turns the request into a response
// by dising events, calling a controller, and returning the response
$response = $kernel->handle($request);
// send the headers and echo the content
$response->send();
// trigger 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.
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 dised inside HttpKernel::handle is kernel.request
, which may have a variety of different listeners.
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.
Other listeners 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).
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
.
Your job is to create a class that implements the interface and fill in its method: getController()
. 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
namespace Symfony\Component\HttpKernel\Controller;
use Symfony\Component\HttpFoundation\Request;
interface ControllerResolverInterface
{
public function getController(Request $request): callable|false;
}
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.
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()
dises 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.
Another typical use-case for this event is to retrieve the attributes from the controller using the getAttributes() method. See the Symfony section below for some examples.
Listeners to this event can also change the controller callable completely by calling ControllerEvent::setController on the event object that's passed to listeners on this event.
Next, HttpKernel::handle()
calls ArgumentResolverInterface::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 ArgumentResolver is a good example.
At this point the kernel has a PHP callable (the controller) and an array of arguments that should be passed when executing that callable.
The next step of HttpKernel::handle()
is executing the controller.
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.
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).
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 dises 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
.
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
.
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 dised 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 dised, 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.
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
// sends the headers and echoes 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).
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 dised. Internally, the body of the handle()
method is wrapped in a try-catch block. When any exception is thrown, the kernel.exception
event is dised so that your system can somehow respond to the exception.
Each listener to this event is passed a ExceptionEvent object, which you can use to access the original exception via the getThrowable() 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 ErrorListener, which if you choose to use, will do this and more by default (see the sidebar below for more details).
The ExceptionEvent exposes the isKernelTerminating() method, which you can use to determine if the kernel is currently terminating at the moment the exception was thrown.
As you've seen, you can create and attach event listeners to any of the events dised 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 EventDiser 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 subclass 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 | RequestEvent |
kernel.controller | KernelEvents::CONTROLLER | ControllerEvent |
kernel.controller_arguments | KernelEvents::CONTROLLER_ARGUMENTS | ControllerArgumentsEvent |
kernel.view | KernelEvents::VIEW | ViewEvent |
kernel.response | KernelEvents::RESPONSE | ResponseEvent |
kernel.finish_request | KernelEvents::FINISH_REQUEST | FinishRequestEvent |
kernel.terminate | KernelEvents::TERMINATE | TerminateEvent |
kernel.exception | KernelEvents::EXCEPTION | ExceptionEvent |
When using the HttpKernel component, you're free to attach any listeners to the core events, use any controller resolver that implements the ControllerResolverInterface and use any argument resolver that implements the ArgumentResolverInterface. However, the HttpKernel component comes with some built-in listeners and everything else 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 36 37 38
use Symfony\Component\EventDiser\EventDiser;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
$routes->add('hello', new Route('/hello/{name}', [
'_controller' => function (Request $request): Response {
return new Response(
sprintf("Hello %s", $request->get('name'))
);
}]
));
$request = Request::createFromGlobals();
$matcher = new UrlMatcher($routes, new RequestContext());
$diser = new EventDiser();
$diser->addSubscriber(new RouterListener($matcher, new RequestStack()));
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
$kernel = new HttpKernel($diser, $controllerResolver, new RequestStack(), $argumentResolver);
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
In addition to the "main" request that's sent into HttpKernel::handle()
, you can also send a 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).
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
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 main request. Each listener is passed some subclass of KernelEvent, whose isMainRequest() method can be used to check if the current request is a "main" or "sub" request.
For example, a listener that only needs to act on the main request may look like this:
1 2 3 4 5 6 7 8 9 10 11
use Symfony\Component\HttpKernel\Event\RequestEvent;
// ...
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
// ...
}
The HttpKernel component is responsible of the bundle mechanism used in Symfony applications. One of the key features of the bundles is that you can use logic paths instead of physical paths to refer to any of their resources (config files, templates, controllers, translation files, etc.)
This allows to import resources even if you don't know where in the filesystem a bundle will be installed. For example, the services.xml
file stored in the Resources/config/
directory of a bundle called FooBundle can be referenced as @FooBundle/Resources/config/services.xml
instead of __DIR__/Resources/config/services.xml
.
This is possible thanks to the locateResource() method provided by the kernel, which transforms logical paths into physical paths:
1
$path = $kernel->locateResource('@FooBundle/Resources/config/services.xml');