Bootstrapping

Il bootstrapping è il processo di inizializzazione dell'ambiente dell'applicazione, di creazione del container di dependency injection (DI) e di avvio dell'applicazione. Parleremo di:

  • come la classe Bootstrap inizializza l'ambiente
  • come si configurano le applicazioni con i file NEON
  • come distinguere tra modalità di produzione e di sviluppo
  • come creare e configurare il container DI

Le applicazioni, siano esse web o script eseguiti dalla riga di comando, iniziano la propria esecuzione con una qualche forma di inizializzazione dell'ambiente. In passato se ne occupava un file chiamato magari include.inc.php, incluso dal file iniziale. Nelle moderne applicazioni Nette è stato sostituito dalla classe Bootstrap, che, in quanto parte dell'applicazione, si trova nel file app/Bootstrap.php. Potrebbe avere per esempio questo aspetto:

namespace App;

use Nette;
use Nette\Bootstrap\Configurator;

class Bootstrap
{
	private Configurator $configurator;
	private string $rootDir;

	public function __construct()
	{
		$this->rootDir = dirname(__DIR__);
		// il configurator si occupa di impostare l'ambiente dell'applicazione e i servizi.
		$this->configurator = new Configurator;
		// imposta la directory dei file temporanei generati da Nette (per esempio i template compilati)
		$this->configurator->setTempDirectory($this->rootDir . '/temp');
	}

	public function bootWebApplication(): Nette\DI\Container
	{
		$this->initializeEnvironment();
		$this->setupContainer();
		return $this->configurator->createContainer();
	}

	private function initializeEnvironment(): void
	{
		// Nette è furbo e la modalità di sviluppo si attiva da sola,
		// oppure potete attivarla per un determinato indirizzo IP togliendo il commento alla riga seguente:
		// $this->configurator->setDebugMode('secret@23.75.345.200');

		// attiva Tracy: il "coltellino svizzero" definitivo per il debugging.
		$this->configurator->enableTracy($this->rootDir . '/log');

		// RobotLoader: carica automaticamente tutte le classi della directory scelta
		$this->configurator->createRobotLoader()
			->addDirectory(__DIR__)
			->register();
	}

	private function setupContainer(): void
	{
		// carica i file di configurazione
		$this->configurator->addConfig($this->rootDir . '/config/common.neon');
	}
}

index.php

Nel caso delle applicazioni web il file iniziale è index.php, che si trova nella directory pubblica www/. Chiede alla classe Bootstrap di inizializzare l'ambiente e di creare il container DI. Poi ottiene dal container il servizio Application, che esegue l'applicazione web:

$bootstrap = new App\Bootstrap;
// inizializza l'ambiente e crea il container DI
$container = $bootstrap->bootWebApplication();
// il container DI crea un oggetto Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
// avvia l'applicazione Nette ed elabora la richiesta in arrivo
$application->run();

Elaborando la richiesta, l'oggetto $application emette eventi: onStartup, onRequest, onPresenter, onResponse, onShutdown e onError (in caso di eccezione non gestita). Potete agganciarvi dei gestori, il che torna comodo per il logging o per il monitoraggio dell'intera applicazione.

Come vedete, la classe Nette\Bootstrap\Configurator aiuta a impostare l'ambiente e a creare il container di dependency injection (DI). Ve la presentiamo ora più in dettaglio.

Modalità di sviluppo e di produzione

Nette si comporta in modo diverso a seconda che giri su un server di sviluppo o di produzione:

🛠️ Modalità di sviluppo
mostra la barra di debug di Tracy con informazioni utili (query SQL, tempo di esecuzione, memoria usata)
in caso di errore mostra una pagina di errore dettagliata, con le chiamate di funzione e il contenuto delle variabili
aggiorna automaticamente la cache quando cambiano i template Latte, i file di configurazione ecc.
🚀 Modalità di produzione
non mostra alcuna informazione di debug; tutti gli errori vengono scritti nel log
in caso di errore mostra un ErrorPresenter oppure una pagina generica “Server Error”
la cache non viene mai aggiornata automaticamente!
ottimizzata per velocità e sicurezza

La scelta della modalità avviene per rilevamento automatico, quindi di norma non c'è bisogno di configurare nulla né di cambiare modalità manualmente:

  • modalità di sviluppo: su localhost (indirizzo IP 127.0.0.1 o ::1) se non è presente un proxy (cioè se non viene rilevato il suo header HTTP)
  • modalità di produzione: ovunque altrove

Se vogliamo attivare la modalità di sviluppo anche in altri casi, per esempio per i programmatori che accedono da un determinato indirizzo IP, usiamo setDebugMode():

$this->configurator->setDebugMode('23.75.345.200'); // si può indicare anche un array di indirizzi IP

Consigliamo vivamente di combinare l'indirizzo IP con un cookie. Salvate un token segreto, per esempio secret1234, nel cookie nette-debug e attivate così la modalità di sviluppo per i programmatori che accedono da un determinato indirizzo IP e che hanno anche il token indicato nel cookie:

$this->configurator->setDebugMode('secret1234@23.75.345.200');

Possiamo anche disattivare del tutto la modalità di sviluppo, perfino su localhost:

$this->configurator->setDebugMode(false);

Attenzione: il valore true forza l'attivazione della modalità di sviluppo, cosa che su un server di produzione non deve mai accadere.

Del rilevamento automatico si occupa internamente il metodo statico Configurator::detectDebugMode(), che potete chiamare anche voi, per esempio per rilevare la modalità di sviluppo fuori dal configurator. Accetta un elenco facoltativo di indirizzi IP o di nomi di computer autorizzati e restituisce se la richiesta corrente deve girare in modalità di sviluppo:

$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');

Lo strumento di debug Tracy

Per un debugging semplice attiviamo l'eccellente strumento Tracy. In modalità di sviluppo visualizza gli errori, in modalità di produzione li registra nella directory indicata:

$this->configurator->enableTracy($this->rootDir . '/log');

File temporanei

Nette usa la cache per il container DI, per RobotLoader, per i template ecc. È quindi necessario impostare il percorso della directory in cui la cache verrà salvata:

$this->configurator->setTempDirectory($this->rootDir . '/temp');

Su Linux o macOS impostate i permessi di scrittura per le directory log/ e temp/.

RobotLoader

Di solito vorremo caricare automaticamente le classi con RobotLoader, quindi dobbiamo avviarlo e fargli caricare le classi dalla directory in cui si trova Bootstrap.php (cioè __DIR__) e da tutte le sue sottodirectory:

$this->configurator->createRobotLoader()
	->addDirectory(__DIR__)
	->register();

Un approccio alternativo è caricare le classi esclusivamente tramite Composer, secondo PSR-4.

Fuso orario

Tramite il configurator potete impostare il fuso orario predefinito.

$this->configurator->setTimeZone('Europe/Prague');

Configurazione del container DI

Parte del processo di avvio è la creazione del container DI, cioè della factory di oggetti, che è il cuore dell'intera applicazione. È in realtà una classe PHP generata da Nette e salvata nella directory della cache. La factory produce gli oggetti chiave dell'applicazione e con i file di configurazione le diciamo come crearli e impostarli, influenzando così il comportamento dell'intera applicazione.

I file di configurazione si scrivono di norma nel formato NEON. In un capitolo a parte potete leggere cosa si può configurare.

In modalità di sviluppo il container viene aggiornato automaticamente ogni volta che cambiano il codice o i file di configurazione. In modalità di produzione viene generato una sola volta e le modifiche non vengono controllate, per massimizzare le prestazioni.

Mentre createContainer() costruisce il container e ne restituisce l'istanza, il metodo loadContainer() restituisce solo il nome della classe del container generata, che potete poi istanziare voi. È utile negli scenari avanzati.

I file di configurazione si caricano con addConfig():

$this->configurator->addConfig($this->rootDir . '/config/common.neon');

Se vogliamo aggiungere altri file di configurazione, possiamo chiamare la funzione addConfig() più volte.

$configDir = $this->rootDir . '/config';
$this->configurator->addConfig($configDir . '/common.neon');
$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
	$this->configurator->addConfig($configDir . '/cli.php');
}

Il nome cli.php non è un errore di battitura: la configurazione si può scrivere anche in un file PHP che la restituisce come array.

Possiamo aggiungere altri file di configurazione anche nella sezione includes.

Se nei file di configurazione compaiono elementi con le stesse chiavi, essi verranno sovrascritti oppure, nel caso degli array, uniti. Un file incluso più tardi ha priorità maggiore rispetto al precedente. Il file in cui è indicata la sezione includes ha priorità maggiore rispetto ai file inclusi al suo interno.

Parametri statici

I parametri usati nei file di configurazione si possono definire nella sezione parameters e anche passare (o sovrascrivere) con il metodo addStaticParameters() (il cui vecchio alias, ora deprecato, è addParameters()). È importante sapere che valori diversi dei parametri provocano la generazione di altri container DI, cioè di altre classi.

$this->configurator->addStaticParameters([
	'projectId' => 23,
]);

Nella configurazione si può fare riferimento al parametro projectId con la notazione consueta %projectId%.

Parametri dinamici

Al container possiamo aggiungere anche parametri dinamici, i cui valori diversi, a differenza dei parametri statici, non provocano la generazione di nuovi container DI.

$this->configurator->addDynamicParameters([
	'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);

Così possiamo aggiungere facilmente, per esempio, le variabili d'ambiente, alle quali si può poi fare riferimento nella configurazione con la notazione %env.variabile%.

$this->configurator->addDynamicParameters([
	'env' => getenv(),
]);

Parametri predefiniti

Nei file di configurazione potete usare questi parametri:

  • %appDir% è il percorso assoluto della directory che contiene il file Bootstrap.php
  • %wwwDir% è il percorso assoluto della directory che contiene il file d'ingresso index.php
  • %tempDir% è il percorso assoluto della directory dei file temporanei
  • %vendorDir% è il percorso assoluto della directory in cui Composer installa le librerie
  • %rootDir% è il percorso assoluto della directory radice del progetto
  • %baseUrl% è l'URL assoluto della directory radice (un parametro dinamico, risolto in fase di esecuzione)
  • %debugMode% indica se l'applicazione è in modalità debug
  • %consoleMode% indica se la richiesta è arrivata dalla riga di comando

Servizi importati

Ora andiamo più a fondo. Benché lo scopo del container DI sia creare oggetti, di tanto in tanto può nascere l'esigenza di inserire nel container un oggetto già esistente. Lo facciamo definendo il servizio con il flag imported: true.

services:
	myservice:
		type: App\Model\MyCustomService
		imported: true

E nel bootstrap inseriamo l'oggetto nel container:

$this->configurator->addServices([
	'myservice' => new App\Model\MyCustomService('foobar'),
]);

Ambienti diversi

Modificate pure la classe Bootstrap secondo le vostre esigenze. Potete aggiungere parametri al metodo bootWebApplication() per distinguere tra progetti web. Oppure possiamo aggiungere altri metodi, come bootTestEnvironment(), che inizializza l'ambiente per i test unitari, bootConsoleApplication() per gli script chiamati dalla riga di comando ecc.

public function bootTestEnvironment(): Nette\DI\Container
{
	Tester\Environment::setup(); // inizializzazione di Nette Tester
	$this->setupContainer();
	return $this->configurator->createContainer();
}

public function bootConsoleApplication(): Nette\DI\Container
{
	$this->configurator->setDebugMode(false);
	$this->initializeEnvironment();
	$this->setupContainer();
	return $this->configurator->createContainer();
}
versione: 4.x