How a Request is Processed in the Symfony Framework

Loading Front Controller

It all starts with the execution of the public/index.php file.

// public/index.php

<?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 is also called Front Controller. It is automatically generated when a Symfony application is installed and is located in recipes of the FrameworkBundle component.

The FrameworkBundle component contains a microkernel trait that loads data from /config/* configuration files, such as services, routes, parameters. It contains a basic set of console commands such as clearing the cache, dumping configurations, services, routes. It also contains the AbstractController base class with useful functions for controllers.

The public/index.php file executes the /vendor/autoload_runtime.php file.

// vendor/autoload_runtime.php

<?php

// autoload_runtime.php @generated by Symfony Runtime

if (true === (require_once __DIR__.'/autoload.php') || empty($_SERVER['SCRIPT_FILENAME'])) {
    return;
}

$app = require $_SERVER['SCRIPT_FILENAME'];

if (!is_object($app)) {
    throw new TypeError(sprintf('Invalid return value: callable object expected, "%s" returned from "%s".', get_debug_type($app), $_SERVER['SCRIPT_FILENAME']));
}

$runtime = $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\SymfonyRuntime';
$runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + [
  'project_dir' => dirname(__DIR__, 1),
]);

[$app, $args] = $runtime
    ->getResolver($app)
    ->resolve();

$app = $app(...$args);

exit(
    $runtime
        ->getRunner($app)
        ->run()
);

This code does the following:

  1. public/index.php is loaded again, which returns a closure that returns a Kernel object and is written to the $app variable.
  2. The object of the Runtime component is initialized, which allows loading the Symfony application from different programs such as PHP-FPM, Nginx, Apache, PHP-PM (PHP Process Manager), ReactPHP, Swoole.
  3. Runtime calls Runner, in our case HttpKernelRunner which knows how to use the Kernel.

The HttpKernelRunner class that runs Kernel functions

// vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php

class HttpKernelRunner implements RunnerInterface
{
    private $kernel;
    private $request;

    public function __construct(HttpKernelInterface $kernel, Request $request)
    {
        $this->kernel = $kernel;
        $this->request = $request;
    }

    public function run(): int
    {
        $response = $this->kernel->handle($this->request);
        $response->send();

        if ($this->kernel instanceof TerminableInterface) {
            $this->kernel->terminate($this->request, $response);
        }

        return 0;
    }
}

Kernel Loading

The Kernel of the project is in src/Kernel.php.

// src/Kernel.php

<?php

namespace App;

use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;

class Kernel extends BaseKernel
{
    use MicroKernelTrait;
}

In our case, there are three classes of kernels:

  1. Project Kernel (src/Kernel.php) that inherits the Kernel from the HttpKernel component and uses the MicroKernelTrait methods from the FrameworkBundle component.
  2. The base Kernel (vendor/symfony/http-kernel/Kernel.php), whose main function is to initialize the service container.
  3. HTTP Kernel (vendor/symfony/http-kernel/HttpKernel.php), which can process the request, call all the necessary events through the EventDispatcher, call the controller and return a response.

Let's go back to our vendor/autoload_runtime.php file.

// vendor/autoload_runtime.php

// ...
$app = $app(...$args);
// ...

Now the $app variable will store the Kernel object.

Start Processing a Request

The start of request processing begins with a call to the run method via a call from vendor/autoload_runtime.php.

// vendor/autoload_runtime.php
// ...
exit(
    $runtime
        ->getRunner($app)
        ->run()
);
// vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php
// ...
public function run(): int
{
    $response = $this->kernel->handle($this->request);
    $response->send();

    if ($this->kernel instanceof TerminableInterface) {
        $this->kernel->terminate($this->request, $response);
    }

    return 0;
}

First, the handle method of the Kernel is called.

// vendor/symfony/http-kernel/Kernel.php

// ...
public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response
{
    if (!$this->booted) {
        $container = $this->container ?? $this->preBoot();

        if ($container->has('http_cache')) {
            return $container->get('http_cache')->handle($request, $type, $catch);
        }
    }

    $this->boot();
    ++$this->requestStackSize;
    $this->resetServices = true;

    try {
        return $this->getHttpKernel()->handle($request, $type, $catch);
    } finally {
        --$this->requestStackSize;
    }
}
//...

The HttpKernel service is then obtained through the service container and the handle method is called.

// vendor/symfony/http-kernel/HttpKernel.php

//...
public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response
{
    $request->headers->set('X-Php-Ob-Level', (string) ob_get_level());

    try {
        return $this->handleRaw($request, $type);
    } catch (\Exception $e) {
        if ($e instanceof RequestExceptionInterface) {
            $e = new BadRequestHttpException($e->getMessage(), $e);
        }
        if (false === $catch) {
            $this->finishRequest($request, $type);

            throw $e;
        }

        return $this->handleThrowable($e, $request, $type);
    }
}
//...

Next, the handleRaw method is called, which is the main function of processing the request.

// vendor/symfony/http-kernel/HttpKernel.php
//...
private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response
{
    $this->requestStack->push($request);

    // request
    $event = new RequestEvent($this, $request, $type);
    $this->dispatcher->dispatch($event, KernelEvents::REQUEST);

    if ($event->hasResponse()) {
        return $this->filterResponse($event->getResponse(), $request, $type);
    }

    // load controller
    if (false === $controller = $this->resolver->getController($request)) {
        throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo()));
    }

    $event = new ControllerEvent($this, $controller, $request, $type);
    $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
    $controller = $event->getController();

    // controller arguments
    $arguments = $this->argumentResolver->getArguments($request, $controller);

    $event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $type);
    $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS);
    $controller = $event->getController();
    $arguments = $event->getArguments();

    // call controller
    $response = $controller(...$arguments);

    // view
    if (!$response instanceof Response) {
        $event = new ViewEvent($this, $request, $type, $response);
        $this->dispatcher->dispatch($event, KernelEvents::VIEW);

        if ($event->hasResponse()) {
            $response = $event->getResponse();
        } else {
            $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this->varToString($response));

            // the user may have forgotten to return something
            if (null === $response) {
                $msg .= ' Did you forget to add a return statement somewhere in your controller?';
            }

            throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17);
        }
    }

    return $this->filterResponse($response, $request, $type);
}
//...

It calls the RequestEvent, ControllerEvent, ControllerArgumentsEvent, ViewEvent events, calls the controller, and returns a response.

The request has now been fully processed and a response has been returned.

Resources

Runtime Component