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.1o::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 fileBootstrap.php%wwwDir%è il percorso assoluto della directory che contiene il file d'ingressoindex.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();
}