Symfony Request & Response flow

An Vo
5 min readMay 3, 2020

--

Symfony Request & Response flow

Symfony has many components and can do a lot of work. But in general, it just has the basic concept: handle the request and return the response. The whole flow is uncovered right in the index.php:

Symfony 5 high-level flow

The index.php is the first file to run in the Symfony process and have 3 main responsibilities:

  1. Create the Request object by using the Request::createFromGlobals(), this function will gather the PHP global variables ($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER) and store into the Request object.
  2. Initialize the Kernel object which contains the handle method, use the handle method to consume the Request object which will return the Response object
  3. Send the Response to client vie $response->send(), this function will send the header via the PHP header() function and send the content via the PHP echo function.

That is the whole Symfony Request & Response flow at a very high level. Now you can answer the interview question “What is the flow of Symfony framework ?” and good luck! 😛

The interviewer will ask further questions to check your understanding, so let’s keep going. The (1) and (3) are quite straightforward, let’s dive deeper into the (2) to reveal the magic inside the handle method. This is written in the vendor/symfony/http-kernel/HttpKernel.php. I’ve just keep the most relevant code so we can focus on the main things:

The main part of handle function in HttpKernel.php in Symfony 5

The handle method will call the handleRaw method which process 4 stages:

  1. request
  2. load controller
  3. controller arguments
  4. response

Request stage

“Request” stage

At first glance, this code block add the request into the RequestStack (thank to that, you can get the Request inside your service by injecting the RequestStack), after that it dispatches the event RequestEvent. Some listeners will listen to this event and modify the request on the fly, to list out the listeners, we use this command in our terminal:

bin/console debug:event-dispatcher kernel.request

and see the result:

Listeners of RequestEvent

The most important listener at this stage is the RouterListener, it will check the request URL to see if any controller is matched. If the request method (e.g. GET, POST) is not matched or the URL is not matched, it will throw Exception, otherwise it will add the matched controller into the Request.

Given that we have a simple controller with show action:

Simple controller with “show” action

Let’s check the RouterListener.php:

RouterListener::onKernelRequest()

The matcher will find the matched controller and return the result:

$parameters = [
"_route" => "post_show",
"_controller" => "App\Controller\PostController::show"
];

After this point, the RouterListener already found the matched controller, concatenated the controller class and controller action into a string ( format class::action), assigned to the _controller key, and stored it in the request attributes.

This is the main thing we should keep in mind about the request stage, that is finding the matched controller from the request URL, throw Exception if request method or request URL is not matched, otherwise store the controller information string (formatclass::method) into the _controller key in the request attributes.

Load controller stage

“Load controller” stage

In this stage, the resolver (ControllerResolver) will parse the _controller in the request attributes into a callable array contains a controller object and its method. It means the string PostController::show will be “resolved” to become a callable :

$controller = [(object) PostController, "show"]

After get callable array and assigned to the $controller, Symfony dispatch the ControllerEvent, let’s see which services will listen by this command

bin/console debug:event-dispatcher kernel.controller
Listeners from RequestEvent

There are two important listeners here:

The ControllerListener detects if the controller has a method __invoke(), it means that client will call the controller without mention the method. In this case, the listener will add the method __invoke() into the callable array so that the array will behave the same with the normal controller.

ParamConverterListener will check if it can convert the controller action arguments. In our case, it see the we type-hint the Post entity in our controller arguments, so it uses the built-in DoctrineParamConverter to get the Post object having id in the request URL. This converter helps our controller thin because we will not need to inject the PostRepository into the controller to find the Post object.

Controller arguments stage

“Controller arguments” stage

In this stage, the argumentResolver (ArgumentResolver) will assign a suitable value to the controller action arguments.

There are some built-in ArgumentResolver located in vendor/symfony/http-kernel/Controller/ArgumentResolver, among them, three following resolvers are usually used: RequestValueResolver, ServiceValueResolver, RequestAttributeValueResolver. They all help in our PostController:

  • RequestValueResolver helps getting the Request object from Request type-hint
  • ServiceValueResolver helps getting the Review service from the Review service type-hint
  • RequestAttributeValueResolver helps getting hasComments value from the request URL wild card

Response stage

“Response” stage

It’s time to call the controller method with the resolved arguments from the previous “controller arguments stage” to get response.

In this stage, Symfony conduct an additional check to see if our controller action returns a Response, then it will dispatch the ResponseEvent and the FinishRequestEvent. In this stage, the Request is also popped out the RequestStack.

As always, we use the commands to see which listeners to these events:

bin/console debug:event-dispatcher kernel.response
bin/console debug:event-dispatcher kernel.finish_request
Listeners of ResponseEvent and FinishRequestEvent

The HttpKernel\..\ResponseListener will have some adjustments in the headers such as Content-Type, Content-Length, fix the protocol version.

The RememberMe\ResponseListener will add remember me cookie to theResponse if some services created it in the Request attributes.

That is the general Symfony Request & Response flow.

--

--