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:
- public/index.php is loaded again, which returns a closure that returns a Kernel object and is written to the $app variable.
- 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.
- 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:
- Project Kernel (src/Kernel.php) that inherits the Kernel from the HttpKernel component and uses the MicroKernelTrait methods from the FrameworkBundle component.
- The base Kernel (vendor/symfony/http-kernel/Kernel.php), whose main function is to initialize the service container.
- 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.