Interno

Se si vuole capire come funziona Symfony ed estenderlo, in questa sezione si potranno trovare spiegazioni approfondite dell’interno di Symfony.

Nota

La lettura di questa sezione è necessaria solo per capire come funziona Symfony dietro le quinte oppure se si vuole estendere Symfony.

Panoramica

Il codice di Symfony è composto da diversi livelli indipendenti. Ogni livello è costruito sulla base del precedente.

Suggerimento

L’auto-caricamento non viene gestito direttamente dal framework, ma dall’autoloader di Composer (vendor/autoload.php), incluso nel file app/autoload.php.

Il componente HttpFoundation

Il livello più profondo è il componente HttpFoundation. HttpFoundation fornisce gli oggetti principali necessari per trattare con HTTP. È un’astrazione orientata gli oggetti di alcune funzioni e variabili native di PHP:

  • La classe Symfony\Component\HttpFoundation\Request astrae le variabili globali principali di PHP, come $_GET, $_POST, $_COOKIE, $_FILES e $_SERVER;
  • La classe Symfony\Component\HttpFoundation\Response astrae alcune funzioni PHP, come header(), setcookie() ed echo;
  • La classe Symfony\Component\HttpFoundation\Session e l’interfaccia Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface astraggono le funzioni di gestione della sessione session_*().

Nota

Si può approfondire nel componente HttpFoundation.

Il componente HttpKernel

Sopra HttpFoundation c’è il componente HttpKernel. HttpKernel gestisce la parte dinamica di HTTP e incapsula in modo leggero le classi Request e Response, per standardizzare il modo in cui sono gestite le richieste. Fornisce anche dei punti di estensione e degli strumenti che lo rendono il punto di partenza ideale per creare un framework web senza troppe sovrastrutture.

Opzionalmente, aggiunge anche configurabilità ed estensibilità, grazie al componente DependencyInjection e a un potente sistema di plugin (bundle).

Vedi anche

Approfondimento sul componente HttpKernel, su dependency injection e sui bundle.

Il bundle FrameworkBundle

Il bundle FrameworkBundle è il bundle che lega insieme i componenti e le librerie principali, per fare un framework MVC leggero e veloce. Dispone in una configurazione predefinita adeguata e di convenzioni che facilitano la curva di apprendimento.

Kernel

La classe Symfony\Component\HttpKernel\HttpKernel è la classe centrale di Symfony ed è responsabile della gestione delle richieste del client. Il suo scopo principale è “convertire” un oggetto Symfony\Component\HttpFoundation\Request in un oggetto Symfony\Component\HttpFoundation\Response.

Ogni kernel di Symfony implementa Symfony\Component\HttpKernel\HttpKernelInterface:

function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)

Controllori

Per convertire una Request in una Response, il kernel si appoggia a un “controllore”. Un controllore può essere qualsiasi funzione o metodo PHP valido.

Il kernel delega la scelta di quale controllore debba essere eseguito a un’implementazione di Symfony\Component\HttpKernel\Controller\ControllerResolverInterface:

public function getController(Request $request);

public function getArguments(Request $request, $controller);

Il metodo getController() restituisce il controllore (una funzione PHP) associato alla Request data. L’implementazionoe predefinita (Symfony\Component\HttpKernel\Controller\ControllerResolver) cerca un attributo _controller della richiesta, che rappresenta il nome del controllore (una stringa “classe::metodo”, come Bundle\BlogBundle\PostController:indexAction).

Suggerimento

L’implementazione predefinita usa Symfony\Bundle\FrameworkBundle\EventListener\RouterListener per definire l’attributo _controller della richista (vedere kernel.request).

Il metodo getArguments() restituisce un array di parametri da passare al controllore. L’implementazione predefinita risolve automaticamente i parametri, basandosi sugli attributi di Request.

Gestione delle richieste

Il metodo handle() prende una Request e restituisce sempre una Response. Per convertire Request, handle() si appoggia su Resolver e su una catena ordinata di notifiche di eventi (vedere la prossima sezione per maggiori informazioni sugli oggetti Event):

  1. Prima di tutto, viene notificato l’evento kernel.request, se uno degli ascoltatori restituisce una Response, salta direttamente al passo 8;
  2. Viene chiamato Resolver, per decidere quale controllore eseguire;
  3. Gli ascoltatori dell’evento kernel.controller possono ora manipolare il controllore, nel modo che preferiscono (cambiarlo, avvolgerlo, ecc.);
  4. Il kernel verifica che il controllore sia effettivamente un metodo valido;
  5. Viene chiamato Resolver, per decidere i parametri da passare al controllore;
  6. Il kernel richiama il controllore;
  7. Se il controllore non restituisce una Response, gli ascoltatori dell’evento kernel.view possono convertire il valore restituito dal controllore in una Response;
  8. Gli ascoltatori dell’evento kernel.response possono manipolare la Response (sia il contenuto che gli header);
  9. Viene restituita la risposta.
  10. Gli ascoltatori dell’evento kernel.terminate possono eseguire dei compiti, dopo che la risposta sia stata servita.

Se viene lanciata un’eccezione durante il processo, viene notificato l’evento kernel.exception e gli ascoltatori possono convertire l’eccezione in una risposta. Se funziona, viene notificato l’evento kernel.response, altrimenti l’eccezione viene lanciata nuovamente.

Se non si vuole che le eccezioni siano catturate (per esempio per richieste incluse), disabilitare l’evento kernel.exception, passando false come terzo parametro del metodo handle().

Richieste interne

In qualsiasi momento, durante la gestione della richiesta (quella “principale”), si può gestire una sotto-richiesta. Si può passare il tipo di richiesta al metodo handle(), come secondo parametro:

  • HttpKernelInterface::MASTER_REQUEST;
  • HttpKernelInterface::SUB_REQUEST.

Il tipo è passato a tutti gli eventi e gli ascoltatori possono agire di conseguenza (alcuni processi possono avvenire solo sulla richiesta principale).

Eventi

Ogni evento lanciato dal kernel è una sotto-classe di Symfony\Component\HttpKernel\Event\KernelEvent. Questo vuol dire che ogni evento ha accesso alle stesse informazioni di base:

getRequestType()
Restituisce il tipo della richiesta (HttpKernelInterface::MASTER_REQUEST o HttpKernelInterface::SUB_REQUEST).
isMasterRequest()
Verifica se è una richiesta principale.
getKernel()
Restituisce il kernel che gestisce la richiesta.
getRequest()
Restituisce la Request attualmente in gestione.

isMasterRequest()

Il metodo isMasterRequest() consente di sapere il tipo di richiesta. Per esempio, se un ascoltatore deve essere attivo solo per richieste principali, aggiungere il seguente codice all’inizio del proprio metodo ascoltatore:

use Symfony\Component\HttpKernel\HttpKernelInterface;

if (!$event->isMasterRequest()) {
    // uscire subito
    return;
}

Suggerimento

Se non si ha familiarità con il distributore di eventi di Symfony, leggere prima la documentazione del componente EventDispatcher.

Evento kernel.request

Classe evento: Symfony\Component\HttpKernel\Event\GetResponseEvent

Lo scopo di questo evento e di restituire subito un oggetto Response oppure impostare delle variabili in modo che il controllore sia richiamato dopo l’evento. Qualsiasi ascoltatore può restituire un oggetto Response, tramite il metodo setResponse() sull’evento. In questo caso, tutti gli altri ascoltatori non saranno richiamati.

Questo evento è usato da FrameworkBundle per popolare l’attributo _controller della Request, tramite Symfony\Bundle\FrameworkBundle\EventListener\RouterListener. RequestListener usa un oggetto Symfony\Component\Routing\RouterInterface per corrispondere alla Request e determinare il nome del controllore (memorizzato nell’attributo _controller di Request).

Vedi anche

Approfondire l’evento kernel.request.

Evento kernel.controller

Classe evento: Symfony\Component\HttpKernel\Event\FilterControllerEvent

Questo evento non è usato da FrameworkBundle, ma può essere un punto di ingresso usato per modificare il controllore da eseguire:

use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

public function onKernelController(FilterControllerEvent $event)
{
    $controller = $event->getController();
    // ...

    // il controllore può essere cambiato da qualsiasi funzione PHP
    $event->setController($controller);
}

Vedi anche

Approfondire l’evento kernel.controller.

Evento kernel.view

Classe evento: Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent

Questo evento non è usato da FrameworkBundle, ma può essere usato per implementare un sotto-sistema di viste. Questo evento è chiamato solo se il controllore non restituisce un oggetto Response. Lo scopo dell’evento è di consentire a qualcun altro di restituire un valore da convertire in una Response.

Il valore restituito dal controllore è accessibile tramite il metodo getControllerResult:

use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\Response;

public function onKernelView(GetResponseForControllerResultEvent $event)
{
    $val = $event->getControllerResult();
    $response = new Response();

    // ... personalizzare in qualche modo la risposta dal valore restituito

    $event->setResponse($response);
}

Vedi anche

Approfondire l’evento kernel.view.

Evento kernel.response

Classe evento: Symfony\Component\HttpKernel\Event\FilterResponseEvent

Lo scopo di questo evento è di consentire ad altri sistemi di modificare o sostituire l’oggetto Response dopo la sua creazione:

public function onKernelResponse(FilterResponseEvent $event)
{
    $response = $event->getResponse();

    // ... modificare l'oggetto Response
}

FrameworkBundle registra diversi ascoltatori:

Symfony\Component\HttpKernel\EventListener\ProfilerListener
Raccoglie dati per la richiesta corrente.
Symfony\Bundle\WebProfilerBundle\EventListener\WebDebugToolbarListener
Inserisce la barra di web debug.
Symfony\Component\HttpKernel\EventListener\ResponseListener
Aggiusta il Content-Type della risposta, in base al formato della richiesta.
Symfony\Component\HttpKernel\EventListener\EsiListener
Aggiunge un header HTTP Surrogate-Control quando si deve cercare dei tag ESI nella risposta.

Vedi anche

Approfondire l’evento kernel.response.

Evento kernel.finish_request

Classe evento: Symfony\Component\HttpKernel\Event\FinishRequestEvent

Lo scopo di questo evento è quello di gestire compiti da eseguire dopo che la richiesta è stata gestita, ma che non necessitano di modificare la risposta. Gli ascoltatori dell’evento kernel.finish_request sono richiamati sia in caso di successo sia in caso di eccezioni.

Evento kernel.terminate

Classe evento: Symfony\Component\HttpKernel\Event\PostResponseEvent

Lo scopo di questo evento è quello di eseguire compiti più “pesanti”, dopo che la risposta sia stata inviata al client.

Vedi anche

Approfondire l’evento kernel.terminate.

Evento kernel.exception

Classe evento: Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent

FrameworkBundle registra un Symfony\Component\HttpKernel\EventListener\ExceptionListener, che gira la Request a un controllore dato (il valore del parametro exception_listener.controller, che deve essere nel formato classe::metodo).

Un ascoltatore di questo evento può creare e impostare un oggetto Response, creare e impostare un nuovo oggetto Exception, oppure non fare nulla:

use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;

public function onKernelException(GetResponseForExceptionEvent $event)
{
    $exception = $event->getException();
    $response = new Response();
    // prepara l'oggetto Response in base all'eccezione catturata
    $event->setResponse($response);

    // in alternativa si può impostare una nuova eccezione
    // $exception = new \Exception('Una qualche eccezione speciale');
    // $event->setException($exception);
}

Nota

Poiché Symfony assicura che il codice di stato della risposta sia impostato nel modo più appropriato a seconda dell’eccezione, impostare lo stato nella risposta non funziona. Se si vuole sovrascrivere il codice di stato (che non andrebbe fatto senza buone ragioni), impostare l’header X-Status-Code:

return new Response(
    'Error',
    Response::HTTP_NOT_FOUND, // ignorato
    array('X-Status-Code' => Response::HTTP_OK)
);

Nuovo nella versione 2.4: Il supporto per le costanti dei codici di stato HTTP è stato aggiunto in Symfony 2.4.

Vedi anche

Approfondire l’evento kernel.exception.

Il distributore di eventi

Event Dispatcher (distributore di eventi) è un componente, responsabile di gran parte della logica sottostante e del flusso dietro a una richiesta di Symfony. Per maggiori informazioni, vedere la documentazione del componente Event Dispatcher.

Profilatore

Se abilitato, il profilatore di Symfony raccoglie informazioni utili su ogni richiesta fatta alla propria applicazione e le memorizza per analisi successive. L’uso del profilatore in ambienti di sviluppo aiuta il debug del proprio codice e a migliorare le prestazioni. Lo si può usare anche in ambienti di produzione, per approfondire i problemi che si presentano.

Raramente si avrà a che fare direttamente con il profilatore, visto che Symfony fornisce strumenti di visualizzazione, come la barra di web debug e il profilatore web. Se si usa Symfony Standard Edition, il profilatore, la barra di web debug e il profilatore web sono già configurati con impostazioni appropriate.

Nota

Il profilatore raccoglie informazioni per tutte le richieste (richieste semplici, rinvii, eccezioni, richieste Ajax, richieste ESI) e per tutti i metodi e formati HTTP. Questo vuol dire che per un singolo URL si possono avere diversi dati di profilo associati (uno per ogni coppia richiesta/risposta esterna).

Visualizzare i dati di profilo

Usare la barra di web debug

In ambiente di sviluppo, la barra di web debug è disponibile in fondo a ogni pagina. Essa mostra un buon riassunto dei dati di profile, che danno accesso immediato a moltissime informazioni utili, quando qualcosa non funziona come ci si aspetta.

Se il riassunto fornito dalla barra di web debug non basta, cliccare sul collegamento del token (una stringa di 13 caratteri casuali) per accedere al profilatore web.

Nota

Se il token non è cliccabile, vuol dire che le rotte del profilatore non sono state registrate (vedere sotto per le informazioni sulla configurazione).

Analizzare i dati di profilo con il profilatore web

Il profilatore web è uno strumento di visualizzazione per i dati di profilo, che può essere usato in sviluppo per il debug del codice e l’aumento delle prestazioni. Ma lo si può anche usare per approfondire problemi occorsi in produzione. Espone tutte le informazioni raccolte dal profilatore in un’interfaccia web.

Accedere alle informazioni di profilo

Non occorre usare il visualizzatore predefinito per accedere alle informazioni di profilo. Ma come si possono recuperare informazioni di profilo per una specifica richiesta, dopo che è accaduta? Quando il profilatore memorizza i dati su una richiesta, vi associa anche un token. Questo token è disponibile nell’header HTTP X-Debug-Token della risposta:

$profile = $container->get('profiler')->loadProfileFromResponse($response);

$profile = $container->get('profiler')->loadProfile($token);

Suggerimento

Quando il profilatore è abiliato, ma non lo è la barra di web debug, oppure quando si vuole il token di una richiesta Ajax, usare uno strumento come Firebug per ottenere il valore dell’header HTTP X-Debug-Token.

Usare il metodo find() per accedere ai token, in base a determinati criteri:

// gli ultimi 10 token
$tokens = $container->get('profiler')->find('', '', 10, '', '');

// gli ultimi 10 token per URL che contengono /admin/
$tokens = $container->get('profiler')->find('', '/admin/', 10, '', '');

// gli ultimi 10 token per richieste locali
$tokens = $container->get('profiler')->find('127.0.0.1', '', 10, '', '');

// gli ultimi 10 token per richieste tra 2 e 4 giorni fa
$tokens = $container->get('profiler')
    ->find('', '', 10, '4 days ago', '2 days ago');

Se si vogliono manipolare i dati di profilo su macchine diverse da quella che ha generato le informazioni, usare i metodi export() e import():

// sulla macchina di produzione
$profile = $container->get('profiler')->loadProfile($token);
$data = $profiler->export($profile);

// sulla macchina di sviluppo
$profiler->import($data);

Configurazione

La configurazione predefinita di Symfony ha delle impostazioni adeguate per il profilatore, la barra di web debug e il profilatore web. Ecco per esempio la configurazione per l’ambiente di sviluppo:

  • YAML
    # carica il profilatore
    framework:
        profiler: { only_exceptions: false }
    
    # abilita il profilatore web
    web_profiler:
        toolbar: true
        intercept_redirects: true
    
  • XML
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:webprofiler="http://symfony.com/schema/dic/webprofiler"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/webprofiler
            http://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd
            http://symfony.com/schema/dic/symfony
            http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <!-- carica il profilatore -->
        <framework:config>
            <framework:profiler only-exceptions="false" />
        </framework:config>
    
        <!-- abilita il profilatore web -->
        <webprofiler:config
            toolbar="true"
            intercept-redirects="true" />
    </container>
    
  • PHP
    // carica il profilatore
    $container->loadFromExtension('framework', array(
        'profiler' => array('only_exceptions' => false),
    ));
    
    // abilita il profilatore web
    $container->loadFromExtension('web_profiler', array(
        'toolbar'             => true,
        'intercept_redirects' => true,
    ));
    

Quando only_exceptions è impostato a true, il profilatore raccoglie dati solo quando l’applicazione solleva un’eccezione.

Quando intercept-redirects è impostata true, il profilatore web intercetta i rinvii e dà l’opportunità di guardare i dati raccolti, prima di seguire il rinvio.

Se si abilita il profilatore web, occorre anche montare le rotte del profilatore:

  • YAML
    _profiler:
        resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
        prefix:   /_profiler
    
  • XML
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <import
            resource="@WebProfilerBundle/Resources/config/routing/profiler.xml"
            prefix="/_profiler" />
    </routes>
    
  • PHP
    use Symfony\Component\Routing\RouteCollection;
    
    $profiler = $loader->import(
        '@WebProfilerBundle/Resources/config/routing/profiler.xml'
    );
    $profiler->addPrefix('/_profiler');
    
    $collection = new RouteCollection();
    $collection->addCollection($profiler);
    

Poiché il profilatore aggiunge un po’ di sovraccarico, probabilmente lo si abiliterà solo in alcune circostanze in ambiente di produzione. L’impostazione only-exceptions limita il profilo alle pagine 500, ma che succede se si vogliono più informazioni quando il client ha uno specifico indirizzo IP, oppure per una parte limitata del sito? Si può usare un Profiler Matcher, su cui si può approfondire in “Usare un Matcher per abilitare dinamicamente il profilatore”.