Come funzionano le applicazioni?
State leggendo il capitolo fondamentale della documentazione di Nette. Imparerete tutti i principi del funzionamento delle applicazioni web, dalla A alla Z, dal momento in cui nasce una richiesta fino a quando lo script PHP termina l'esecuzione. Dopo la lettura capirete:
- come funziona il tutto
- cosa sono Bootstrap, il presenter e il container DI
- che aspetto ha la struttura delle directory
Struttura delle directory
Aprite lo scheletro di esempio di un'applicazione web, chiamato WebProject. Mentre leggete potete consultare i file di cui si parla.
La struttura delle directory ha più o meno questo aspetto:
web-project/ ├── app/ ← directory dell'applicazione │ ├── Core/ ← classi fondamentali necessarie al funzionamento │ │ └── RouterFactory.php ← configurazione degli indirizzi URL │ ├── Presentation/ ← presenter, template & co. │ │ ├── @layout.latte ← template del layout │ │ └── Home/ ← directory del presenter Home │ │ ├── HomePresenter.php ← classe del presenter Home │ │ └── default.latte ← template dell'azione default │ └── Bootstrap.php ← classe di avvio Bootstrap ├── assets/ ← risorse (SCSS, TypeScript, immagini sorgente) ├── bin/ ← script eseguiti dalla riga di comando ├── config/ ← file di configurazione │ ├── common.neon │ └── services.neon ├── log/ ← errori registrati ├── temp/ ← file temporanei, cache, … ├── vendor/ ← librerie installate da Composer │ ├── ... │ └── autoload.php ← autoloading di tutti i pacchetti installati ├── www/ ← directory pubblica, document root del progetto │ ├── assets/ ← file statici compilati (CSS, JS, immagini, ...) │ ├── .htaccess ← regole di mod_rewrite │ └── index.php ← file iniziale che avvia l'applicazione └── .htaccess ← vieta l'accesso a tutte le directory tranne www
Potete cambiare la struttura delle directory in qualsiasi modo, rinominare o spostare le cartelle: è del tutto flessibile. Nette dispone inoltre di un rilevamento automatico intelligente e riconosce da sé la posizione dell'applicazione, compresa la base del suo URL.
Nelle applicazioni un po' più grandi possiamo organizzare le cartelle dei presenter e dei template in sottodirectory e raggruppare le classi in namespace, che chiamiamo moduli.
La directory www/ rappresenta la directory pubblica, il document-root del progetto. Potete rinominarla senza dover
configurare nient'altro sul lato applicativo. Basta configurare l'hosting
in modo che il document-root punti a questa directory.
Potete anche scaricare WebProject direttamente, Nette compreso, con Composer:
composer create-project nette/web-project
Su Linux o macOS impostate i permessi di scrittura per le
directory log/ e temp/.
L'applicazione WebProject è pronta all'uso: non c'è proprio nulla da configurare e potete vederla subito nel browser
accedendo alla cartella www/.
Richiesta HTTP
Tutto inizia quando un utente apre una pagina nel proprio browser. Il browser invia una richiesta HTTP al server. Questa
richiesta punta a un unico file PHP che si trova nella directory pubblica www/, cioè index.php.
Supponiamo che la richiesta sia per l'indirizzo https://example.com/product/123. Grazie a un'opportuna configurazione del
server, anche questo URL viene mappato sul file index.php, che viene poi eseguito.
Il suo compito è:
- inizializzare l'ambiente
- ottenere la factory
- avviare l'applicazione Nette, che gestisce la richiesta
Quale factory? Non stiamo mica producendo trattori, stiamo costruendo siti web! Un attimo di pazienza, verrà spiegato tra poco.
Per “inizializzazione dell'ambiente” intendiamo, per esempio, l'attivazione di Tracy, un fantastico strumento per registrare o visualizzare gli errori. Su un server di produzione registra gli errori, in ambiente di sviluppo li mostra direttamente. L'inizializzazione comprende quindi anche stabilire se il sito gira in modalità produzione o sviluppo. Nette usa a questo scopo un rilevamento automatico intelligente: se eseguite il sito su localhost, lavora in modalità di sviluppo. Non dovete configurare nulla e l'applicazione è subito pronta sia per lo sviluppo sia per la messa online. Questi passaggi vengono eseguiti e descritti in dettaglio nel capitolo sulla classe Bootstrap.
Il terzo punto (sì, abbiamo saltato il secondo, ma ci torneremo) è l'avvio dell'applicazione. In Nette la gestione delle
richieste HTTP è compito della classe Nette\Application\Application (d'ora in poi Application). Quando
quindi diciamo “avviare l'applicazione”, intendiamo concretamente chiamare il metodo, dal nome azzeccato, run()
su un oggetto di questa classe.
Nette fa da mentore e vi guida a scrivere applicazioni pulite secondo metodologie collaudate. Una delle più consolidate è la
dependency injection, in breve DI. Non vogliamo appesantirvi ora con la spiegazione della DI: c'è un capitolo dedicato. La conseguenza essenziale è che gli
oggetti chiave vengono di norma creati da una factory di oggetti nota come container DI (o DIC). Sì, è la factory di cui
si parlava prima. È lei a produrre per noi anche l'oggetto Application, ed è per questo che ci serve prima il
container. Lo otteniamo con la classe Configurator, gli facciamo creare l'oggetto Application, ne
chiamiamo il metodo run() e così l'applicazione Nette parte. È esattamente quello che avviene nel file index.php.
Nette Application
La classe Application ha un solo compito: rispondere alla richiesta HTTP.
Le applicazioni scritte in Nette sono divise in tanti cosiddetti presenter (in altri framework potreste incontrare il termine “controller”, che è in sostanza la stessa cosa). Sono classi che rappresentano ciascuna una determinata pagina del sito: per esempio la home page, un prodotto di un e-shop, un form di login, un feed sitemap e così via. Un'applicazione può avere da uno a migliaia di presenter.
Application comincia chiedendo al cosiddetto router di decidere quale presenter deve gestire la richiesta
corrente. È il router a stabilire la competenza. Esamina l'URL in ingresso https://example.com/product/123 e, in
base alla propria configurazione, decide che questo compito spetta per esempio al presenter Product, che deve
eseguire l'azione show per il prodotto con id: 123. È buona pratica scrivere la coppia presenter
+ azione separata dai due punti, come Product:show.
Il router ha quindi trasformato l'URL nella coppia Presenter:azione + parametri, nel nostro caso
Product:show + id: 123. Che aspetto abbia un router del genere lo vedete nel file
app/Core/RouterFactory.php e lo descriviamo in dettaglio nel capitolo Routing.
Andiamo avanti. Application conosce ora il nome del presenter e può proseguire. Lo fa creando un'istanza della
classe ProductPresenter, che contiene il codice del presenter Product. Più precisamente, chiede al
container DI di creare il presenter, perché la creazione degli oggetti è compito suo.
Il presenter potrebbe avere questo aspetto:
class ProductPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ProductRepository $repository,
) {
}
public function renderShow(int $id): void
{
// ottiene i dati dal modello e li passa al template
$this->template->product = $this->repository->getProduct($id);
}
}
Il presenter prende in carico la gestione della richiesta. Il compito è chiaro: eseguire l'azione show con
id: 123. Nella terminologia dei presenter questo significa che viene chiamato il metodo renderShow(),
che riceve 123 nel parametro $id.
Un presenter può gestire più azioni, può cioè avere più metodi render<Azione>(). Consigliamo però di
progettare i presenter con una sola azione o comunque con il minor numero possibile di azioni.
È stato quindi chiamato il metodo renderShow(123). Il suo codice è un esempio inventato, ma mostra come i dati
vengono passati al template, cioè scrivendoli in $this->template.
Il presenter restituisce poi una risposta. Può essere una pagina HTML, un'immagine, un documento XML, l'invio di un file dal
disco, un JSON oppure un redirect a un'altra pagina. Attenzione: se non indichiamo esplicitamente come rispondere (come nel caso
di ProductPresenter), la risposta sarà il rendering di un template in una pagina HTML. Perché? Perché nel 99% dei
casi vogliamo disegnare un template. Il presenter adotta quindi questo comportamento come predefinito, per semplificarci il
lavoro. È l'essenza di Nette.
Non dobbiamo nemmeno indicare quale template disegnare: il framework ne deduce automaticamente il percorso. Nel caso
dell'azione show prova semplicemente a caricare il template show.latte che si trova nella stessa
directory della classe ProductPresenter. Cerca inoltre il layout nel file @layout.latte (maggiori
dettagli sulla ricerca dei template).
Poi i template vengono disegnati. Così si conclude il compito del presenter e dell'intera applicazione. Se il template non esiste, viene restituita una pagina di errore 404. Potete saperne di più sui presenter nella pagina Presenter.
Per sicurezza ricapitoliamo l'intero processo con un URL leggermente diverso:
- L'URL è
https://example.com - L'applicazione si avvia, viene creato il container DI e viene eseguito
Application::run(). - Il router decodifica l'URL nella coppia
Home:default. - Viene creata un'istanza della classe
HomePresenter. - Viene chiamato il metodo
renderDefault()(se esiste). - Viene disegnato il template, per esempio
default.latte, insieme al layout, per esempio@layout.latte.
Avrete forse incontrato ora molti concetti nuovi, ma crediamo che abbiano senso. Sviluppare applicazioni in Nette è straordinariamente semplice.
Template
A proposito di template, Nette usa il sistema di template Latte. Ecco perché
i file dei template hanno estensione .latte. Latte viene usato soprattutto perché è il sistema di template più
sicuro per PHP, e anche il più intuitivo. Non dovete imparare molto di nuovo: bastano la conoscenza di PHP e qualche tag. Trovate
tutto ciò che vi serve nella documentazione.
Nel template create i link verso altri presenter e azioni così:
<a n:href="Product:show $productId">dettaglio del prodotto</a>
Basta scrivere la familiare coppia Presenter:azione al posto dell'URL vero e proprio e aggiungere gli eventuali
parametri necessari. Il trucco sta in n:href, che dice a Nette di elaborare questo attributo. Verrà allora
generato:
<a href="/product/456">dettaglio del prodotto</a>
Della generazione dell'URL si occupa il router già menzionato. I router in Nette sono eccezionali perché sanno eseguire non
solo la trasformazione da URL a coppia Presenter:azione, ma anche quella inversa: generare un URL a partire dal nome
del presenter, dall'azione e dai parametri. Grazie a questo, in Nette potete cambiare completamente il formato degli URL in tutta
l'applicazione già finita senza modificare un solo carattere nei template o nei presenter, semplicemente modificando il router.
Ciò rende possibile anche la cosiddetta canonizzazione, un'altra funzionalità unica di Nette che migliora la SEO (Search Engine
Optimization) impedendo automaticamente che lo stesso contenuto esista su URL diversi. Molti programmatori trovano questa
possibilità stupefacente.
Componenti interattivi
Dobbiamo dirvi ancora una cosa sui presenter: hanno un sistema di componenti integrato. Chi ha più esperienza ricorderà forse qualcosa di simile in Delphi o in ASP.NET Web Forms; React o Vue.js si basano su concetti in qualche modo affini. Nel mondo dei framework PHP è una funzionalità del tutto unica.
I componenti sono unità indipendenti e riutilizzabili che incorporiamo nelle pagine (cioè nei presenter). Possono essere form, datagrid, menu, sondaggi: in pratica tutto ciò che ha senso riutilizzare. Possiamo creare componenti nostri oppure attingere all'ampia scelta di componenti open source.
I componenti influenzano profondamente il modo di sviluppare le applicazioni. Aprono nuove possibilità di comporre le pagine a partire da unità già pronte. E hanno anche qualcosa in comune con Hollywood.
Container DI e configurazione
Il container DI, cioè la factory di oggetti, è il cuore dell'intera applicazione.
Niente paura, non è una scatola nera magica, come potrebbero suggerire le righe precedenti. In realtà è una classe PHP
piuttosto banale, generata da Nette e salvata nella directory della cache. Contiene molti metodi con nomi come
createServiceAbcd(), ognuno capace di creare e restituire un determinato oggetto. Sì, c'è anche un metodo
createServiceApplication__application(), che produce l'istanza di Nette\Application\Application che ci
serviva in index.php per avviare l'applicazione. Ci sono anche i metodi per creare i singoli presenter e
così via.
Gli oggetti creati dal container DI si chiamano, per qualche motivo, servizi.
La cosa davvero particolare di questa classe è che non la programmate voi: la genera il framework. Genera davvero il codice
PHP e lo salva su disco. Voi vi limitate a fornire le istruzioni su quali oggetti il container deve saper creare, e come
esattamente. Queste istruzioni si scrivono nei file di configurazione, che
usano il formato NEON e hanno quindi estensione .neon.
I file di configurazione servono esclusivamente a istruire il container DI. Se quindi indicate, per esempio, l'opzione
expiration: 14 days nella sezione session, il
container DI, creando l'oggetto Nette\Http\Session che rappresenta la sessione, ne chiamerà il metodo
setExpiration('14 days'), rendendo così reale la configurazione.
C'è un intero capitolo pronto per voi che descrive cosa si può configurare e come definire i propri servizi.
Appena vi addentrerete un po' nella creazione dei servizi, incontrerete il termine autowiring. È una funzionalità che vi semplificherà incredibilmente la vita: sa passare automaticamente gli oggetti dove vi servono (per esempio nei costruttori delle vostre classi), senza che dobbiate fare nulla. Scoprirete che il container DI di Nette è un piccolo miracolo.
E adesso?
Abbiamo trattato i principi fondamentali delle applicazioni Nette. Finora è stata una panoramica superficiale, ma presto approfondirete e col tempo creerete applicazioni web straordinarie. Dove andare adesso? Avete già provato il tutorial Crea la tua prima applicazione?
Oltre a quanto descritto sopra, Nette offre tutto un arsenale di classi utili, uno strato per il database e altro ancora. Provate a curiosare nella documentazione. Oppure visitate il blog. Scoprirete molte cose interessanti.
Che il framework vi porti molta gioia 💙