Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

index.php to HttpKernel::handle()

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Let's start from the very beginning of the request. When we load a page, the first file that's executed is public/index.php. No matter what, this is where it all starts. So let's literally go through this file line-by-line and see what happens.

Tip

If you start a new project in Symfony 5.3 or later, this file will look quite different thanks to the new symfony/runtime component. But, the same things are still happening behind the scenes.

index.php Bootstrapping

The first thing it does is require this config/bootstrap.php file. For our purposes, this... isn't important. It requires the Composer autoloader... and then the rest of this file is all about loading and normalizing environment variables. Sure, environment variables are important to Symfony, but if you want to understand the request-response flow, not so much.

Next, if we're in debug mode, it calls Debug::enable(). That's great to set up some debugging tools... but not relevant to us.

Hello Kernel

The first thing we care about is down here: $kernel = new Kernel(). This is actually instantiating our src/Kernel.php class, which is the heart of our application.

The Kernel is passed the environment as the first argument and a debug flag as the second. That controls a bunch of behavior... but isn't very important to the request-response flow.

But the next line is important. We always knew that there was a Request object inside Symfony. If you ever wondered who creates the Request and where, here's your answer: it's created in our code - not somewhere deep in the core.

The ::createFromGlobals() method - I'll hold command or control to open that method inside Symfony - is a shortcut to create the Request object and populate its data with the normal superglobal variables, like $_SERVER and $_POST. This gives us a nice Request object that represents the current request info.

HttpKernel::handle(): Our App in One Method

The next line... oh... the next line. This is probably my favorite line of code in all of PHP: $response = $kernel->handle($request). That runs our app. We don't know exactly what happens inside that method - that's what we're going to figure out - but isn't it beautiful? Our application & Symfony are not some weird, global monster that takes over our PHP process and eats our objects. Nope, it's a pure function. Input $request, output $response... which is exactly what our job as a developer is! Understand the incoming request, and use that to create a response.

One of the properties of a "pure" function like this is that you can call it as many times as you want. So yes, in theory, a single Kernel can handle multiple requests inside just one PHP process. In fact, let's do that!

Up above, let's say $request1 = Request::create() - which is another shortcut to create a Request object. Let's make this look like a Request for our login page. Pass /login as the first arg.

Now create a $request2 variable and pretend that this is a request for /register.

39 lines public/index.php
... lines 1 - 24
$request1 = Request::create('/login');
$request2 = Request::create('/register');
... lines 27 - 39

Could we run our kernel and get 2 responses for these 2 requests? Uh... totally! $response1 = $kernel->handle($request1)... and then $response2 = $kernel->handle($request2). Let's see what they look like: dump($response1), dump($response2) and then die.

39 lines public/index.php
... lines 1 - 27
$response1 = $kernel->handle($request1);
$response2 = $kernel->handle($request2);
dump($response1);
dump($response2);
die;
... lines 34 - 39

Let's do this! Move over, refresh and... check it out! We just handled two different requests on the same page! The first does contain the HTML for the login page, and the second... for the registration page. Amazing.

And this idea of handling multiple requests in Symfony is something that really does happen! It happens with sub-requests - a topic that we will cover later in this tutorial - and some people use an event loop in PHP to boot a single kernel and then handle many, real, HTTP requests.

Ok, remove all of this code. It's now obvious that if we really want to understand what happens inside Symfony, we need to find out what happens inside of this $kernel->handle() method. We're going be opening a lot of core files, so make sure you have an easy way to "jump to a file" by typing a filename in your editor. In PhpStorm, I can hit Shift+Shift to open a file called HttpKernel.php, which lives deep inside Symfony. If you don't see it, make sure the "Include non-project items" checkbox is checked - PhpStorm usually does that automatically if you type a specific filename.

Once inside... scroll down to the handle() method.

Hello HttpKernel::handle()

Ok, technically the $kernel->handle() method we saw in index.php is not the handle() method in this class. Symfony first initializes the dependency injection container - the topic of a future deep-dive tutorial - and then calls this method.

The first thing I want you to notice is that the entire function is surrounded by a try-catch block. So almost immediately when our app starts running, our code is surrounded by a try catch! That's not important yet. But later, we'll see what happens when an exception is thrown from anywhere.

The real logic of HttpKernel lives in this handleRaw() method. Scroll down a little to find it. Ah yes: handleRaw(). This is the Symfony framework. These 50 lines of code are the heart of everything that happens in Symfony! And not just Symfony: these same 50 lines of code run Drupal, phpBB and many other things!

So next: let's start our journey through this strange and wondrous method.

Leave a comment!

7
Login or Register to join the conversation

Hey @weaverryan and Team!

I've been diving deeeep into Symfony trying to load in some params from a 3rd party via an API request and then set these params at run time. I tried an onKernelRequest Subscriber and had the API request working etc, but... can't set the params here due to "Impossible to call set() on a frozen ParameterBag." error. So then I created a CompilarPass() class to try and get the params and set them during the container build process. But I run into a dependency injection loop where I need the RequestStack and my other FetchParams service in my CustomCompilarPass class but they aren't "built" in the container yet? So when I instantiate my CustomCompilarPass() in the kernel I actually need to inject my FetchParams service & RequestStack. I tried registering everything as services but I don't think they are available yet? Anyhow, just wondering if you had any advice on what to tap into to make an api request and set / modify some params at runtime. I know env file or environment variables are probably preferred but have an interesting use case. Thanks! Jon

Reply

Hey @Jonathan_E

That sounds interesting! I believe a compiler pass won't work because it is executed only once when Symfony boots the container. I think you can go with an event subscriber but implement a custom ParameterBag, but the problem will be where to store the data. I think you can use cache, the session, or a database. I hope it helps

Cheers!

Reply

Hey @MolloKhan!

Thank you for the suggestion. I'll give it a try and see how it turns out. It is sort of an odd scenario but looking forward to giving it a try. Take care! Jon

Reply
Cameron Avatar
Cameron Avatar Cameron | posted 1 year ago

I don't undertand the purpose of learning internals. ok, maybe I'll be able to contribute to the source coude or do some unusual hack to the core code, but I really don't understand what other (practical) reason a dev needs to know about the internals or even who this type of course is suited to. can someone help me out?

Reply

In addition to what Ryan said, I find useful analyzing how Symfony is written and how it solve problems. Symfony puts extra effort on following good programming techniques and even applying design patterns, so, by reviewing how it works internally you don't only learn how it works, you also learn how to develop good OO code. Cheers!

2 Reply

Hey Fox C.!

That's a fair question. I think there is a portion of developers (I'd be guessing at the percentage) that just *like* to dive into the internals and know how the machine works. About the benefit of doing so? It's difficult to give specific examples, but the more you know of the internals, the more likely it is that if you hit a difficult issue, you'll have the knowledge to be able to jump into the core code and discover the solution for yourself. It's definitely not a requirement... and it takes serious time to learn the internals. If it's not your thing, I wouldn't worry about it - stick to the practical stuff.

Cheers!

2 Reply
Cameron Avatar

Ok, so it's part knowing how to track down difficult issues too, thanks.

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial also works well for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-iconv": "*",
        "antishov/doctrine-extensions-bundle": "^1.4", // v1.4.3
        "aws/aws-sdk-php": "^3.87", // 3.133.20
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.12.1
        "doctrine/doctrine-bundle": "^2.0", // 2.2.3
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // 2.2.2
        "doctrine/orm": "^2.5.11", // 2.8.2
        "easycorp/easy-log-handler": "^1.0", // v1.0.9
        "http-interop/http-factory-guzzle": "^1.0", // 1.0.0
        "knplabs/knp-markdown-bundle": "^1.7", // 1.9.0
        "knplabs/knp-paginator-bundle": "^5.0", // v5.4.2
        "knplabs/knp-snappy-bundle": "^1.6", // v1.7.1
        "knplabs/knp-time-bundle": "^1.8", // v1.16.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.24
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.9.1
        "liip/imagine-bundle": "^2.1", // 2.5.0
        "oneup/flysystem-bundle": "^3.0", // 3.7.0
        "php-http/guzzle6-adapter": "^2.0", // v2.0.2
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "sensio/framework-extra-bundle": "^5.1", // v5.6.1
        "symfony/asset": "5.0.*", // v5.0.11
        "symfony/console": "5.0.*", // v5.0.11
        "symfony/dotenv": "5.0.*", // v5.0.11
        "symfony/flex": "^1.9", // v1.17.5
        "symfony/form": "5.0.*", // v5.0.11
        "symfony/framework-bundle": "5.0.*", // v5.0.11
        "symfony/mailer": "5.0.*", // v5.0.11
        "symfony/messenger": "5.0.*", // v5.0.11
        "symfony/monolog-bundle": "^3.5", // v3.6.0
        "symfony/property-access": "5.0.*|| 5.1.*", // v5.1.11
        "symfony/property-info": "5.0.*|| 5.1.*", // v5.1.10
        "symfony/routing": "5.1.*", // v5.1.11
        "symfony/security-bundle": "5.0.*", // v5.0.11
        "symfony/sendgrid-mailer": "5.0.*", // v5.0.11
        "symfony/serializer": "5.0.*|| 5.1.*", // v5.1.10
        "symfony/twig-bundle": "5.0.*", // v5.0.11
        "symfony/validator": "5.0.*", // v5.0.11
        "symfony/webpack-encore-bundle": "^1.4", // v1.11.1
        "symfony/yaml": "5.0.*", // v5.0.11
        "twig/cssinliner-extra": "^2.12", // v2.14.3
        "twig/extensions": "^1.5", // v1.5.4
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.0
        "twig/inky-extra": "^2.12", // v2.14.3
        "twig/twig": "^2.12|^3.0" // v2.14.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.4.0
        "fakerphp/faker": "^1.13", // v1.13.0
        "symfony/browser-kit": "5.0.*", // v5.0.11
        "symfony/debug-bundle": "5.0.*", // v5.0.11
        "symfony/maker-bundle": "^1.0", // v1.29.1
        "symfony/phpunit-bridge": "5.0.*", // v5.0.11
        "symfony/stopwatch": "^5.1", // v5.1.11
        "symfony/var-dumper": "5.0.*", // v5.0.11
        "symfony/web-profiler-bundle": "^5.0" // v5.0.11
    }
}
userVoice