Parte 6 - Pulizia

Scopriamo insieme gli attimi che precedono l'avvio definitivo dell'applicazione.
francesco
Francesco Malatesta
16/10/2014 in

In questa serie, traduzione autorizzata di quella pubblicata da Christopher Pitt su Rebuilding Laravel, esploreremo in profondità il framework in tutti i suoi aspetti. Dall'autoloading alla configurazione, passando per le operazioni di pulizia ed il routing.

Rimane ancora poco prima dell'avvio dell'applicazione.

Tipi di Richiesta

Continua l'esplorazione in vendor/../Foundation/start.php, dove notiamo:

Request::enableHttpMethodParameterOverride();

Siamo in vendor/laravel/framework/src/Illuminate/Foundation/start.php.

Entrare nello specifico di questo metodo richiederebbe molto più tempo di quello che ho intenzione di spendere per questo articolo. Ad ogni modo, diciamo che abilita l'uso di campi di form nascosti per trasmettere il metodo di request desiderato.

Come probabilmente già sai, la specifica HTTP permette svariati tipi di richiesta (GET, POST, PUT, DELETE...). Tuttavia, molti browser non li supportano tutti. Molto spesso gli unici "permessi" sono i classici GET e POST. Per questa ragione, quindi, un modo piuttosto comune di simulare la richiesta è specificarne il tipo in un campo nascosto del form.

Con questa chiamata a enableHttpMethodParameterOverride() puoi creare i tuoi form con un input nascosto chiamato _method. Tale campo conterrà il nome del tipo di richiesta di cui hai bisogno. Il framework, automaticamente, agirà come se la richiesta sia di quel tipo.

Nota: lo stesso discorso, chiaramente, vale anche per le richieste AJAX.

Ancora Service Provider

Seguendo il percorso, vendor/../Foundation/start.php carica il resto dei service provider.

$providers = $config['providers'];
$app->getProviderRepository()->load($app, $providers);

Siamo in vendor/laravel/framework/src/Illuminate/Foundation/start.php.

Torniamo ora alla classe Application:

public function getProviderRepository()
{
  $manifest = $this['config']['app.manifest'];
 
  return new ProviderRepository(new Filesystem, $manifest);
}

Siamo in vendor/laravel/framework/src/Illuminate/Foundation/Application.php.

I service provider sono fondamentalmente di due tipi. Quelli dal caricamento "dilazionato" (o deferred) e quelli che invece vengono immediatamente caricati. Questo è l'obiettivo del file manifest dei servizi. Vediamo com'è:

public function load(Application $app, array $providers)
{
  $manifest = $this->loadManifest();
  
  if ($this->shouldRecompile($manifest, $providers))
  {
    $manifest = $this->compileManifest($app, $providers);
  }
  
  if ($app->runningInConsole())
  {
    $manifest['eager'] = $manifest['providers'];
  }
  
  foreach ($manifest['when'] as $provider => $events)
  {
    $this->registerLoadEvents($app, $provider, $events);
  }
  
  foreach ($manifest['eager'] as $provider)
  {
    $app->register($this->createProvider($app, $provider));
  }
  
  $app->setDeferredServices($manifest['deferred']);
}

Siamo in vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php.

L'obiettivo principale di questo metodo è il caricare il file manifest (e a volte ricompilarlo) in modo tale da determinare quando effettivamente ogni service provider deve essere caricato. Le classi "marcate" come "eager" (quindi non le deferred) vengono create e registrate.

Il manifest file contiene una copia dell'array dei provider che viene passato nella chiamata a load(). Se l'array nel manifest file non coincide con quello del file di configurazione allora il manifest viene rigenerato. Un'altra cosa da notare è che i deferred provider non vengono visti come tali quando l'app viene avviata da console.

Se un service provider è segnato come "deferred" verrà caricato solo in caso di richiesta (usando il Container). Ogni service provider inoltre può definire una funzine (provides()) che ritorna un array di chiavi del Container. Se una di queste chiavi è quella necessaria, questa verrà collegata al deferred provider, tramite il manifest file. Il provider corrispondente verrà registrato ed avviato.

Per illustrare meglio questo punto assumiamo che tu abbia un provider come il seguente:

class NotifierService
{
  protected $defer = true;
  
  public function register()
  {
    $this->app->bind("notifier", function() {
      return new Notifier();
    });
  }
  
  public function boot()
  {
    // ...do some boot processing
  }
  
  public function provides()
  {
    return ["notifier"];
  }
}

Il service provider verrà registrato e avviato SOLO al suo richiamo tramite:

$notifier = App::make("notifier");

Avviato!

La parte finale di questo file comincia con la creazione di una callback.

$app->booted(function() use ($app, $env)

Siamo in vendor/laravel/framework/src/Illuminate/Foundation/start.php.

Il codice che segue verrà eseguito solo quando il resto dell'applicazione avrà completato il caricamento. Vedremo dopo i dettagli a riguardo.

Quello che rimane del codice, in questo file, è dedicato al caricamento degli start file, che possono essere trovati in app/start. Il file app/start/global.php verrà sempre incluso, mentre un file coerente con l'ambiente attuale (come ad esempio _app/start/local.php) verrà caricato di conseguenza.

A questo punto dovrebbe esserci il caricamento del file app/routes.php.

Torniamo in public/index.php, dove potremo vedere la linea:

$app->run();

Siamo in vendor/laravel/framework/src/Illuminate/Foundation/start.php.

Possiamo quindi vedere che la classe Application definisce un metodo run():

public function run(SymfonyRequest $request = null)
{
  $request = $request ?: $this['request'];
  
  $response = with(
    $stack = $this->getStackedClient()
  )->handle($request);
  
  $response->send();
  
  $stack->terminate($request, $response);
}

Siamo in vendor/laravel/framework/src/Illuminate/Foundation/Application.php.

Questo metodo prende l'oggetto request e lo gestisce tramite una "cosa" chiamata Stacked Client. Si tratta del middleware che abbiamo visto prima. Alcune classi vengono messe sullo stack in modo tale da "influenzare" l'input e l'output senza modificare il resto del codice dell'applicazione.

protected function getStackedClient()
{
  $sessionReject = $this->bound('session.reject') ? 
    $this['session.reject'] : null;
  
  $client = with(new \Stack\Builder)
    ->push('Illuminate\Cookie\Guard', $this['encrypter'])
    ->push('Illuminate\Cookie\Queue', $this['cookie'])
    ->push(
      'Illuminate\Session\Middleware',
      $this['session'],
      $sessionReject
    );
  
  $this->mergeCustomMiddlewares($client);
  
  return $client->resolve($this);
}

Siamo in vendor/laravel/framework/src/Illuminate/Foundation/Application.php.

Questa classe Stack\Builder agisce come una lista di modificatori sul risultato del metodo handle() della classe Application. Questo metodo handle() è responsabile di passare la richiesta al router.

Le specifiche sono un po' difficili da spiegare senza una conoscenza pregressa del router e dell'implementazione della request. Vedremo tutto meglio a breve, anche se in ogni caso arrivati a questo punto l'applicazione è stata definitivamente avviata!