Symfony

Symfony #

Symfony Application (Request / Response) flow #

Symfony Request Flow

Front controller:

  • The only entry point of every request instead of having different PHP files for each URL path
  • The routing of different URLs to different parts of your application is done internally
/index.php executes index.php
/index.php/contact executes index.php
/index.php/blog executes index.php

index.php

<?php

use App\Kernel;
use Symfony\Component\HttpFoundation\Request;

require dirname(__DIR__).'/vendor/autoload.php';

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);

// Get variables from superglobals $_GET, $_POST, $_COOKIE, $_FILES, $_SERVER
$request = Request::createFromGlobals();

// Initialize the Kernel
// Uses the handle method to consume the Request object which will return the Response object
$response = $kernel->handle($request);

// Send the Response to the client
$response->send();

$kernel->terminate($request, $response);

Built-in Symfony Events (HttpKernel Component) #

https://symfony.com/doc/5.4/components/http_kernel.html

HttpKernel Flow

During the handling of an HTTP request, the application dispatches some events which you can use to modify how the request is handled and how the response is returned.

The dispatches are inside Symfony\Component\HttpKernel\HttpKernel, method handleRaw().

Namespace of events: Symfony\Component\HttpKernel\Event\

Each event extends KernelEvent and have the following methods:

getRequestType()
getKernel()
getRequest()
isMainRequest()

Command to list the listeners that listen to an event

bin/console debug:event-dispatcher <event-name>
$ php bin/console debug:event-dispatcher kernel.request

Registered Listeners for "kernel.request" Event
===============================================

 ------- --------------------------------------------------------------------------------------- ----------
  Order   Callable                                                                                Priority
 ------- --------------------------------------------------------------------------------------- ----------
  #1      Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure()           2048
  #2      Symfony\Component\HttpKernel\EventListener\ValidateRequestListener::onKernelRequest()   256
  #3      Symfony\Component\HttpKernel\EventListener\SessionListener::onKernelRequest()           128
  #4      Symfony\Component\HttpKernel\EventListener\LocaleListener::setDefaultLocale()           100
  #5      Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest()            32
  #6      Symfony\Component\HttpKernel\EventListener\LocaleListener::onKernelRequest()            16
 ------- --------------------------------------------------------------------------------------- ----------

kernel.request (Event class: RequestEvent)

  • First event dispatched
  • Purpose: Either to create and return a Response directly, or to add information to the Request
  • Listened by the RouterListener to search of a matching controller
  • The RouterListener stores the information of the matched controller inside $request->attributes

kernel.controller (Event class: ControllerEvent)

  • If the kernel.request didn’t return a Response, the handler creates an instance of a Controller by using a ControllerResolver
  • The ControllerResolver uses the informations added in $request->attributes by the RouteListener
  • Then the kernel.controller event is dispatched
    • Purposes: Initialize things or change the controller just before the controller is executed.
    • Example: Listened by SensioFrameworkExtraBundle/ParamConverterListener to transform raw data into objects and stores object in the $request->attributes

kernel.controller_arguments (Event class: ControllerArgumentsEvent)

  • the ArgumentResolver uses reflection on the callable to return an array of the names and/or hint of each of the arguments
  • Then the kernel.controller_arguments event is dispatched
  • And the controller is called

kernel.view (Event Class: ViewEvent)

  • Purposes: Transform a non-Response return value from a controller into a Response
  • If the controller doesn’t return a Response object, then the kernel dispatches a kernel.view event
    • Example: Used by FOSRestBundle to return different content-type responses (json, xml, html, …)

kernel.response (Event Class: ResponseEvent)

  • Purposes: Modify the Response object just before it is sent (modifying headers, adding cookies, inject JS, …)
  • Dispatched after the controller or any kernel.view listener returns a Response
    • We can call $response->send() if we want to deliver directly the Response to the client

kernel.finish_request (Event Class: FinishRequestEvent)

  • Dispatched after kernel.response, if the Response has not been sent yet
  • Purpose: Reset the global state of the application
    • Example:
      • the translator listener resets the translator’s locale to the one of the parent request
      • RouterListener: After a sub-request is done, we need to reset the routing context to the parent request so that the URL generator operates on the correct context again.

kernel.terminate

  • Dispatched after the response has been sent (see index.php)
  • Purposes: Useful to perform slow or complex tasks that don’t need to be completed to send the response
    • Example: Sending emails

WARNING: Internally, the HttpKernel makes use of the fastcgi_finish_request PHP function. This means that at the moment, only the PHP FPM server API is able to send a response to the client while the server’s PHP process still performs some tasks. With all other server APIs, listeners to kernel.terminate are still executed, but the response is not sent to the client until they are all completed.

kernel.exception Event Class: ExceptionEvent

  • Purposes: Handle some type of exception and create an appropriate Response to return for the exception
  • It’s dispatched if any exception is thrown at any point inside

Request context #

Holds information about the current request

public function __construct(
  string $baseUrl = '',
  string $method = 'GET',
  string $host = 'localhost',
  string $scheme = 'http',
  int $httpPort = 80,
  int $httpsPort = 443,
  string $path = '/',
  string $queryString = ''
) {}

Service container #

Private vs Public services #

  • Public: The service can be fetched with `$container->get(‘service_id’)
  • Private: The service is accessible from Dependency injection only

Services are private by default from symfony 4.0

Autowiring, Autoconfigure #

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
                            # Classes that implements LoggerInterface is automatically configured as a Logger service
                            # Auto tag => Ex: Adds the tag "twig.extension" automatically to classes that implement ExtensionInterface

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Tests,Kernel.php}'

Testing #

Different type of symfony tests

bin/console make:test

Which test type would you like?:
  [TestCase       ] basic PHPUnit tests
  [KernelTestCase ] basic tests that have access to Symfony services
  [WebTestCase    ] to run browser-like scenarios, but that don't execute JavaScript code
  [ApiTestCase    ] to run API-oriented scenarios
  [PantherTestCase] to run e2e scenarios, using a real-browser or HTTP client and a real web server