
Summary:
This guide breaks down the Symfony Kernel lifecycle from request to response showing how index.php triggers the Kernel, processes with handle(), sends the response, and runs termination tasks.
September 12, 2025
Symfony combines a disciplined architecture with tools that prioritize developer happiness and efficiency. But what actually happens when someone hits your website?
This article takes you behind the curtain of a Symfony application. You’ll see how a simple index.php file launches a full pipeline that turns an HTTP request into a response and back again.
1. The Modern Front Controller: public/index.php
In Symfony, the front controller became even simpler thanks to the Runtime component:
<?php // public/index.php use App\Kernel; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return function (array $context) { return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); };
This file does two key things:
- Loads Composer’s autoloader and Symfony’s Runtime component via vendor/autoload_runtime.php.
- Returns an anonymous function that instantiates your Kernel class with the correct environment and debug mode.
Everything else – calling the Kernel, generating the Request, sending and terminating the response – is handled automatically by the Symfony Runtime component.
2. What Happens Behind the Scenes
At first glance, public/index.php looks simple – although it’s the entry point that triggers the Symfony full request handling process. Let’s walk through each step with file references.
2.1 Runtime takes control
When PHP executes public/index.php:
- vendor/autoload_runtime.php boots the Runtime component.
- The anonymous function returns a new App\Kernel object.
By the time control leaves this file, you already have a fully booted Kernel instance ready to process an HTTP request.
2.2 Building the Request (conceptually in front controller)
The Runtime automatically builds the request for you conceptually; it looks like :
<?php // (conceptually into public/index.php) use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals();
This factory method:
- Reads all PHP superglobals ($_POST, $_GET, $_FILES, $_SERVER, $_COOKIE).
- Normalises them into a clean Request object.
- Adds easy methods for headers, parameters, session and many more.
You rarely need to create Request objects manually – Symfony inbuilt create one from the globals for you.
2.3 Handling the Request (Kernel::handle() inside BaseKernel)
The Runtime then calls:
// (conceptually in public/index.php) $response = $kernel->handle($request);
What happens behind the scenes:
- Symfony dispatches the kernel.Request event, allowing listeners such as firewalls, caches, and profilers to perform their processing.
- Then match the URL to a route and find out which controller to call.
- Next, it resolves any arguments of the controller’s needs.
- The controller is called, and its return value is captured.
- If the controller doesn’t return a Response, it will dispatch the kernel.view to convert the result into a proper Response.
- Finally, the kernel.response event will be dispatched, giving a chance for any last-minute modifications before sending the final response back.
Finally, handle() function, you have a fully-formed Response object ready to send.
2.3.1 Where is handle() actually defined?
Open your src/Kernel.php and you’ll see:
<?php // src/Kernel.php namespace App; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\HttpKernel\Kernel as BaseKernel; final class Kernel extends BaseKernel { use MicroKernelTrait; }
This file is tiny on purpose:
- BaseKernel (in vendor/symfony/http-kernel) contains all the heavy lifting for booting bundles, managing the service container, and running the request pipeline.
- MicroKernelTrait (in vendor/symfony/framework-bundle) adds convenience methods for small apps (configure routes, configure services, register bundles).
Your App\Kernel class extends the BaseKernel class and adds a trait to add functionality. When you call $kernel->handle($request), the main logic actually happens inside the BaseKernel class.
2.3.2 Deep Dive: What BaseKernel Does – Key Methods and Flow
Here’s what actually happens inside BaseKernel (file: vendor/symfony/http-kernel/Kernel.php) in the order it runs:
Method / Function | Purpose / What It Does | Where in Lifecycle |
__construct($environment, $debug) | It will sets up env / debug flags, initialise base attributes. | When you create an object for new Kernel() in public/index.php. |
boot() | ensures bundle registration, container instantiation, and config loading. Internally calls initializeBundles(), initializeContainer(). | Happens before the first time call handle(). |
registerBundles() | Returns bundles array to load based on env (often delegated to MicroKernelTrait, which reads config/bundles.php). | Part of the boot phase. |
buildContainer() / initializeContainer() | loads your service definitions, parameters, and config. Compiles the DI container. | Part of the boot phase. |
handle(Request $request, int $type, bool $catch) | The main request -> response flow: dispatches kernel.request, track controller, dispatches method, call it dispatches view, dispatches response. | Every HTTP Request |
Event dispatch inside handle() — kernel.request, kernel.controller, kernel.controller_arguments, kernel.view, kernel.response, kernel.exception | Provide you or system bundles to attach logic before The controller, after the controller, on exceptions. | Distributed in handle(). |
terminate(Request $request, Response $response) | After sending the response, triggers kernel.terminate event so background work can happen. | After sending a response to the client. |
Because you’re using MicroKernelTrait, you can also override configureRoutes() or configureContainer() inside your own Kernel if you want to add routes/services programmatically instead of through config files.
2.4 Sending the Response (conceptually in front controller)
<?php // (conceptually in public/index.php) $response->send();
This:
- Send HTTP headers and cookies.
- Streams or echoes the response body.
- Flushes everything to the client.
Up to now everything lived only in PHP memory — now the user’s browser finally gets the response.
2.5 Post-response Termination (Kernel::terminate() inside BaseKernel)
Finally, Symfony calls:
<?php // (conceptually in public/index.php) $kernel->terminate($request, $response);
This is the clean-up and background work stage:
- Dispatches the kernel.terminate event.
- Let’s you run time-consuming tasks after the response is sent (send emails, log analytics, dispatch jobs).
- Frees up PHP to finish background work without blocking the user.
2.6 All of this, automatically
With the Runtime component, you don’t see those four lines explicitly in index.php anymore, but Symfony still performs exactly this sequence internally for every request.
3. Key Takeaways
The Kernel is the heart of Symfony – it boots the framework, registers bundles, handles requests and responses, and dispatches the events.
Your src/Kernel.php is intentionally tiny; the real work is done in vendor code.
BaseKernel defines the lifecycle methods; MicroKernelTrait gives you easy hooks to override parts of it.
The front controller (public/index.php) and the Runtime component hide the boilerplate so you can focus on your app.
4. Visualizing the The Request–Response Pipeline in Detail
Inside handle() Symfony’s HttpKernel orchestrates:
5. Hooking Into the Lifecycle
You can run code at almost any stage via event subscribers.
Example: adding a header to every response:
namespace App\EventSubscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; class AddHeaderSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ 'kernel.response' => 'onKernelResponse', ]; } public function onKernelResponse(ResponseEvent $event): void { $event->getResponse()->headers->set('X-App', 'MyBlog'); } }
This injects headers without touching any controller.
6. Why This Lifecycle Matters
Understanding Symfony’s Kernel lifecycle helps you in several key ways:
- For debugging routing, controller, and response issues, it is much easier.
- You can add cross-cutting features like custom headers, authentication and logging the right spots.
- It helps you write maintainable and clean code that integrates naturally with the framework.
- And it gives you the insight to optimise performance by caching or deferring resource-heavy tasks.
7. Conclusion
Symfony’s public/index.php may only be a few lines, but it triggers a sophisticated chain of events. The Runtime component:
- Loads Composer’s autoloader.
- Creates a clean Request from PHP’s globals.
- Passes that request into your Kernel to run through routing, controllers, and events.
- Produces and sends a Response back to the browser.
- Calls terminate() so you can run background work after the response is sent.
Understanding this lifecycle gives you a clearer mental model for debugging, extending, and optimising your Symfony applications.
Related Blog
eCommerce Development
Building a Robust B2B and B2C Storefront with Pimcore Integration
Pimcore combines several essential tools into one platform: Product Information Management (PIM), Digital Asset Management (DAM), Content Management (CMS), and e-commerce functionality. This open-source solution helps businesses manage their product data and digital assets from a central location. Many companies...
Symfony
Automating Symfony Test Environments with Docker and PHPUnit
Testing and setting Symfony API applications may be awfully challenging to control in case different environments set up with manual management, and it takes a lot of time to establish databases and set up configuration again. Docket proposes a solution...
Symfony
Unleash Symfony API Testing with PHPUnit
Building robust APIs in Symfony 7.2 requires thorough testing to ensure reliability and security. In this guide, we’ll use PHPUnit to test the API endpoints from JWT authentication setup -/api/register, /api/login_check, /api/users/{id}, /api/users/me, and /api/profile. You’ll learn to set up...

Keep up-to-date with our newsletter.
Sign up for our newsletter to receive weekly updates and news directly to your inbox.