
GUIDE PER ASPIRANTI PROGRAMMATORI
Guida Laravel in Italiano
Scoprirai cosa è Laravel e come sviluppare applicazioni web robuste e funzionali, sfruttando tutti i vantaggi offerti da PHP e le utilità messe a disposizione dal framework Laravel per gestire con semplicità ogni necessità tipica dello sviluppo del backend (ma anche del frontend) di una moderna web application


Vuoi avviare una nuova carriera o fare un upgrade?
Trova il corso Digital & Tech più adatto a te nel nostro catalogo!
1
Introduzione ad HTTP, Composer e Laravel
1.1
Che cos'è HTTP
HTTP (Hypertext Transfer Protocol) è una delle tecnologie alla base del Web; in particolare, HTTP è un protocollo a livello di applicazione nato per la trasmissione di documenti, come ad esempio i file HTML. Il protocollo HTTP è stato inizialmente pensato per la comunicazione tra web browser e web server, ma può essere usato anche per altri scopi.
HTTP segue uno schema client-server, in cui una applicazione (client) apre una connessione verso il server, effettua una richiesta e attende la risposta del server. Tra le altre caratteristiche di questo protocollo, abbiamo l’essere basato su concetti come risorse e URI (Uniform Resource Identifier) e utilizzare una struttura di messaggi semplice.
HTTP e il Web
Per capire come funziona HTTP, possiamo analizzare il comportamento di un qualsiasi web browser nel momento in cui viene richiesto di caricare una pagina web.
Web Browser come HTTP client
Nel momento in cui diciamo al browser di caricare una certa pagina web, il browser agisce come client HTTP che comunica con un server tramite HTTP. In particolare, supponendo di voler caricare la pagina https://example.com/index.html, il browser aprirà una connessione verso il server e invierà al server un messaggio di richiesta del contenuto del documento HTML index.html che si trova nella root del server web https://example.com. Il messaggio inviato dal browser si chiama richiesta (request).
Ricevuta la richiesta, il server si occuperà di recuperare il contenuto del file richiesto e inviarlo in risposta a chi gli ha fatto richiesta. Anche il server web, quindi, invierà un messaggio che si chiama risposta (response).
Una volta ricevuta il documento HTML il browser comincerà a fare il parsing della pagina per renderizzare e caricare il contenuto e si accorgerà che il documento HTML indica che sono necessari altri file remoti (risorse) per mostrarlo. Per esempio, i file https://example.com/style.css, https://example.com/main.js e https://pictures.example.com/logo.png.
Il browser, quindi, farà partire altri messaggi di richiesta a tutti i vari server, che risponderanno con altrettanti messaggi di risposta.
Lato server web, ognuna di queste richieste è indipendente dalle altre: solo il browser sa che sono collegate tra loro perché servono a caricare una certa pagina. Per questo motivo il protocollo HTTP viene detto stateless (senza stato), poiché non viene mantenuto alcun collegamento tra due richieste fatte sulla stessa connessione.
Quali sono le caratteristiche base del protocollo HTTP
Il protocollo HTTP è stato progettato per essere semplice e la comunicazione HTTP avviene con pochi componenti in gioco, in particolare:
client: è il componente che inizia una richiesta, il web browser nel caso del web, ma potrebbe anche essere una app che comunica con un server API
server: un server non è necessariamente una singola macchina, anche se virtualmente può apparire come tale; riceve le richieste e risponde con il contenuto richiesto, nel fare ciò potrebbero entrare in gioco componenti quali load balancer, cache, database, …
proxy: nel mondo reale, tra il client e il server ci possono essere diversi computer che si “passano” la richiesta e la risposta per farla arrivare alla macchina giusta; basti pensare al fatto che il proprio computer potrebbe non essere l’unico in casa a richiedere la homepage di Google; i proxy intervengono a un livello più basso, ma garantiscono che richiesta e risposta siano correttamente consegnate
Altre caratteristiche importanti del protocollo HTTP che è importante annotare sono:
semplicità: i messaggi HTTP, come vedremo tra poco, sono semplici e facilmente comprensibili anche da occhio umano, rendendo, quindi, agevole effettuare prove a mano e verificare il contenuto delle risposte
estendibilità: è possibile introdurre nuove funzionalità, basta che client e server siano in grado di gestire l’estensione; un esempio importante di estensione del protocollo HTTP, sono gli header, non presenti nella prima versione di questo protocollo, ma aggiunti in seguito, permettono di passare informazioni aggiuntive nei messaggi (i famosi cookie sono scambiati nell’header Cookie)
Flusso HTTP e messaggi
Una comunicazione HTTP dal punto si comporta nel dettaglio nel modo seguente:
il client apre una connessione TCP con il server: in pratica, instaura un canale per inviare e ricevere messaggi (il client può riusare una connessione già aperta o crearne una nuova, a seconda dei casi)
il client invia un messaggio HTTP di richiesta, che contiene anche gli eventuali header, per esempio:
GET /index.php HTTP/1.1
Host: example.com
Accept-Language: it
il server riceve la risposta, verifica se e come elaborarla, produce e invia una risposta
il client legge la risposta del server, per esempio
HTTP/1.1 200 OK
Date: 09 Jan 2023 14:28:02 GMT
Server: Nginx
Last-Modified: 27 Dec 2022 20:18:22 GMT
Accept-Ranges: bytes
Content-Length: 29769
Content-Type: text/html<!DOCTYPE html>
...
il client chiude la connessione o la lascia aperta per futuri utilizzi
Approfondimenti
i server web moderni usano il protocollo HTTPS (HyperText Transfer Protocol over Secure Socket Layer), che è una estensione di HTTP che usa la crittografia per rendere sicura la comunicazione tra client e server
con protocollo a livello di applicazione si fa riferimento ai vari livelli di astrazione con cui possono comunicare computer via rete; in particolare l’application layer è il livello più alto tra i vari presenti
il protocollo HTTP è stateless, ma non è sessionless: la possibilità di creare una sessione tra client e server, resa possibile dai cookie gestiti tramite gli header di messaggio del protocollo HTTP, permette a client e server di sapere, ad esempio, che un certo utente è collegato a un sito di e-commerce per fare acquisti.
1.2
Cosa sono le richieste HTTP
Cosa sono le richieste HTTP
Le richieste HTTP sono messaggi inviati dal client al server per iniziare una determinata azione. Hanno una determinata struttura e sono caratterizzati da un metodo (o verbo) che permette di distinguere il tipo di azione richiesta.
Struttura richieste HTTP
Una richiesta HTTP è composta da:
un metodo che indica l’azione richiesta
una risorsa verso cui si sta compiendo l’azione
zero o più header che caratterizzano la richiesta
un body (opzionale) che contiene i dati da inviare alla risorsa
Un esempio di richiesta HTTP è il seguente:
GET /guide/laravel/index.php HTTP/2
Host: example.com
Accept-Encoding: gzip, deflate, br
Accept-Language: it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
In questa richiesta, nella prima riga è indicato il metodo (GET) e la risorsa (/guide/laravel/index.php), le successive contengono gli header (nella forma Nome-Header: valore; notare, in particolare, l’header che permette di sapere verso quale host indirizzare la richiesta). Non è presente un body (che, vedremo tra poco, non avrebbe molto senso nelle richieste di tipo GET).
Metodi richieste HTTP
I metodi delle richieste HTTP identificano l’azione desiderata da compiere sulla risorsa. Anche se possono essere nomi, di solito sono indicati anche come verbi HTTP, proprio perché rappresentano l’azione da compiere.
Anche se, nell’esempio di comunicazione client-server che abbiamo presentato nella lezione precedente, abbiamo descritto un browser che chiede a un server alcune risorse da scaricare, bisogna considerare che il set completo dei metodi HTTP è pensato per permettere ai client di caricare o modificare risorse sul server.
Qui di seguito i metodi principali e il loro utilizzo desiderato.
GET
Richiede la risorsa indicata, che il server fornisce come body della risposta.
Poiché è usata per richiedere dati al server non dovrebbe includere un body.
HEAD
Ha lo stesso effetto della GET, ma la risposta del server è senza il body.
Può risultare utile da eseguire prima della GET per ottenere dal server informazioni sulla dimensione della risorsa, fornite nella risposta del server con l’header Content-Length.
POST
Invia dati al server usando la risorsa indicata.
Una richiesta POST viene, in genere, inviata tramite un form HTML e comporta una modifica sul server. In questo caso, il tipo di contenuto viene gestito dell’elemento <form> nella pagina. Valori possibili per il tipo di contenuto sono application/x-www-form-urlencoded e multipart/form-data.
Quando la richiesta POST viene inviata tramite un metodo diverso da un form HTML, ad esempio tramite XMLHttpRequest, il body può essere di qualsiasi tipo (per esempio application/json)
PUT
Crea una nuova risorsa o sostituisce la risorsa indicata con il body della richiesta.
A differenza del metodo POST, il metodo PUT è idempotente: chiamarlo una o più volte successivamente ha lo stesso effetto, mentre successive richieste `POST identiche possono avere effetti aggiuntivi (per esempio a ogni POST creo un nuovo ordine su un server di e-commerce).
DELETE
Elimina la risorsa indicata.
PATCH
Applica una modifica parziale alla risorsa.
Come il metodo POST può avere effetti aggiuntivi ogni volta che è chiamata su una risorsa (quindi non è idempotente).
A differenza del metodo PUT, nel body del metodo PATCH possono essere indicate delle “istruzioni” per modificare la risorsa.
Non tutti i server web implementano questo metodo per le varie risorse.
OPTIONS
Chiede le opzioni di comunicazione per la risorsa indicata (per esempio il server può rispondere quali altri metodi sono applicabili alla risorsa).
Riferimenti
Per maggiori approfondimenti su questi e altri metodi HTTP:
https://httpwg.org/specs/rfc9110.html#methods
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
1.3
Cosa sono le risposte HTTP
Cosa sono le risposte HTTP
Le risposte HTTP sono messaggi inviati dal server in risposta a una richiesta inviata dal client. Hanno una struttura specifica e sono caratterizzate dal codice di risposta (o status code) che permette di capire se la richiesta è andata a buon fine oppure no.
Struttura risposta HTTP
Una risposta HTTP è caratterizzata da:
lo status code della risposta
lo status text della risposta, un breve testo, puramente informativo, che offre una descrizione testuale e human-readable dello status code
zero o più header che caratterizzano la risposta
un body (opzionale) che contiene i dati associati alla risorsa richiesta
Un esempio di risposta HTTP è il seguente:
HTTP/2 201 Created
Access-Control-Allow-Origin: *
Date: Mon, 16 Jan 2023 19:40:32 GMT
Content-Type: application/json
Content-Length: 131
Cache-Control: private, no-cache, no-store, must-revalidate
Strict-Transport-Security: max-age=15724800; includeSubDomains
{
"id": "fef10dff-d5ad-4d60-b28a-b3fb6c12edf7",
"foo": "bar"
}
In questa risposta, nella prima riga è indicato lo status code o lo status text, le successive contengono gli header che caratterizzano il contenuto della risposta. Dopo una riga vuota di separazione, è presente la risorsa richiesta (in questo caso un file JSON).
Status code HTTP
I codici delle risposte HTTP permettono al client di capire se una specifica richiesta HTTP è andata a buon fine e in che modo. Se consideriamo, infatti, che un client HTTP è, in fondo, un’applicazione generica (non solo un browser web, ma anche un’app per smartphone che chiama un server API), sapere quali codici di risposta possono essere potenzialmente ricevuti ci permette di istruire la nostra applicazione per comportarsi in modi specifici a seconda del codice di risposta ricevuto.
Gli status code HTTP sono numeri a tre cifre, la cui prima cifra permette di capire a quale classe appartiene lo status code. In particolare:
1xx (informativo) - la richiesta è stata ricevuta, elaborazione in corso
2xx (successo) - la richiesta è stata ricevuta, compresa e accettata con successo
3xx (redirezione) - ulteriori azioni devono essere intraprese per completare la richiesta
4xx (errore del client) - la richiesta contiene una sintassi errata o non può essere soddisfatta
5xx (errore del server) - il server non è riuscito a soddisfare una richiesta apparentemente valida
Ogni numero tra 100 e 599 è, quindi, uno status code valido; alcuni specifici valori sono stati codificati nella specifica HTTP per avere un particolare significato, che riporteremo tra poco.
I server, d’altra parte, possono implementare codici di risposta propri, purché validi e purché in linea con la classe di appartenenza. Per esempio, un server potrebbe rispondere con:
HTTP/2 481 TheCatIsOnTheTable
Access-Control-Allow-Origin: *
Date: Mon, 16 Jan 2023 19:40:32 GMT
Lo status code 481 non è definito nella specifica HTTP, ma è appartiene alla classe “errore del client”. Il client può quindi:
non sapere di questo specifico errore, ma trattandosi di un codice 4xx dovrebbe poterlo gestire come un errore generico lato client
conoscere il significato di questo errore specifico e implementare di conseguenza un comportamento ben definito rispetto agli altri 4xx
Ovviamente, il server dovrebbe documentare quali status code gestisce e quale significato dare a ognuno (anche se una spiegazione minima è nello status text).
I codici di risposta definiti dalla specifica HTTP più frequenti e importanti sono i seguenti:
200 OK
Il codice di risposta 200 OK indica un successo generico della richiesta. L’esatto significato di “successo” dipende dal metodo HTTP della richiesta, così come header e body presenti nella risposta. Per esempio, il body di una 200 OK a seguito di una GET sarà la stessa risorsa richiesta, quello di 200 OK a seguito di una POST potrebbe contenere informazioni di stato o il risultato ottenuto dall’azione corrispondente alla richiesta.
NOTA: il protocollo HTTP è un protocollo di comunicazione, dice come scambiare informazioni, non quali. Nel tempo, si sono formalizzate alcune buone pratiche nell’uso di metodi di richiesta, codici di risposta, contenuto dei body e formato delle URI delle risorse, ma ciò va oltre la specifica HTTP stessa. Per maggiori informazioni cfr https://en.wikipedia.org/wiki/Representational_state_transfer
201 Created
La richiesta è andato a buon fine ed è stata creata una nuova risorsa (il cui contenuto o URI è di solito presente nel body). È la risposta tipica a seguito di una POST o PUT andata a buon fine.
202 Accepted
Il codice di risposta 202 Accepted indica che la richiesta è stata accettata, ma è ancora da elaborare. Notare che, essendo HTTP un protocollo stateless, il server non invierà in modo asincrono una ulteriore risposta per indicare l’effettiva riuscita. Può essere utile, per esempio, su risorse da contattare per far partire un processo batch.
204 No Content
Il codice di risposta 204 No Content indica che non ci sono contenuti da inviare in risposta per la richiesta, ma gli header potrebbero essere utili. Per esempio, il client può aggiornare gli header memorizzati nella cache per questa risorsa con quelle nuove.
301 Moved Permanently
Il codice di risposta 301 Moved Permanently, indica che l’URL della risorsa richiesta è stato modificato in modo permanente. Il nuovo URL viene fornito nella risposta tramite l’header Location:
NOTA: ciò vuol dire che il client dovrà ripetere la richiesta, ma cambiando risorsa con quella fornita dal server.
304 Not Modified
Il codice di risposta 304 Not Modified comunica al client che la risposta non è stata modificata, quindi il client può continuare a utilizzare la stessa versione della risposta memorizzata nella cache.
308 Permanent Redirect
Il codice di risposta 308 Permanent Redirect indica che la risorsa si trova ora in modo permanente in un altro URI, specificato dall’header Location: della risposta. Non è permesso il cambio del metodo da POST a GET.
NOTA: riportiamo questo codice per evidenziare che le specifiche HTTP si sono evolute nel tempo, anche per adattarsi all’uso e alle implementazioni. Sebbene, infatti, possa sembrare uguale a 301 Moved Permanently, la differenza tra le due è nella concessione del cambio di metodo. In passato infatti, alcuni web browser e web server avevano implementato un comportamento custom per il quale a seguito di una POST riuscita veniva fornita una risposta 301 con l’URI verso cui spostarsi per mostrare all’utente un altro contenuto. Da ciò, l’esigenza di un nuovo codice di risposta che fosse più specifico e meno fraintendibile.
401 Unauthorized
Per quanto riguarda il codice di risposta 401 Unauthorized occorre precisare che, sebbene lo standard HTTP specifichi “non autorizzato”, semanticamente questa risposta significa “non autenticato”. Ciò vuol dire che il client deve autenticarsi per ottenere la risposta richiesta.
403 Forbidden
Il codice di risposta 403 Forbidden segnala che il cliente non ha diritti di accesso al contenuto, ovvero non è autorizzato; quindi, il server si rifiuta di fornire la risorsa richiesta. A differenza di 401 Unauthorized, l’identità del client è nota al server.
404 Not Found
Il famosissimo codice di risposta 404 Not Found, che ben conosciamo tutti, ci segnala che il server non riesce a trovare la risorsa richiesta.
L’esatto significato di questo codice dipende anche dall’ambito in cui è stata effettuata la richiesta. Se, infatti, pensiamo a un browser, vuol dire che è stata richiesta una risorsa non presente (esempio: il browser sta chiedendo al server i file delle immagini che ha trovato nell’HTML di una pagina web, ma sul server non è stato caricato un certo file JPEG, oppure è stato commesso un errore nella contenuto dell’HTML).
Se, invece, siamo nell’ambito delle API, potrebbe anche significare che l’endpoint è formalmente valido, ma la risorsa non esiste (GET https://api.ecommerce.com/order/3452 nel caso non esista alcun ordine con id 3452).
410 Gone
Il codice di risposta 410 Gone è simile al precedente (il server non può restituire la risorsa), ma con diversa motivazione.
Questa risposta viene inviata quando il contenuto richiesto è stato definitivamente eliminato dal server. I client dovrebbero rimuovere le proprie cache e i link alla risorsa.
Questo codice d’errore è diventato molto importante nel tempo per strategie SEO, poiché indica ai crawler che quella risorsa può essere rimossa dalla SERP.
500 Internal Server Error
Il codice di risposta 500 Internal Server Error segnala, chiaramente, che il server ha riscontrato una situazione che non sa come gestire.
503 Service Unavailable
Il codice di risposta 503 Service Unavailable indica che il server non è momentaneamente in grado di gestire la richiesta, perché temporaneamente sovraccarico o per una manutenzione pianificata.
Questa risposta dovrebbe essere utilizzata per condizioni temporanee e può indicare tramite l’header Retry-After il tempo stimato prima del ripristino del servizio.
Riferimenti
Per maggiori approfondimenti su questi e altri status code HTTP:
https://httpwg.org/specs/rfc9110.html#status.codes
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
1.4
Come installare Laravel
In questa sezione ti mostreremo come installare Laravel su Windows e Mac. Per procedere all'installazione di Laravel uno dei prerequisiti è l'installazione di PHP all'interno del tuo PC, per farlo segui la nostra guida su come installare php.
Iniziamo!
Installazione Laravel su Windows:
1. Composer: Il primo passo è scaricare Composer, uno strumento essenziale per Laravel. Andate sul sito di Composer (https://getcomposer.org/) e nella sezione Download, scaricate l'eseguibile per Windows.
2. Installazione Composer: Dopo aver scaricato il file, avviate l'installazione e seguite le istruzioni. Cliccate su "NEXT" finché non verificate che il percorso sia quello del vostro computer, quindi cliccate nuovamente su "NEXT". Lasciate i campi vuoti e cliccate su "NEXT" di nuovo. Infine, cliccate su "Install" per completare l'installazione.
3. Verifica di Composer: Aprite il vostro terminale e digitate il comando `composer`. Dovreste vedere una schermata che conferma che Composer è stato installato correttamente.
4. Installazione di Laravel: Ora possiamo installare Laravel. Nel terminale, digitate il seguente comando:
composer global require laravel/installer
5. Configurazione PHP: Andate nella cartella di installazione di PHP e aprite il file `php.ini` con VSCode. Cerca la riga `;extension=fileinfo` e rimuovi il punto e virgola iniziale in modo che diventi `extension=fileinfo`. Salva il file.
6. Riavvio del terminale: Chiudete e riaprite tutti i terminali.
7. Creazione di un nuovo progetto: Ora siete pronti per creare il vostro primo progetto Laravel. Spostatevi nella cartella dove volete creare il progetto e digitate il comando:
laravel new primo_progetto
8. Lasciate che il processo si completi. Quando vedrete un messaggio di conferma, siete ufficialmente entrati nel mondo di Laravel!
Installazione Laravel su Mac:
1. Composer con Brew: Sul vostro Mac, aprite il terminale e nella home directory eseguite il comando:
brew install composer
Questo installerà Composer utilizzando Brew.
2. Installazione di Laravel: Dopo aver installato Composer, eseguite il seguente comando nella home directory del terminale:
composer global require laravel/installer
Questo comando installerà Laravel in modo globale nel vostro computer.
3. Configurazione del PATH: Ora dobbiamo dire al sistema dove trovare il comando "laravel". Prima di tutto, determiniamo se state utilizzando Bash o Zsh. Guardate in alto nel terminale e verificate se c'è scritto "bash" o "zsh".
4. Modifica del file di configurazione: Se state utilizzando Bash, eseguite il comando:
vim .bash_profile
Altrimenti, se state utilizzando Zsh, eseguite:
vim .zshrc
5. Aggiungere il percorso al file di configurazione: Inserite la seguente linea di codice nella parte aperta del file:
export PATH="$HOME/.composer/vendor/bin:$PATH"
Premete il tasto "i" per iniziare a scrivere, incollate la linea, quindi premete il tasto "ESC" e digitate ":wq" per salvare ed uscire dal programma.
6. Riavvio del terminale: Chiudete e riaprite il terminale per applicare le modifiche.
7. Creazione di un nuovo progetto: Ora siete pronti per creare il vostro primo progetto Laravel. Spostatevi nella cartella in cui desiderate creare il progetto e digitate il comando:
laravel new primo_progetto
8. Benvenuti nel mondo di Laravel! Ora potete iniziare a costruire qualcosa di straordinario.
Perfetto adesso che abbiamo installato Laravel possiamo iniziare a programmare ! Nel prossimo capitolo approfondiremo il gestore di pacchetti composer.
1.5
Gestore di dipendenze Composer
Che cos’è Composer
Composer è un gestore di dipendenze per il linguaggio PHP, ovvero un tool che permette di dichiarare da quali librerie dipende il proprio progetto e offre comandi per installare e aggiornare tali dipendenze.
Nel mondo dello sviluppo software, infatti, è normale utilizzare delle librerie esterne (o di terze parti) che implementano le varie funzionalità al contorno utili al proprio progetto. Non vogliamo inventare la ruota ogni volta da capo, vogliamo essere focalizzati sulle esigenze core del nostro progetto. Per questo, ad esempio, possiamo decidere di aggiungere al nostro progetto come dipendenza una determina libreria che ci permette di verificare se un indirizzo email è valido ed esistente, per poi usarla nel flusso di registrazione utente del sito che stiamo sviluppando con Laravel.
Da questo punto vista Laravel stesso è una libreria esterna (una serie di singole librerie, in verità), inclusa tra le dipendenze del nostro progetto tramite Composer.
Per completezza, anticipiamo subito che Composer si occupa non solo di definire e recuperare le varie dipendenze di progetto, ma anche dell’autoloading delle classi PHP, come illustreremo più avanti.
Come installare Composer
Per l’installazione di Composer è richiesto che sul proprio computer sia presente PHP 7.2.5 o più recente.
Le esatte istruzioni per l’installazione differiscono a seconda che si stia usando un sistema operativo UNIX-Like (linux o macOS) oppure Windows.
Istruzioni esatte possono essere consultate presso il sito di Composer:
Setup di un progetto
Un progetto che utilizza Composer è caratterizzato dalla presenza di un file composer.json nella sua directory principale.
Tale file contiene le dipendenze del progetto e contiene altre informazioni aggiuntive.
Un esempio minimale di file di configurazione è il seguente:
{
"require": {
"monolog/monolog": "^3.0.0"
}
}
Tale file ci dice che l’unica dipendenza del progetto (la sezione require) è Monolog (una libreria PHP per il logging).
Ogni dipendenza è composta da:
il nome del pacchetto (package name): monolog/monolog, composto a sua volta dal nome del vendor e il nome della pacchetto, separati da /
un selettore di versione (version contraint): ^3.0.0 è una indicazione di compatibilità desiderata tra le versioni che verranno trovate
Nel momento in cui daremo il comando composer install, la configurazione sarà usata per cercare online nel registro dei pacchetti un pacchetto compatibile con le indicazioni fornite.
$ composer install
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 2 installs, 0 updates, 0 removals
- Locking monolog/monolog (3.2.0)
- Locking psr/log (3.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
- Downloading psr/log (3.0.0)
- Downloading monolog/monolog (3.2.0)
- Installing psr/log (3.0.0): Extracting archive
- Installing monolog/monolog (3.2.0): Extracting archive
10 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
Durante la sua esecuzione Composer ha:
trovato che è necessario un altro pacchetto, psr/log per installare Monolog
scelto l’ultima versione disponibile di Monolog, la 3.2.0, poiché “compatibile” con il selettore indicato (^3.0.0 vuol dire in pratica “ogni versione maggiore di 3.0.0, ma non la 4.0.0 e successive”)
aggiunto il file composer.lock, un file speciale che serve per “bloccare” l’esatta versione delle dipendenze recuperate
creata la directory vendor in cui sono stati estratti i file PHP che compongono le due librerie recuperate
creato nella directory vendor il file autoload.php che possiamo usare nei nostri file PHP per gestire l’autoload delle classi
Sarà, quindi, possibile aggiungere al nostro mini progetto una directory src all’interno della quale creare un file log_me.php con il seguente contenuto:
<?php
// Autoload files using the Composer autoloader.
require_once __DIR__.'/../vendor/autoload.php';
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger;
// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Level::Warning));
// add records to the log
$log->warning('Foo');
$log->error('Bar');
Gestione delle dipendenze
Composer mette a disposizione diversi comandi per gestire l’aggiunta e l’aggiornamento delle dipendenze, in particolare:
composer update - recupera la versione più recente compatibile con il selettore di versione indicato di tutte le dipendenze
composer update vendor1/package1 vendor2/package2 ... - come sopra, ma soltanto per i pacchetti indicati
composer require vendor/package - aggiunge un pacchetto alle dipedenze
composer show vendor/package - mostra informazioni sul pacchetto
composer require vendor/package --dev - aggiunge un pacchetto alle dipendenze di tipo “dev”
Maggiori informazioni su quali comandi sono disponibili e sulle varie opzioni possono essere recuperate eseguendo composer list e composer help.
I vari pacchetti Composer sono distribuiti tramite il servizio Packagist. È possibile cercare pacchetti e consultare i relativi dettagli sia tramite il sito web - https://packagist.org/ - che tramite il comando composer search.
Creazione di progetti da template
Con Composer è anche possibile creare un nuovo progetto basandosi su un template esistente. In pratica, invece di creare un progetto vuoto e andare, man mano, ad aggiungere le singole dipendenze, è possibile istruire composer per creare un nuovo progetto basandosi su un template che contiene già tutte le dipendenze necessarie.
Un template di progetto è realizzato e distribuito a sua volta come un pacchetto: il suo nome è, quindi, nella forma vendor/project e possono esisterne versioni diverse. Nel caso in cui non venga indicata una versione, Composer recupera l’ultimo template disponibile. Nel template ci sono i pacchetti (e la relativa versione) scelti da chi ha realizzato il template più, eventualmente, tutti i file necessari ad avere un progetto funzionante da subito.
Per creare un nuovo progetto usando un template possiamo usare il comando create-project, indicato il pacchetto template da usare e il nome del progetto che vogliamo creare:
composer create-project vendor/project:version nome-progetto
Verrà creata una directory nome-progetto all’interno del quale troveremo il file composer.json, la directory vendor ed eventualmente directory e file creati dagli script di inizializzazione del template.
Come vedremo nella prossima lezione, è possibile creare un nuovo progetto che usa Laravel con questo comando.
Dipendenze di sviluppo
Composer permette di distinguere tra due gruppi di dipendenze, le require e le require-dev:
{
"require": {
"monolog/monolog": "^3.0.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
}
}
Nel primo gruppo vanno inserite quelle dipendenze che servono a far funzionare la nostra applicazione, nel secondo tutte le dipendenze che non servono al codice “di produzione”, ma sono di supporto durante le fasi di sviluppo.
Nell’esempio precedente è stata inclusa la libreria per l’esecuzione di test PhpUnit tra le dipendenze di sviluppo.
Dipendenze di piattaforma
Nell’elenco dei require è anche possibile indicare alcune dipendenze “di sistema”. Composer, infatti, definisce al suo interno alcuni “platform package”, pacchetti virtuali che non possono essere installati da Composer, ma che sono o devono essere installati sul sistema. Tale elenco include:
php stesso, per il quale è possibile indicare la versione richiesta o necessaria al nostro progetto
le estensioni di PHP, nel formato ext-<name>
alcune librerie di sistema, nel formato lib-<name>
Anche se con alcune limitazioni, definire questi platform package permette di scoprire se stiamo installando l’applicativo PHP su una macchina che ha tutto quello che serve alla sua esecuzione.
Autoloading
Una delle funzionalità più importanti di Composer è, però, quella di gestire l’autoloading delle classi PHP.
Proviamo a eseguire i seguenti comandi da terminale e, nel momento in cui viene chiesto come configurare, a premere invio sulle righe dove non è stata data una risposta nel seguente esempio:
$ mkdir composer-demo
$ cd composer-demo
$ composer init --name=acme/composer-demo --type=project
This command will guide you through creating your composer.json config.
Package name (<vendor>/<name>) [acme/composer-demo]:
Description []:
Author [n to skip]: n
Minimum Stability []:
Package Type (e.g. library, project, metapackage, composer-plugin) [project]:
License []:
Define your dependencies.
Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes]? no
Add PSR-4 autoload mapping? Maps namespace "Acme\ComposerDemo" to the entered relative path. [src/, n to skip]:
{
"name": "acme/composer-demo",
"type": "project",
"autoload": {
"psr-4": {
"Acme\\ComposerDemo\\": "src/"
}
}
"require": {}
}
Do you confirm generation [yes]? yes
Generating autoload files
Generated autoload files
PSR-4 autoloading configured. Use "namespace Acme\ComposerDemo;" in src/
Include the Composer autoloader with: require 'vendor/autoload.php';
Creiamo, quindi, i file src/Greetings.php e src/run.php con il seguente contenuto:
// src/Greetings.php
<?php
namespace Acme\ComposerDemo;
class Greetings
{
public static function sayHelloWorld()
{
return 'Hello World';
}
}
// src/run.php
<?php
// Autoload files using the Composer autoloader.
require_once __DIR__ . '/../vendor/autoload.php';
use Acme\ComposerDemo\Greetings;
echo Greetings::sayHelloWorld();
Se da terminale eseguiamo il nostro script otterremo:
$ php src/run.php
Hello World
La configurazione della sezione autoload del file composer.json regola il contenuto del file vendor/autoload.php. In particolare, nella nostra configurazione d’esempio, abbiamo istruito l’autoload per recuperare le classi del namespace Acme\ComposerDemo nella directory src.
Se volessimo cambiare directory o cambiare nome del namespace, dovremmo aggiornare i file PHP e il contenuto del file composer.json e poi eseguire il comando composer dump-autoload per aggiornare il contenuto del file vendor/autoload.php.
Vedremo, più avanti, come progetti utilizzino Laravel sfruttando questa funzione per gestire l’albero delle directory del progetto in modo coerente e prevedibile
1.6
Editor online Laravel
Se sei alle prime armi con questo framework PHP o se stai cercando un modo rapido per sviluppare e testare applicazioni web, utilizzare Laravel su un editor online potrebbe essere utile per una serie di motivi; vediamone alcuni:
Accessibilità da qualsiasi luogo: Gli editor online sono accessibili da qualsiasi dispositivo con una connessione Internet. Questo significa che puoi lavorare su progetti Laravel da qualsiasi luogo, senza dover configurare l'ambiente di sviluppo su ogni computer che utilizzi.
Niente configurazioni complesse: Configurare un ambiente di sviluppo locale per Laravel può richiedere tempo e competenze tecniche. Gli editor online eliminano questa complicazione, consentendoti di iniziare immediatamente a scrivere codice Laravel senza preoccuparti dell'installazione e della configurazione del server web, del database e di altre componenti.
Ambiente di test in tempo reale: Gli editor online ti consentono di vedere i risultati del tuo codice Laravel in tempo reale. Puoi scrivere codice PHP, definire rotte, creare viste e vedere come il tuo sito web reagisce senza dover ricaricare manualmente la pagina o configurare un server web locale.
Apprendimento e condivisione: Se sei nuovo a Laravel, un editor online può essere un ottimo strumento per imparare il framework. Puoi accedere a esempi di codice Laravel, esperimenti e progetti condivisi dalla community, prendendo ispirazione e apprendendo dalle pratiche migliori direttamente nell'editor online.
Collaborazione semplificata: Se stai lavorando con altri sviluppatori su un progetto Laravel, un editor online può semplificare la collaborazione. Puoi condividere facilmente il tuo lavoro con i membri del team o con i clienti, consentendo loro di visualizzare e testare l'applicazione senza dover installare nulla localmente.
Risparmio di risorse: Gli editor online eseguono il codice su server remoti, quindi non sfruttano le risorse del tuo computer. Questo può essere utile se stai lavorando su una macchina con risorse limitate o se vuoi evitare di sovraccaricare il tuo sistema con un ambiente di sviluppo completo.
Versionamento dei progetti: Molti editor online consentono di salvare i tuoi progetti in modo versionato, il che significa che puoi tenere traccia delle modifiche nel tempo. Questo è utile se desideri ripristinare una versione precedente del tuo codice o condividere diverse iterazioni del tuo lavoro.
In sintesi, utilizzare un editor online per sviluppare con Laravel può semplificare il processo di sviluppo, risparmiare tempo ed eliminare la necessità di configurazioni complesse. Tuttavia, è importante notare che per progetti complessi o altamente personalizzati, dovresti comunque configurare un ambiente di sviluppo locale per Laravel.
Ti avevamo già parlato di PHPSandbox - l’editor online che ti suggeriamo per testare Laravel - nella nostra guida PHP in italiano; per capire come utilizzarlo ti basterà seguire la nostra mini-guida PHPSandbox: editor online PHP. Attenzione, però!
Al momento della creazione del progetto dovrai selezionare come template "Laravel 10”
Pronto ad iniziare?
1.7
Laravel, il framework per artigiani del web
Cos'è Laravel?
Laravel è un framework del linguaggio PHP pensato per creare applicazioni web. È costruito basandosi sulle migliori tecnologie e librerie PHP e rende molto semplice l’implementazione di funzioni tipiche di applicativi web, quali autenticazione, routing delle richieste, gestione delle sessioni e del caching.
L’utilizzo di un framework offre diversi vantaggi ad uno sviluppatore web:
offre implementazioni complete e collaudate dei casi d’uso tipici a cui il framework è rivolto, per esempio, nel caso di Laravel la gestione di richieste e risposte HTTP, in modo che il developer possa dedicarsi a “cosa” deve fare il suo applicativo e non a implementare da zero ogni dettaglio
fornisce blocchi modulari per permettere di includere e usare di volta in volta solo quello che serve, per esempio chi dovesse sviluppare un servizio di API con Laravel potrebbe evitare di includere i pacchetti che offrono il templating HTML, necessari solo a chi dovesse creare un server web per erogare una pagina HTML
integra in modo coerente i vari blocchi usando un design comune, in modo che tutti i vari componenti possano, ad esempio, sfruttare lo stesso sistema di configurazione ed evitando di trovarsi nella situazione di dover “cucire” e tenere insieme librerie con stili di programmazione molto diversi
Laravel fornisce una struttura versatile per creare applicazioni web di ogni tipo e lo fa cercando di offrire agli sviluppatori php la migliore esperienza possibile in aree critiche quali la dependency injection, l’astrazione del database, la gestione di code e job schedulati, l’esecuzione di test.
Il team di sviluppo di Laravel definisce il framework nei seguenti modi:
progressivo: è facilmente usabile da sviluppatori php alle prime armi e, al tempo stesso, completo e flessibile per sviluppatori senior
scalabile: le applicazioni Laravel possono scalare orizzontalmente (cioè aggiungendo altre istanze dell’applicazione se serve e non comprando singoli server più potenti) per gestire centinaia di milioni di richieste al mese
comunitario: combina i migliori pacchetti dell’ecosistema PHP per offrire un framework robusto e “developer-friendly”
Come creare il primo progetto Laravel
Per poter creare un progetto Laravel è necessario installare sulla propria macchina PHP e Composer. In aggiunta, è raccomandato installare anche NodeJS e NPM.
È possibile creare un nuovo progetto Laravel tramite il comando create-project di Composer, indicando come template il pacchetto laravel/laravel:
$ composer create-project laravel/laravel example-app
Creating a "laravel/laravel" project at "./example-app"
Installing laravel/laravel (v9.5.0)
- Downloading laravel/laravel (v9.5.0)
- Installing laravel/laravel (v9.5.0): Extracting archive
...
Alla fine dell’installazione, avremo a disposizione una applicazione web di riferimento. Potremmo avviarla sul nostro computer in modalità di sviluppo con:
$ cd example-app
$ php artisan serve
INFO Server running on [http://127.0.0.1:8000].
Press Ctrl+C to stop the server
Per poi vedere le pagine erogate dall’applicazione avviando un browser e aprendo la pagina http://localhost:8000
NOTA: in questa guida avremo come riferimento quanto offerto da Laravel nella versione 9.x
Artisan
Per avviare la nostra applicazione in modalità di sviluppo, abbiamo usato php artisan serve. Artisan è l’interfaccia a riga di comando inclusa con Laravel per impartire determinati comandi nel progetto. Molti di questi comandi sono utili nelle varie fasi del ciclo di sviluppo di una applicativo Laravel, altri possono essere usati anche in fase di setup o aggiornamento dell’applicazione.
A titolo d’esempio, il comando migrate serve a eseguire la migrazione del database ed è, quindi, utile per in fase di deploy dell’applicativo, mentre il comando cache:clear serve per pulire le cache dell’applicativo Laravel e può essere usato sul server in cui l’applicazione è in esecuzione.
Particolarmente utile è il comando make che ci permette di aggiungere al progetto nuove classi di determinate tipologie.
Ci sono molti comandi per diverse necessità e l’elenco esatto dipende anche da quali pacchetti e librerie sono state incluse nel proprio progetto. Per vedere la lista completa dei comandi disponibili nel proprio progetto è possibile utilizzare il comando list:
php artisan list
Ogni comando include abitualmente anche un “help” che descrive la finalità del comando stesso e le opzioni e argomenti disponibili. Per mostrare tale aiuto, è sufficiente far precedere il comando interessato con help. Ad esempio, per avere maggiori informazioni sul comando Artisan che esegue le migrazioni del database è possibile:
php artisan help migrate
È anche possibile scrivere dei propri comandi, che verranno inclusi nell’elenco di quelli disponibili nel progetto, per eseguire particolari task
1.8
L’ecosistema Laravel
Quando definiamo Laravel un framework intendiamo dire che Laravel offre agli sviluppatori php una struttura di supporto e delle funzionalità generiche che possono essere utilizzate per realizzare l’effettivo contenuto della singola applicazione. Si tratta, quindi, di codice scritto da terzi che permette di risolvere un problema comune, in modo simile a una libreria software.
La differenza tra libreria e framework è, però, legata a come queste intervengono nel flusso di esecuzione dell’applicazione. Quando si usa una libreria, è lo sviluppatore a scegliere quando chiamare una determinata funzione della libreria, mantenendo, quindi, pieno controllo sul flusso. Quando si usa un framework, invece, è il framework a determinare come e dove determinate funzioni possono essere implementate affinché possano essere effettivamente chiamate ed eseguite, realizzando quella che definisce “inversione del controllo”.
Inversione del controllo
Ovviamente un framework può fare uso di librerie (rendendolo visibile o nascondendolo allo sviluppatore web) per implementare specifici comportamenti.
Laravel si appoggia ad alcuni consolidati pacchetti del mondo del linguaggio PHP per offrire le funzionalità di base necessarie a una applicazione web. Questo insieme di pacchetti e strutture definite da Laravel prendono il nome di Laravel Framework e possiamo dire che corrispondono indicativamente al pacchetto Composer di nome, per l’appunto, laravel/framework.
Il team di Laravel offre alcuni pacchetti Composer aggiuntivi che costituiscono il cosiddetto first-party ecosystem, ovvero l’ecosistema base di Laravel sviluppato e mantenuto direttamente dal team di Laravel e pensato per rispondere ad alcune specifiche esigenze di chi sviluppa con Laravel.
Questi pacchetti possono essere pensati come estensioni del framework base, poiché offrono spesso soluzioni a determinati problemi specifici mantenendo l’approccio di “inversione del controllo” tipica del framework.
Pacchetti specifici di Laravel
Nell’elenco che segue abbiamo riportato i pacchetti sviluppati e sponsorizzati direttamente dal team di Laravel che offrono soluzioni a problemi specifici. Si tratta di necessità che non sono comuni in ogni applicazione web, ma che, qualora fosse necessario supportare, sarebbe ottimale avere come estensione del framework e non come libreria.
In rigoroso ordine alfabetico, i pacchetti “extra” al momento mantenuti direttamente dal team di Laravel sono:
Cashier - interfaccia per i servizi di pagamento in abbonamento Stripe
Dusk - esecuzione di test browser automatizzati
Echo - sviluppo di applicazioni con broadcasting degli eventi tramite WebSocket
Horizon - dashboard per monitorare job e queue Laravel che usano Redis
Nova - progettazioni di interfacce e dashboard di admin (a pagamento)
Octane - sfrutta gli application server Open Swoole, Swoole, and RoadRunner per aumentare le performance
Pint - linter/fixed del codice PHP in stile Laravel
Sail - gestisce un ambiente di sviluppo basato su Docker
Sanctum - sistema di autenticazione per applicazioni SPA e mobile basato su token
Scout - aggiunge “full-text search” al proprio database
Socialite - autenticazione social con Facebook, Twitter, GitHub, LinkedIn e altro
Spark - permette di offrire piani a sottoscrizione per il proprio servizio (a pagamento)
Telescope - debug di tutto ciò che accade nell’applicazione nel proprio ambiente di sviluppo
Starter Kit di Laravel
Con il termine starter kit il team di Laravel indica al momento due specifici pacchetti Composer che estendono le funzionalità base di una applicazione Laravel che necessiti di gestire l’autenticazione degli utenti.
I due starter kit disponibili al momento si chiamano Breeze e JetStream. Entrambi semplificano e automatizzano la realizzazione di rotte, controller e view (vedremo più in là cosa si intende con questi termini) necessarie a far registrare e autenticare utenti.
Entrambi vanno installati tramite Composer (composer require laravel/breeze --dev oppure composer require laravel/jetstream) a seguito della quale sarà compito dello sviluppatore indicare o scegliere la specifica modalità in cui verrà utilizzato lo starter kit.
Nel creare applicazioni web in cui è coinvolta l’autenticazione degli utenti, infatti, sarà di particolare rilevanza scegliere come la parte frontend dell’applicazione, ovvero quella che è in esecuzione nel browser degli utenti, dialogherà con la parte backend dell’applicazione stessa, ovvero quella in esecuzione con PHP e Laravel sul server. In base alla modalità e alla scelta effettuata per implementare il front end, i due starter kit andranno configurati e usati in modi diversi.
NOTA: sebbene non sia considerato uno starter kit, poiché non offre supporto alle funzioni relative all’autenticazione, vale la pena ricordare l’esistenza del pacchetto Composer laravel/ui.
Deployment
Un brevissimo accenno finale a quelli che sono servizi (a pagamento) offerti dal team di Laravel, tutti incentrati nella gestione dei deployment di applicazioni Laravel in produzione, ma con differenze tra modalità e ambienti di deploy supportati:
Envoyer: deployment e monitoraggio di applicazioni Laravel
Forge: server management e deploy management
Vapor: piattaforma di deployment serverless, basato su AWS Lambda
1.9
Struttura di un progetto Laravel
Quando si crea un nuovo progetto con Laravel viene creata una directory al cui interno sono già disponibili tutti i file necessari per sfruttare fin da subito le potenzialità offerte da Laravel. Si tratta di un punto di partenza valido per applicativi di ogni dimensione ed esigenza, in cui ogni directory assolve a uno specifico compito legato al ciclo di vita dello sviluppo e dell’esecuzione dell’applicazione.
Trattandosi di un progetto Composer, ovviamente troveremo al suo interno il file composer.json nel quale sono elencati i pacchetti base di un applicativo Laravel.
Accanto ad esso, troviamo una serie di directory, ognuna delle quali assolverà al suo scopo.
Contenuto nuovo progetto Laravel
Contenuto della directory radice
Directory App in Laravel
La directory app contiene il codice base dell’applicazione. È la directory nella quale lavoreremo di più, poiché ospita tutte le classi PHP dell’applicazione. Vedremo poco oltre come queste classi sono organizzate al suo interno.
Directory Bootstrap in Laravel
La directory bootstrap contiene il file app.php che serve a fare il "bootstrap” (avvio) del framework quanto si fa partire l’applicazione. Al suo interno si trova la directory cache che Laravel usa per ottimizzare le performance. Salvo particolari esigenze, non è necessario modificare i file contenuti in bootstrap.
Directory Config in Laravel
La directory config contiene i file di configurazione dell’applicazione. Ogni file si occupa di un determinato gruppo di configurazioni (per esempio config/database.php conterrà le opzioni di configurazione del database, config/mail.php quelle del sistema di invio email). Notare che le varie opzioni sono indicate nella forma
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
che indica, ad esempio, che il valore dell’opzione username verrà ricavato dalla variabile ambientale DB_USERNAME; se tale variabile non è impostata, verrà usato come default forge. Questo meccanismo permette di usare le variabili ambientali per configurare in modo diverso l’applicazione a seconda dell’environment (sviluppo locale, staging, produzione, …)
Directory Database in Laravel
La directory database contiene migrazioni, factory model e seed per il database. Durante lo sviluppo sul proprio computer, è possibile usare questa directory per mantenere un database locale SQLite.
Directory Lang in Laravel
La directory lang ospita le traduzioni dell’applicazione nelle varie lingue supportate.
Directory Public in Laravel
La directory public contiene eventuali risorse “statiche” erogate dalla nostra applicazione web (asset quali immagini, JavaScript, CSS nella loro versione ottimizzata per essere inviata ai client) e soprattutto il file index.php.
Il file index.php è il punto d’ingresso di ogni richiesta HTTP che arriva al nostro applicativo web, che viene catturata e “instradata” alla classe che si occuperà di gestirla. Il file index.php carica anche l’autoloading.
Directory Resources in Laravel
La directory resources contiene le view (template di pagine HTML) e i file sorgente degli asset (che verranno ottimizzati da opportuni task e copiati in public).
Directory Routes in Laravel
La directory routes contiene le cosiddette rotte dell’applicazione. Le applicazioni Laravel includono diversi tipi di rotta, ognuno gestito da uno dei file inclusi in questa directory. Non tutti i gruppi sono necessari in tutte le applicazioni.
routes/web.php: contiene le rotte del gruppo web, per le quali Laravel mette a disposizione sessione, protezione CSFR e cifratura dei cookie. Sono, in pratica, le rotte per gestire le chiamate e risorse come pagine web con form e simili.
routes/api.php: contiene le rotte del gruppo api, ovvero le rotte per quelle risorse pensate per essere stateless. Sono in pratica le rotte per gestire delle API RESTful
routes/console.php: contiene delle rotte particolari che servono a invocare dei comandi
routes/channels.php: serve a registrare i canali di event broadcasting dell’applicazione
Directory Storage in Laravel
La directory storage contiene diverse tipologie di file generati automaticamente dal framework Laravel (log, cache, …). Al suo interno tali file sono “segregati” nelle sottodirectory app, framework e logs.
La directory storage/app/public, in particolare, può essere usata per salvare file creati dagli utenti dell’applicativo (per esempio foto di profilo caricate) che dovranno, poi, essere pubblicamente accessibili.
Directory Test in Laravel
La directory test contiene i test automatizzati. Laravel utilizza PHPUnit per gestire test unitari e feature.
Directory Vendor in Laravel
La directory vendor, come in ogni progetto Composer, contiene le dipendenze e il file autoload.php
Contenuto della directory App in Laravel
Nella directory app è contenuta la gran parte del codice e delle funzionalità specifiche di un progetto Laravel. In un progetto Laravel standard la directory app ospita le classi PHP del namespace App e ne viene fatto l’autoload tramite Composer.
All’interno della directory app ci sono diverse altre sottodirectory, che raggruppano le varie classi PHP necessarie all’applicazione in base alla tipologia di classe gestita da Laravel. Vedremo, più avanti in questa guida, il significato e l’utilizzo specifico di Model, Controller e altro. Per ora è sufficiente pensare che se dobbiamo aggiungere alla nostra applicazione Laravel una nuova pagina HTML e che quella pagina corrisponde a una certa URI, quasi sicuramente dovremo aggiungere una nuova classe della directory app/Http/Controllers che indicherà come comportarsi all’arrivo di una richiesta per la nuova risorsa.
Contenuto directory App
Vediamo alcune delle sottodirectory principali di app.
Directory App/Console in Laravel
La directory Console contiene comandi Artisan personalizzati della propria applicazione. Un esempio tipico è un comando che permette di aggiungere un nuovo utente alla nostra applicazione.
Directory App/Exceptions in Laravel
La directory Exceptions contiene gli handler di eccezioni dell’applicazione ed è anche la posizione consigliata per definire le eccezioni specifiche emesse dalla propria applicazione.
Directory App/Http in Laravel
La directory Http contiene tutte le classi e la logica necessaria a gestire le richieste (HTTP) che arrivano all’applicazione. Controller, middleware e form sono definiti in questa directory.
Directory App/Models in Laravel
La directory Model contiene le classi dei modelli Eloquent, la libreria ORM fornita di default con Laravel. Eloquent permette di interagire con i database in modo semplice: per ogni tabella presente sul database esiste un corrispettivo “modello” (Model) che viene usato per interagire con la tabella. Le classi model aggiunte in questa directory permettono di effettuare query, inserire, modificare e rimuovere entry nel database.
Comandi Artisan make in Laravel
Sebbene sia possibile aggiungere manualmente nuove classi PHP all’interno della directory app, può risultare più comodo affidarsi al comando Artisan make quando è necessario creare una nuova classe di un certo tipo.
La distinzione in varie directory nella directory app è anche legata alla classe ereditata dai singoli file. Supponiamo, ad esempio, di voler creare un nuovo Model per salvare sul database il contenuto di un form di contatto. Possiamo usare Artisan per creare questo nuovo modello.
php artisan make:model FormContact
viene creato un nuovo file app/Models/FormContact.php con il seguente contenuto
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class FormContact extends Model
{
use HasFactory;
}
La classe per il nuovo modello, seppure vuoto, è già impostata per ereditare la classe Model di eloquent. In modo simile, altre classi create con artisan make possono avere già implementazioni di riferimento di funzioni necessarie alla classe che ereditano.
La lista di entità che è possibile creare sul proprio progetto con Artisan è disponibile eseguendo php artisan list make.
Altri file di progetto in Laravel
Oltre ai file necessari per gestire classi PHP e dipendenze Composer, nel template di un progetto Laravel sono anche disponibili da subito:
file .gitignore configurato specificamente per progetti Laravel
file package.json e vite.conf.js per gestire eventuali asset JavaScript e CSS
file phpunit.xml per configurare l’esecuzione di PHPUnit
file .evn e .env.example per definire le variabili ambientali usati dalla configurazione del progetto
2
Routing, controller e view in Laravel
2.1
Laravel e il ciclo di vita delle richieste
Il routing è uno degli aspetti essenziali di una applicazione web ed è, ovviamente, alla base di un progetto Laravel. Con routing si intende il meccanismo per il quale le richieste HTTP vengono instradate (route) alla porzione di codice che le gestirà ed è alla base del ciclo di vita di una richiesta HTTP gestita da un applicativo Laravel.
In Laravel il routing viene configurato e gestito registrando delle rotte nei file routes/web.php e routes/api.php, che rispettivamente gestiscono le richieste relative alla interfaccia web dell’applicazione e le richieste di tipo API RESTful.
Conoscere i meccanismi di funzionamento del routing in Laravel è importante per comprendere meglio la finalità, il comportamento previsto e le interazioni tra i vari componenti che costituiscono una web app Laraver, riuscendo, quindi, a superare l’iniziale senso di “succede come per magia”.
Vediamo, quindi, nel dettaglio il ciclo di vita di una richiesta HTTP in una applicazione Laravel, ovvero cosa accade “sul server” quando arriva una richiesta HTTP di un client.
Server web e applicativo web Laravel
Nel mondo del web è importante distinguere tra server web e applicativo web.
Il server web è un applicativo a sé stante che nella pratica si occupa di ricevere le richieste HTTP dei client e decidere quale file è associato alla risorsa richiesta.
Facciamo un breve esempio: abbiamo una directory di nome /opt/website/ con dentro i file index.html e style.css. Se avviamo il server web configurato nel modo seguente:
root: /opt/website/
req: / -> index.html
req: /* -> *
Stiamo in pratica dicendo che:
se il client manda una richiesta HTTP per la risorsa /, allora il server restituisce il file /opt/website/index.html
per ogni altra richiesta il server restituisce il file richiesto se presente in /opt/website/website/
Se il file richiesto non esiste, il web server fornirà una risposta HTTP con status code 404 Not Found.
Un server web opera a livello richieste e risposte HTTP, limitandosi a smistare le richieste verso i file inclusi in una certa directory.
Un sito web statico, fatto cioè da sole pagine HTML che non cambiano in base alla richiesta effettuata o che non richiedono la presenza di form per l’invio dei dati, può essere reso disponibile sul web caricando i file su un macchina in cui gira solo un server web, gestendo in modo opportuno link e directory su disco.
Un’ applicazione Laravel necessita di un server web per ricevere richieste e restituire risposte HTTP, ma il server web viene praticamente configurato nel seguente modo:
root: /opt/app-laravel/public/
req: /* -> run(php index.php)
Ovvero: a ogni richiesta HTTP ricevuta esegui lo script public/index.php del progetto Laravel.
Il file index.php in Laravel
Il file public/index.php di un progetto Laravel è, quindi, lo script PHP che viene invocato ogni volta che arriva una richiesta al nostro server.
In questo file non è presente molto codice, ma solo quanto serve per caricare il resto del framework Laravel. Al suo interno infatti, a parte il necessario require dell’autoload, troviamo solo
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Kernel::class);
$response = $kernel->handle($request = Request::capture())->send();
$kernel->terminate($request, $response);
che possiamo interpretare nel seguente modo:
recupera una istanza dell’applicazione Laravel $app
carica il $kernel opportuno (in base al tipo di richiesta arrivata sarà un kernel di tipo HTTP o console)
fai gestire la $request arrivata al kernel e invia una $response
Questo mini ciclo è ripetuto ad ogni richiesta che arriva al server, passando la richiesta da uno strato all’altro fino a giungere all’effettivo codice che gestirà e restituirà il contenuto della risposta.
La selezione del middleware
Le richieste di tipo HTTP proseguono il viaggio seguendo una via apparentemente tortuosa, legata nuovamente al tipo di richiesta arrivata.
Le versioni più recenti di Laravel (in questa guida stiamo usando la versione 9.x attualmente disponibile) offrono, infatti, la possibilità di distinguere due diversi “gruppi” di rotte, definiti middleware.
Quando viene inizializzata l’applicazione Laravel, viene caricato un servizio interno per la gestione delle rotte. Tale servizio corrisponde al contenuto del file app/Providers/RouteServiceProvider.php, ovvero la classe App\Providers\RouteServiceProvider. La versione di default di questo servizio prevede quanto segue:
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php')) Route::middleware('web')
->group(base_path('routes/web.php'));
});
ovvero, tutte le richieste di risorse il cui path inizia per api appartengono al middleware api e verranno smistate al file routes/api.php per un successivo ulteriore smistamento. Ogni altra richiesta è smistata verso il middleware web e verrà ulteriormente smistata dal file routes/web.php.
Richiesta
Middleware
GET /api/orders/
api
POST /page/form
web
GET /docs/api/
web
GET /blog/2022/12/23/post.html
web
DELETE /api/orders/23455
api
DELETE /order/23455
web
NOTA: quanto descritto finora è la configurazione presente in un’ applicazione Laravel appena creata dal template di default. È possibile cambiare queste configurazioni in base alle proprie esigenze, per esempio rimuovere completamente il middleware api nel caso in cui non siano necessarie delle API RESTful, ma si voglia usare Laravel per realizzare solo in sito web.
La selezione della rotta registrata
Una volta individuato il middleware, la nostra richiesta HTTP prosegue il suo viaggio nella sua destinazione finale.
La responsabilità è ora data ai file routes/web.php e routes/api.php che, sebbene in modi lievemente diversi data la diversa natura del tipo di richiesta che stanno gestendo, contengono al loro interno le effettive rotte registrate.
// routes/web.php
Route::get('/', function () {
return view('welcome');
});
Route::get('/hello', function () {
return 'Hello Laravel';
});
// routes/api.php
Route::middleware('auth:sanctum')->get('/orders', function (Request $request) {
return $request->orders();
});
Se esiste, quindi, una rotta registrata per il path della richiesta che si sta gestendo, allora sarà eseguita la corrispondente funzione e verrà rimandata indietro quanto restituito dalla funzione stessa.
Ad esempio, limitandoci alle sole richieste nel middleware web:
nel caso in cui arrivasse una richiesta GET /hello, allora verrà rimandato indietro il testo “Hello Laravel” che diventerà il body della risposta HTTP con status 200 OK
per la richiesta GET / verrà invocata una funzione dedicata che fa il rendering di una view, ovvero cerca il file template associato, lo elabora e restituisce il risultato come testo (molto probabilmente HTML) che, anche in questo, diventerà il body di una risposta HTTP con status 200 OK
ogni altra richiesta, sia per metodo che per risorsa richiesta, non avendo una corrispettiva rotta registrata, non potrà essere soddisfatta e Laravel si occuperà di preparare e restituire una risposta con status 404 Not Found
Il file routes/web.php è, quindi, in pratica, il file da consultare per conoscere quali sono le rotte attualmente registrate e il punto di partenza quando si deve andare a definire una nuova rotta.
NOTA: Oltre a quanto descritto finora, Laravel permette anche un’altra modalità per definire una rotta, tramite controller; Per ora basti pensarlo come un modo per raggruppare in una singola classe rotte che fanno parte dello stesso aggregato.
Artisan e le rotte in Laravel
Per ottenere un elenco completo delle rotte registrate dall’applicazione Laravel, è anche possibile usare l’opportuno comando Artisan:
php artisan route:list
L’opzione -v mostra anche i middleware associati, mentre l’opzione --path permette di mostrare solo le rotte che contengono una certa stringa nell’URI:
$ php artisan route:list --path=user -v
GET|HEAD api/user
⇂ api
⇂ App\Http\Middleware\Authenticate:sanctum
GET|HEAD user
⇂ web
Se avete aggiunto una rotta e non compare eseguendo questo comando, probabilmente non è stata aggiunta nel modo corretto. Laravel non sarà, quindi, in grado di gestire richieste per quella rotta.
Conclusioni
il routing è il modo con cui una richiesta HTTP di una certa URI viene fatta arrivare a un file o a una porzione di codice che si occupa di creare il contenuto della risposta
in Laravel è, praticamente, sufficiente registrare una rotta per gestire le richieste
una rotta registrata è individuata dal metodo HTTP e dal path della risorsa
una rotta registrata indica anche cosa fare per costruire la risposta da mandare al cliente
tutte le rotte registrate sono presenti nel file routes/web.php
Laravel nasconde allo sviluppatore web gran parte della gestione di basso livello dei meccanismi di routing, ma è sempre possibile customizzare la propria app andando a modificare il contenuto di specifici file e classi
2.2
Route in Laravel
In Laravel una Route permette di configurare la nostra applicazione per rispondere quando viene effettuata una richiesta a una determinata URI. In questo modo, la nostra applicazione può rispondere a richieste di risorse che non devono essere necessariamente file specifici presente sul server. Ciò consente una grande flessibilità sotto molti punti di vista, non solo dal punto di vista della semplicità dello sviluppo, ma anche, ad esempio, per ottimizzazioni di tipo SEO.
La route più semplice che è possibile definire in Laravel è:
use Illuminate\Support\Facades\Route;
Route::get('/hello', function () {
return 'Hello Laravel'
});
Tale definizione è composta da:
il metodo statico della classe Route (genericamente legato al metodo HTTP della richiesta)
la stringa che rappresenta il path o parte del path della URI (/hello) della richiesta
una callback, ovvero closure (function () {...}) che definisce la risposta effettiva che verrà restituita
Come visto nella lezione precedente, queste definizioni di route vanno aggiunte al file routes/web.php per le richieste legate a pagine web. È importante ricordare, però, che l’ordine con cui le varie route vengono definite in questo file sarà determinare per il meccanismo di match: la prima route definita che può gestire la richiesta arrivata sarà quella che, effettivamente, restituirà la risposta, anche se dovesse esserne definita una più specifica più avanti nel file.
Infatti, per ogni richiesta ricevuta Laravel scansiona le rotte registrate fino a trovarne una che dichiara di poter gestire il metodo e il path della richiesta.
Route e metodi HTTP in Laravel
Le route più immediate che è possibile registrare in una applicazione Laravel indicano il metodo HTTP tramite il metodo statico di Route:
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
Sono disponibili altri metodi statici di Route di “raggruppare” più metodi HTTP, nel caso in cui si desideri rispondere nello stesso modo.
Route::match(['get', 'post'], '/', function () {
// risposta comune per richieste GET e POST
});
Route::any('/', function () {
// risposta comune per **ogni** richiesta
});
Nel caso sia necessario usare queste definizioni “con match”, è fondamentale ricordare che diventa importante l’ordine con cui sono state dichiarate le rotte per la stessa URI. Le rotte che fanno uso dei metodi get, post, put, patch, delete e options vanno definite prima di quelle con any e match, per essere sicuri che la richiesta sia gestita dalla route corretta.
Route di redirect in Laravel
Nel caso in cui si voglia effettuare una redirect per una determinata URI, è possibile definire una route con il metodo speciale redirect:
Route::redirect('/here', '/there');
La redirect così definita restituirà al client una risposta con codice HTTP 302. È possibile restituire un altro codice indicandolo come terzo parametro del metodo redirect:
Route::redirect('/here', '/there', 310);
È anche disponibile un metodo dedicato per le redirezione permanenti che restituisce il codice 301
Route::permanentRedirect('/here', '/there');
Route di fallback in Laravel
Il metodo Route::fallback permette di definire il comportamento dell’applicazione Laravel nel caso in cui non sia stata trovata alcuna rotta registrata per gestire una richiesta. Tipicamente, non è necessario definire una route di fallback, poiché le applicazioni Laravel sono inizialmente già configurate per restituire una pagina “404” nel caso in cui non si sia trovata una route per gestire la richiesta.
Route::fallback(function () {
// no other route matched :(
});
IMPORTANTE: nel caso in cui si registri una route di fallback, dovrà essere l’ultima riportata nel file route/web.php.
Rotte con “prefissi” in Laravel
Nel caso in cui dovessimo gestire delle URI con path come, per esempio, /user/profile e /user/orders, è possibile usare il metodo prefix per raggruppare insieme nel seguente modo:
Route::prefix('/user')->group(function () {
Route::get('/profile', function () {
// risposta per l'URI "/user/profile"
});
Route::get('/orders', function () {
// risposta per l'URI "/user/orders" URL
});
});
In questo modo, le route sono raggruppabili e bene organizzate in base all’effettivo path, semplificando la lettura del file route/web.php.
Ovviamente, lo stesso comportamento si sarebbe ottenuto senza usare prefix, ma indicando il path completo nelle due registrazioni:
Route::get('/user/profile', function () {
// risposta per l'URI "/user/profile"
});
Route::get('/user/orders', function () {
// risposta per l'URI "/user/orders" URL
});
Dependency injection in Laravel
Nel caso fosse necessario utilizzare una dipendenza esterna nella funzione di callback di una route, è possibile sfruttare la dependency injection messa a disposizione da Laravel.
Consideriamo il seguente esempio: vogliamo fare in modo che la risposta alla rotta /hello contenga il nome passato opzionalmente come query string alla URI. Cioè vogliamo che:
se il client richiede “GET /hello” la risposta sarà “Hello Laravel”
se il client richiede “GET /hello?name=Foo” la risposta sarà “Hello Foo”
Possiamo implementare questo comportamento nel seguente modo:
use Illuminate\Http\Request;
Route::get('/hello', function (Request $request) {
$name = $request->query('name');
if ($name == '') {
$name = 'Laravel';
}
return 'Hello $name';
});
In questo modo, la funzione associata alla route dichiara di avere una dipendenza aggiuntiva specificando un parametro con il type-hinting. Nel momento in cui la funzione verrà effettivamente eseguita, Laravel si occuperà anche di iniettare la richiesta arrivata come oggetto di tipo Illuminate\Http\Request.
Sarà, quindi, possibile usare tale oggetto all’interno del codice del metodo callback, per esempio per estrarre dettagli della richiesta necessari ad elaborare la risposta desiderata.
NOTA: le URI sono così strutturate: <scheme>://<host><path>[?<query>]. Quindi, la URI https://www.example.com/path/to/foo?param=value&option=none è composta dallo schema https, l’host www.example.com, il path /path/to/foo e la query param=value&option=none. Una URI ha sempre almeno un path (“/”), mentre la query è opzionale.
Cache delle route in Laravel
Durante lo sviluppo di un’applicazione Laravel, può capitare di registrare una nuova route mentre si sta eseguendo il server web locale con php artisan serve. In questa situazione, la rotta è subito disponibile, senza necessità di arrestare e riavviare il server web locale.
Nel momento in cui si effettua il deploy dell’applicazione in ambiente di produzione, è possibile avvantaggiarsi del meccanismo di cache di Laravel, che opera anche sulle rotte. In questo modo, specie nel caso di applicazioni con molte rotte registrate, Laravel prepara e usa una “build” ottimizzata del file route/web.php in modo da ridurre i tempi di caricamento e risoluzione. Tale cache può essere generata con:
php artisan route:cache
Una volta creata la cache, però, Laravel non userà più il contenuto del file route/web.php e, nel caso si aggiungano nuove rotte, sarà necessario aggiornare la cache eseguendo nuovamente il comando. Per rimuovere la cache è possibile usare:
php artisan route:clear
2.3
Route parametriche in Laravel
In questa lezione vedremo come Laravel permette di gestire con una singola rotta URI in cui sono presenti elementi variabili. Ciò consente di semplificare le proprie rotte ed, ovviamente, poter creare una struttura di URL coerente anche per quelle situazioni in cui la pagina dipende dalla effettiva presenza di un contenuto su un database.
Come esempio, basti pensare alla pagina per accedere al contenuto di uno specifico post su un blog, o uno specifico ordine su un sito di e-commerce. Sebbene sia possibile recuperare tali contenuti indicando l’identificativo del post o dell’ordine tramite query string, ci sono situazioni in cui è desiderabile avere tale identificativo direttamente nel path della URI.
La possibilità di definire rotte con porzioni dinamiche del path della URI ci permette di:
creare URI più semplici rispetto alle analoghe con query string, che sono anche in linea con le buone pratiche SEO (per esempio https://www.example.com/blog/45887 invece di https://www.example.com/blog/post?id=45887)
essere coerenti con le buone pratiche delle API RESTful: GET https://www.example.com/api/order/03bd7e9c
gestire porzioni “intermedie” del path, per esempio https://www.example.com/blog/topic/lifestyle/posts/ e https://www.example.com/blog/topic/comedy/posts/
Laravel permette di creare pagine web e risorse in grado di gestire queste situazioni in modo semplice e immediato tramite specifiche definizioni di rotte.
Rotte con parametri in Laravel
Nel definire una rotta è possibile istruire Laravel per fare in modo che una porzione della URI sia considerata variabile e venga catturata e messa a disposizione della funzione di callback. Vediamo un esempio:
Route::get('/blog/{id}', function ($id) {
return 'This is the post '.$id;
});
Rispetto alla definizione di una rotta statica (cioè una rotta che accetta solo un possibile path), possiamo notare che:
nella stringa che rappresenta il path, la parte variabile è compresa tra due parentesi graffe ({id})
nella funzione di callback è presente un parametro ($id)
È anche importante sapere che:
per le parti variabili del path, comprese tra {}, è possibile usare solo lettere e _
è possibile indicare più parti variabili, che verranno poi associate ai parametri della callback in base all’ordine (non è, quindi, necessario usare gli stessi nomi)
Route::get('/posts/{post_id}/comments/{comment_id}', function ($post, $commentId) {
return 'This is the comment '.$commentId.' for post '.$post;
});
Rotte con parametri opzionali in Laravel
Nel caso in cui sia necessario registrare un parametro di rotta opzionale, è sufficiente aggiungere un ? dopo il nome del parametro e indicare il valore di default di tale parametro
Route::get('/blog/{id?}', function ($id = 100) {
return 'This is the post '.$id;
});
Validazione dei parametri in Laravel
Nel caso sia necessario validare il valore ricevuto per il parametro, Laravel offre un modo conveniente per definire tale validazione. È possibile, infatti, concatenare il metodo where alla definizione della rotta indicando, come nome della where, il nome del parametro e una espressione regolare di validazione. Ogni parte variabile della url può avere la sua validazione.
Nel caso in cui la richiesta non soddisfi la validazione, Laravel risponderà con un risposta 404 Not Found.
// by_type può essere costituito solo da lettere minuscole
Route::get('/blog/topic/{by_type}/posts/', function ($type) {
//
})->where('by_type', '[a-z]+');
// id può essere costituito solo da numeri
Route::get('/blog/{id}', function ($id) {
return 'This is the post '.$id;
})->where('id', '[0-9]+');
Nel caso di parametri multipli, è possibile indicare una validazione per ciascuno di essi:
Route::get('/posts/{post_id}/comments/{comment_id}', function ($postId, $commentId) {
//
})->where(['post_id' => '[0-9]+', 'comment_id' => '[a-z0-9]+']);
Oltre a fornire una espressione regolare per il controllo di validità, sono disponibili dei metodi helper per aggiungere rapidamente validazioni dei casi più comuni:
// by_type può essere costituito solo da lettere minuscole
Route::get('/blog/topic/{by_type}/posts/', function ($type) {
//
})->whereAlpha('by_type');
// id può essere costituito solo da numeri
Route::get('/blog/{id}', function ($id) {
return 'This is the post '.$id;
})->whereNumber('id');
In caso di più parametri è possibile concatenare diversi helper, uno per parametro.
Parametri e dependency injection in Laravel
Per utilizzare una dipendenza esterna nella funzione di callback di una route con parametri, è necessario indicare i parametri con dependency injection prima dei parametri di rotta.
use Illuminate\Http\Request;
Route::get('/posts/{id}/comments', function (Request $request, $id) { $order = $request->query('order_by');
// recupera i commenti in base al criterio
// $comments = Comments:findBy($id)->orderBy($order);
// ...
});
2.4
Controller in Laravel
Laravel è pensato per rendere semplice e immediata la separazione dei compiti (separation of concerns) tra diverse classi. Per questo motivo è possibile separare la definizione di una rotta (intesa come definizione della URI e del metodo HTTP) dalla gestione del comportamento per generare la risposta. Le classi che si occupano della gestione sono definite controller.
Tali classi presentano diversi metodi pubblici e ciascuna di esse viene associata a una URI e a un metodo HTTP. In questo modo, è possibile raggruppare la logica di gestione di richieste correlate in una singola classe ed evitare che il file delle rotte route/web.php cresca a dismisura rendendo impossibile capire quali rotte effettivamente abbiamo dichiarato.
In una applicazione Laravel, i controller sono salvati nella directory app/Http/Controllers.
Creare un controller in Laravel
Il modo più pratico per creare una classe controller è tramite il comando Artisan make.
php artisan make:controller <NomeController>
È buona abitudine usare il suffisso Controller per il nome del controller e della rispettiva classe, per esempio UserController o PostController.
Rotte e controller in Laravel
Una volta creato un controller, dovremo collegare il controller a una rotta o a un gruppo di rotte. Il modo più semplice è quello di collegare la rotta al controller e al metodo che gestirà la rotta.
use App\Http\Controllers\PostController;
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{id}', [PostController::class, 'show']);
le richieste GET /post verranno gestite dal metodo index classe PostController
le richieste GET /post/{id} verranno gestite dal metodo show classe PostController (che riceverà come parametro $id)
In alternativa, quando si hanno più rotte collegate a un controller, per esempio nei casi in cui è possibile compiere diverse azioni sulla stessa “entità”, è possibile raggruppare insieme tali rotte usando il metodo controller per definire il controller comune e indicare solo il metodo del controller da invocare sulla singola rotta.
use App\Http\Controllers\PostController;
Route::controller(PostController::class)->group(function () {
Route::get('/posts', 'index');
Route::get('/posts/{id}', 'show');
Route::post('/posts', 'store');
});
Metodi di un controller e parametri in Laravel
Vediamo, ora, cosa scrivere all’interno di una classe controller per collegarlo opportunamente con una rotta. Proseguendo con l’esempio del PostController, il suo contenuto potrebbe essere simile al seguente:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(): string
{
return 'List of blog posts';
}
public function show($id): string
{
return "Content for blog post $id";
}
public function store(Request $request): string
{
return 'Created new blog post';
}
}
Possiamo notare che:
i controller possono estendere la classe Controller; non è necessario, ma permette di accedere, nel caso, ad alcuni metodi disponibili per questa classe
i nomi dei metodi della classe corrispondono a quanto indicato nella definizione della rotta, senza questa corrispondenza il metodo del controller non verrà invocato
i metodi del controller possono avere parametri a cui verrà passato il valore del o dei parametri della route
è possibile sfruttare la dependency injection per passare istanze specifiche al metodo, come, per esempio, l’istanza della Request in gestione
Convenzioni metodi HTTP e metodi del controller in Laravel
Anche se non è necessario per far funzionare la connessione tra definizione della rotta e metodi del controller, è buona pratica usare alcuni nomi convenzionali per i metodi del controller in base al tipo di richiesta che stanno gestendo. Ciò è particolarmente utile nel caso di controller che gestiscono gruppi di rotte.
Metodo HTTP
URI
Metodo del controller
Azione
GET
/posts
index
Elenca tutti i post
GET
/posts/{id}
show
Mostra il post <id>
POST
/posts
store
Crea un nuovo post
PUT/PATCH
/posts/{id}
update
Aggiorna un post esistente
DELETE
/posts/{id}
destroy
Elimina un post esistente
In questo modo sarà sempre possibile sapere facilmente e direttamente nel controller quale tipo di richiesta viene gestita da un determinato metodo.
Sapere quale controller e metodo gestisce una rotta in Laravel
Quando si esegue il comando Artisan route:list verrà evidenziato quale controller e quale metodo è collegato alla singola rotta.
$ php artisan route:list --except-vendor
GET|HEAD posts ...................................... PostController@index
POST posts ...................................... PostController@store
GET|HEAD posts/{id} .................................. PostController@show
GET|HEAD user ............................................................
GET|HEAD user/orders .....................................................
GET|HEAD user/profile ....................................................
Dependency injection in Laravel
Oltre a quanto visto per i singoli metodi, è possibile sfruttare il meccanismo della dependency injection per collegare un controller ad altre entità di un progetto Laravel.
Nel nostro esempio di controller per i post di un blog, potrebbe essere necessario recuperare dal database l’elenco dei post, o il contenuto di un singolo post oppure scrivere su database un nuovo post. Senza scendere, ora, nel dettaglio, in una applicazione Laravel è possibile definire una classe repository che serve per interfacciarsi con un database da ogni punto dell’applicazione.
È possibile sfruttare la dependency injection per rendere disponibile una istanza di tale repository aggiungendo un costruttore al nostro controller.
namespace App\Http\Controllers;
use App\Repositories\PostRepository;
class PostController extends Controller
{
/**
* L'istanza del repository dei post utilizzabile da ogni metodo del controller
*/
protected $posts;
public function __construct(PostRepository $post)
{
$this->posts = $posts;
}
// ...
}
Controller single action in Laravel
I controller single action (o invocabili) possono essere utilizzati nel caso in cui, a fronte di una richiesta HTTP, il nostro applicativo deve compiere un’ azione particolarmente complessa. In tal caso, è possibile dedicare un intero controller alla gestione della richiesta.
È possibile creare un controller single action fornendo l’opzione --invokable al comando Artisan make:controller.
php artisan make:controller ShowUpcomingEventsController --invokable
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ShowUpcomingEventsController extends Controller
{
/**
* Gestisce la specifica richiesta
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
{
// elaborazione complicata per recuperare i prossimi eventi a calendario
}
}
Nel collegare controller a rotta non è necessario specificare il metodo, poiché verrà utilizzato di default il metodo __invoke.
// in routes/web.php
Route::get('events', ShowAllEventsController::class);
Route::get('events/past', ShowPastEventsController::class);
Route::get('events/upcoming', ShowUpcomingEventsController::class);
2.5
View in Laravel
Le view in Laravel sono particolari componenti del framework nei quali è possibile definire e salvare la struttura delle pagine HTML e dei file che verranno effettivamente restituiti dalla risposta HTTP. Ciò consente di separare ulteriormente la logica applicativa (route & controller) dalla logica di presentazione (view).
Le view rendono semplice e immediato costruire pagine HTML complesse senza “intaccare” la pulizia del codice dei vari controller. In questo, inoltre, è possibile distinguere le modifiche alla logica di comportamento, sempre nel controller, dalle modifiche visive del contenuto restituito, che potranno essere, quindi, apportate modificando solo la view.
Risposte HTML
Prima di affrontare nel dettaglio le view di Laravel, proviamo a realizzare una response che, effettivamente, restituisca al client che ne fa richiesta una semplice pagina HTML.
Route::get('/hello', function () {
return 'Hello Laravel';
});
In questo modo, abbiamo configurato la rotta per fare return di una stringa che Laravel convertirà in una response HTTP. Verranno, quindi, restituiti al client lo stato (200 OK) e alcuni header necessari. In particolare, Laravel aggiungerà alla risposta l’header Content-Type con valore text/html; charset=UTF-8, indicando, quindi, al client di considerare il contenuto ricevuto come file HTML.
Route::get('/hello', function () {
return response('Hello Laravel', 200)
->header('Content-Type', 'text/plain')
->cookie('name', 'value', $minutes);
});
Possiamo essere più espliciti e utilizzare il metodo response per costruire la risposta desiderata. In questo caso, va indicato il contenuto del body della risposta e il codice HTTP. È possibile anche esplicitare eventuali header per rimpiazzare quelli inseriti di default da Laravel o per aggiungerne degli altri.
A differenza della prima versione, stiamo, qui, dicendo che il contenuto restituito è di tipo text/plain. Il browser, in questo caso, non proverà a farne il rendering come HTML.
Ci sono diversi metodi specifici che è possibile mettere in chain al metodo response per costruire la risposta desiderata.
Route::get('/hello', function () {
return response('<html><body><h1>Hello Laravel</h1></body></html>', 200)
->header('Content-Type', 'text/html');
});
Anche il metodo response, però, richiede che venga passato come parametro l’esatto contenuto del body della response sotto forma di stringa. Questo rende particolarmente difficile poter gestire pagine HTML complete e complesse direttamente nella definizione della route o dei controller, poiché dovremmo premurarci di includere nella stringa l’intero sorgente HTML.
Laravel offre la possibilità di ospitare il contenuto HTML da restituire in un file separato tramite le view.
View e logica di presentazione in Laravel
Le view permettono di separare la logica applicativa (che per ora consideriamo essere quanto viene fatto dal controller) dalla logica di presentazione (ovvero come il risultato verrà mostrato a chi ha effettuato la richiesta).
Le view altro non sono che file ospitati nella directory resources/views che costituiranno il contenuto di una risposta. I controller potranno recuperare una certa view tramite una convenzione basata sul nome del file della view e l’uso del metodo view nel controller o nella rotta.
<!-- La view salvata nel file resources/views/hello.blade.php -->
<html>
<body>
<h1>Hello Laravel</h1>
<p>I'm a view</p>
</body>
</html>
// rotta nel file routes/web.php
Route::get('/hello', function () {
return view('hello');
});
NOTA: sebbene, per semplicità di stesura, gli esempi di questa lezione usino il metodo view nella definizione della rotta, è, ovviamente, possibile usare tale metodo nella classe controller associata alla rotta.
Nomi delle view e relativi file di risorsa in Laravel
Nella prossima lezione approfondiremo meglio il linguaggio di templating Blade; per ora, è sufficiente ricordare che i file delle view nella directory resources/views debbono avere estensione .blade.php poiché sono a tutti gli effetti dei template di Blade.
Il nome del file corrisponderà, quindi, al nome della view da richiedere nel controller. Nell’esempio precedente, la view ‘hello’ corrisponderà al file resources/views/hello.blade.php.
È possibile creare sottodirectory in resources/views in modo da organizzare le varie view realizzate.
Le view raccolte nelle varie sottodirectory saranno richiamabili dal controller tramite una “dot notation”.
return view('user.profile');
Contenuto directory delle view
L’organizzazione delle view all’interno della directory resources/views può essere slegata da nomi di controller e path delle URI gestite. Non è, quindi, necessario che la view user.orders e la view admin.orders corrispondano rispettivamente alle URI https://example.com/user/orders e https://example.com/admin/orders.
Passaggio di dati alle view in Laravel
Il meccanismo delle view di Laravel offre, ovviamente, la possibilità di passare dati dinamici alla singola view selezionata. In questo modo, è possibile realizzare contenuti dinamici.
Il file della view, infatti, è un file template, ossia un file che serve da scheletro per costruire l’effettiva risposta in base ai dati ricevuti.
<!-- La view salvata nel file resources/views/hello.blade.php -->
<html>
<body>
<h1>Hello {{ $name }}</h1>
<p>I'm a view</p>
</body>
</html>
// file routes/web.php
Route::view('/hello', 'hello', ['name' => 'Laravel']);
Route::get('/hello-bis', function () {
return view('hello', ['name' => 'Laravel']);
});
È possibile passare un array di dati alla view selezionata per rendere tali dati disponibili alla view. Usando le opportune convenzioni, il sistema di rendering dei template sarà in grado di usare tali dati.
Dal punto di vista dell’invocazione del metodo view, i dati da passare devono essere un array di coppie chiave / valore. Sarà possibile accedere ai valori nella view usando la chiave ($name nell’esempio precedente).
Un metodo alternativo per passare dati alla view è tramite il metodo with, che permette di aggiungere singole coppie chiave / valore.
return view('hello')
->with('name', 'Laravel')
->with('copyright', 2023);
Ciò consente, ad esempio, di strutturare il proprio codice in modo da effettuare all’inizio del codice di un metodo del controller le operazioni necessarie a calcolare i vari valori, associare tali valori ai nomi delle variabili della view in un array e passarli al metodo helper view per generare il contenuto definitivo.
// nella classe controller per la rotta /user/profile
public function userProfile() {
$userName = $service->getCurrentUserName();
$data = ['firstName' => $username];
return view('user.profile', $data);
}
3
Template HTML in Laravel con Blade
3.1
Template Blade in Laravel
Blade è il php template engine fornito di default dal framework Laravel. È pensato per rendere semplice la scrittura di template, mantenendo, allo stesso tempo, la possibilità di definire template non banali che possono utilizzare al loro interno codice PHP.
Lo scopo di un template engine è quello di creare uno specifico contenuto da presentare partendo da un modello e applicando ad esso dati. Il modello è salvato, abitualmente, in un file chiamato, per l’appunto, “template”, mentre i dati vengono recuperati da altre porzioni della propria applicazione e applicati al template tramite l’invocazione di un opportuno metodo.
Ciò consente di separare, nella propria applicazione, la parte di logica del recupero dei dati dalla parte di presentazione del dato (per intenderci, possiamo applicare lo stesso dato a diversi template e ottenere pagine diverse nell’aspetto, oppure possiamo scegliere cosa mostrare in base ai dati che abbiamo recuperato in quel momento).
I file di template Blade hanno estensione .blade.php e sono raccolti nella directory resources/views di una applicazione Laravel. L’elaborazione del template avviene invocando il metodo helper view, come visto nella precedente lezione.
Elementi base di un template Blade in Laravel
I template definiti con Blade fanno uso di particolari costrutti, chiamati statement o direttive, per permettere allo sviluppatore web di decidere come mostrare il contenuto.
<!-- resources/views/hello.blade.php -->
<html>
<body>
<h1>Hello {{ $name }}</h1>
<p>Today is {{ time() }}</p>
</body>
</html>
Lo statement più semplice e importante è {{ }}, indicato anche come echo statement. Tra le doppie parentesi graffe è possibile inserire del codice PHP che verrà elaborato e il suo risultato verrà inserito nel rendering del template. È possibile usare variabili (che verranno passate al template engine dal metodo helper view) o altri metodi del linguaggio PHP.
<!-- resources/views/hello.blade.php -->
<html>
<body>
<h1>
@if($name)
Hello {{ $name }}
@else
Hello Laravel
@endif
</h1>
</body>
</html>
// routes/web.php
Route::get('/hello', function (Request $request) {
$name = $request->query('name');
return view('hello', ['name' => $name]);
});
Altre direttive permettono di aggiungere logica (di presentazione) al proprio template, semplificando, quindi, il codice lato route / controller. L’esempio qui sopra è una versione alternativa di quanto visto nella lezione sulle rotte, in cui la scelta di cosa mostrare, nel caso in cui non sia stato passato un name in query string, passa dal controller alla view, grazie alle funzionalità offerte dalla direttiva @if del template engine Blade.
NOTA: nel caso in esempio, l’uso della direttiva @if è esagerato. È possibile, infatti, usare l’operatore null coalescence di PHP dentro a un echo statement per ottenere lo stesso risultato in modo più elegante e chiaro: <h1>Hello {{ $name ?? 'Laravel' }}</h1>. Non dimenticare mai che i template Blade sono pur sempre file PHP, anche se l’esecuzione di codice PHP è permessa in particolari posizioni.
Direttive Blade di flusso
Alcune direttive di Blade risultano particolarmente utili per mostrare contenuti in base a determinate condizioni, basate ovviamente sui valori passati al template e letti da Blade come variabili PHP. Una caratteristica comune a queste direttive è che racchiudono la logica tra una direttiva d’apertura e una direttiva di “end”.
Direttiva @if in Blade
È possibile realizzare costrutti “if” usando le direttive @if, @elseif, @else e @endif. Queste direttive funzionano in maniera identica agli analoghi PHP.
@if (count($likes) === 1)
You have only one like :-(
@elseif (count($likes) > 1 && count($likes) < 10)
You have {{ count($likes) }} likes :-)
@elseif (count($likes) >= 10)
People really likes you :-D
@else
No likes :'(
@endif
Al fine di rendere il template più leggibile, Blade mette a disposizione alcune direttive di comodità che ricadono nell’ambito degli “if”:
@unless(<condition>) ... @endunless che corrisponde a un “if not”
@isset(<variable>) ... @endisset che controlla se la variabile è definita e non ha valore null
@empty(<variable>) ... @endempty che controlla è “empty”
Direttiva @switch in Blade
Le direttive @switch, @case, @break, @default e @endswitch permettono di costruire costrutti “switch”, sempre operando in maniera analoga alle controparti PHP.
@switch($variable)
@case(1)
First case...
@break
@case(2)
Second case...
@break
@default
Default case...
@endswitch
Direttive di loop in Blade
Sono, ovviamente, disponibili direttive analoghe a quelle PHP per gestire con strutture di loop, che operano nello stesso modo.
@for ($value = 0; $value < 10; $value++)
The current value is {{ $value }}
@endfor
@foreach ($fruits as $fruit)
<p>Fruit {{ $fruit->$name }} in your order</p>
@endforeach
@forelse ($fruits as $fruit)
<li>{{ $fruit->$name }}</li>
@empty
<p>No fruit</p>
@endforelse
@while (true)
<p>I'm looping forever.</p>
@endwhile
L’esecuzione della iterazione corrente del loop può essere saltata tramite le direttive @countinue e @break (usando una direttiva @if per definire la condizione di skip).
Una particolarità della direttiva @foreach è quella di rendere disponibile una speciale variabile $loop all’interno del loop stesso. Tale variabile permette di accedere a informazioni sul loop stesso, come, per esempio, l’iterazione corrente ($loop->iteration, che parte da 1, oppure $loop->index che parte da 0) oppure se è la prima/ultima del loop ($loop->first e $loop->last, entrambi booleani).
Direttive Blade di contenuto
Alcune direttive Blade sono state rese disponibili per semplificare l’elaborazione di determinati elementi HTML partendo dai valori passati al rendering del template
La direttiva @class permette di compilare una stringa per definire una classe CSS.
// valori passati al template engine
$isActive = false;
$hasError = true;
<!-- file .blade.php -->
<span @class([
'font-bold' => $isActive,
'text-gray-500' => ! $isActive,
'bg-red' => $hasError,
])></span>
<!-- output -->
<span class="text-gray-500 bg-red"></span>
Questa direttiva accetta un array chiave/valore in cui la chiave è il nome della classe CSS da aggiungere e il valore è una espressione booleana che stabilisce se aggiungere tale classe.
La direttiva @checked può essere usata per aggiungere l’attributo checked a un elemento HTML di tipo <input> quando la condizione indicata ha valore true.
<input type="checkbox"
name="newsletter"
value="newsletter"
@checked(@old('newsletter), $user->$newsletterOK)
/>
In modo analogo operano le direttive @selected per le option di una select HTML (), @disabled, @readonly e @required.
Layout e view child in Blade
Il vero punto di forza del template engine Blade risiede, però, nella sua capacità di poter separare parti comuni a vari singoli template in file separati e ricostruire, poi, il contenuto assemblando insieme le varie porzioni, con una logica basata sulla ereditarietà. Questo meccanismo prende il nome di template layout.
La possibilità di avere uno o più template base condivisi è molto importante per la creazione di siti web, poiché gran parte delle pagine di un sito o di una web app mantengono una struttura comune ed elementi ripetuti. Se si dovesse creare un file di template con l’HTML completo per ogni tipo di pagina erogata, si finirebbe con il dover modificare su più file le parti di codice che sono in comune (header, footer, ..).
Cominciamo con un pratico esempio, ipotizzando di voler realizzare un template layout in cui ci sono parti comuni (per esempio, i titoli con il nome del sito) e parti specifiche della pagina che stiamo realizzando.
Layout
Il file di template blade che rappresenterà la nostra “base” per tutte le pagine potrà essere salvato nella directory resources/views/layouts/ - quanto meno per ricordarci che è uno dei template layout di base - e avrà il seguente contenuto:
{{-- resources/views/layouts/page.blade.php --}}
<html>
<head>
<title>@yield('title') - Yet No Name</title>
</head>
<body>
@section('header')
<p>This is the website Yet No Name</p>
@show
<div class="content">
@yield('content')
</div>
</body>
</html>
In questo template abbiamo definito l’intera struttura di una pagina HTML e abbiamo utilizzato alcune direttive speciali di Blade: @section e @yeld. La direttiva @section serve a definire una sezione di un contenuto, mentre la direttiva @yeld è usata come segnaposto per dire dove andare a inserire una @section con lo stesso nome.
È possibile, a questo punto, definire una vista “child” che erediterà questo layout di base tramite la direttiva @extends:
{{-- resources/views/one.blade.php --}}
@extends('layouts.page')
@section('title', 'Page One')
@section('header')
@parent
<p>This is the page one.</p>
@endsection
@section('content')
<p>This is the content for Page One.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
@endsection
Una child view usa la direttiva @extends per indicare la view layout genitore da cui il child dovrebbe “ereditare”. All’interno della child view è possibile indicare il contenuto da iniettare nel genitore tramite le direttive @section. L’esatta composizione dipende dalla modalità con cui sono state definite le varie sezioni nel layout genitore e nella view figlia.
In particolare notare:
@yeld('title') nel layout e @section('title', 'Page One') nel child → in questo modo nel layout abbiamo indicato un segnaposto, lasciando al child fornire il contenuto.
@yield('content')nel layout e @section('content') ... @endsection nel child → si comporta come il precedente, ma essendo un contenuto più complesso è compreso tra la direttiva di apertura e quella di chiusura della sezione.
@section('sidebar') ... @show nel layout e @section('sidebar') @parent ... @endsection nel child → in questo caso, la section si apre nel layout e viene chiusa nel child, poiché è stata indicata la direttiva @show nel layout e @parent nel child.
Il file HTML generato invocando view('one') sarà, quindi, il seguente
<html>
<head>
<title>Page One - Yet No Name</title>
</head>
<body>
<p>This is the website Yet No Name</p>
<p>This is the page one.</p>
<div class="content">
<p>This is the content for Page One.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</div>
</body>
</html>
SubView incluse
NOTA: riportiamo per completezza la direttiva @include anche se il suggerimento degli sviluppatori di Laravel è quello di usare per la stessa esigenza i componenti Blade
La direttiva @include di Blade opera nel modo che il nome suggerisce: include una view all’interno di una view.
{{-- ... -- }}
@section('content')
@include('shared.footer')
La view inclusa riceverà tutti i dati disponibili nella view genitore, ma sarà possibile passarne altri indicando un array di coppie chiave / valore.
Sono disponibili alcune varianti della direttiva: @includeWhen, @includeUnless (condizione booleana per includere la view) e @includeFirst (la prima view trovata in un array di view).
View di collection
È disponibile una direttiva che unisce in sé le funzioni delle direttive @foreach e @include
{{-- file resources/view/page.blade.php --}}
@each('shared.order', $orders, 'order')
{{-- file resources/view/shared/fruit.blade.php --}}
<span>{{ $order->$id }} - {{ $order->$amount }} - {{ $order->$status }}</span>
Il primo parametro della direttiva @each è il nome della view da utilizzare, il secondo parametro è l’array o la collection su cui iterare, il terzo argomento è il nome che avrà il singolo elemento su cui si sta iterando all’interno della view.
3.2
Blade Component in Laravel
I template Blade presentati nella precedente lezione ci hanno permesso non solo di renderizzare un contenuto dinamico, ma anche di creare delle view componibili. Le direttive @yeld e @section ci hanno permesso di separare file di view condivisi e dedicati alla definizione del layout comune da file di view specifici dedicati al contenuto più customizzato.
La modalità con cui view di layout e view child interagiscono, però, potrebbe non essere immediatamente comprensibile, proprio in virtù della sua modalità di funzionamento per ereditarietà.
Laravel e Blade offrono un'alternativa all’approccio basato su section, layout e include: tale approccio fa uso di quelli che sono definiti component e slot.
NOTA: i component di Blade si sono evoluti molto ad ogni rilascio di Laravel; in questa guida facciamo riferimento a quanto disponibile nella versione 9.x di Laravel
Confronto tra layout/section e component/slots in Laravel
Supponiamo che la nostra applicazione Laravel gestisca una sola pagina, https://example.com/welcome. Vediamo tre alternative con cui è possibile scrivere template di view che realizzeranno lo stesso output HTML.
Singolo template
{{-- resources/views/welcome.blade.php --}}
<html lang="en">
<head>
{{-- many stuff in head here ... --}}
</head>
<body>
<h1>Welcome Laravel</h1>
</body>
</html>
Section e layout
{{-- resources/views/layout.blade.php --}}
<html lang="en">
<head>
{{-- many stuff in head here ... --}}
</head>
<body>
@yeld('content')
</body>
</html>
{{-- resources/views/welcome.blade.php --}}
@extends('layout')
@section('content')
<h1>Welcome Laravel</h1>
@endsection
Il template che useremo per le view (welcome) deve estendere il template che usa come layout. Entrambi devono fare riferimento alla stessa section (content), uno con la direttiva @yeld, l’altro con la direttiva @section.
Component e slot
{{-- resource/views/components/layout.blade.php --}}
<html lang="en">
<head>
{{-- many stuff in head here ... --}}
</head>
<body>
{{ $slot }}
</body>
</html>
{{-- resources/views/welcome.blade.php --}}
<x-layout>
<h1>Welcome Laravel</h1>
<x-layout>
Viene creato un template di tipo component creando il file nella directory riservata resource/views/components/. Tale componente include un echo statement della variabile $slot nel punto in cui andrà a inserire contenuto passato dall’esterno.
Il template che andremo a usare per la view (welcome) richiama il componente tramite uno pseudo elemento HTML (x- seguito dal nome del componente) e specifica tra l’apertura e chiusura di questo elemento il valore che assumerà la variabile $slot.
Componente e view che lo utilizza non devono dichiarare la stessa section. I due non sono più legati da una relazione di ereditarietà e la modalità di utilizzo “alla linguaggio HTML” senza più direttive, rende i template più facili da leggere (fai il rendering di questo componente qui, usando questo contenuto).
Rendering dei Blade Component
Un componente Blade è renderizzato in un template utilizzando la sintassi <x-.
{{-- nel template chiedo di fare il rendering del componente --}}
<x-alert></x-alert>
{{-- nel componente resources/views/components/alert.blade.php definisco i dettagli --}}
<div>
<span>This is a static alert content by now...</span>
<button>OK</button>
</div>
È possibile passare dati ai componenti utilizzando una sintassi analoga a quella degli attributi HTML. Gli attributi che contengono espressioni e variabili PHP devono avere : come prefisso.
Nella definizione del componente è possibile definire gli attributi che dovrebbero essere usati come variabili per i dati tramite la direttiva @props
{{-- il valore di message verrà recuperato dalla variable $message --}}
<x-alert type="warning" :message="$message"></x-alert>
@props([
// questa prop ha un default
'type' => 'info',
'message',
])
<div class="alert type-{{ $type }}">
<span>{{ $message }}</span>
<button>OK</button>
</div>
Un modo alternativo per passare dati a un componente è tramite gli slot. Invece di usare il meccanismo degli attributi HTML, con gli slot viene passato l’intero contenuto indicato tra i tag di apertura e chiusura.
{{-- rendering nel template --}}
<x-alert type="warning">{{ $message }}</x-alert>
@props([
'type' => 'info',
])
<div class="alert type-{{ $type }}">
<span>{{ $slot }}</span>
<button>OK</button>
</div>
NOTA: è anche possibile passare più slot al componente, con un meccanismo chiamato named slot. Per approfondimenti rimandiamo alla guida dei Blade component
È disponibile un meccanismo definito “attribute bag” che permette di leggere dal componente tutti gli altri eventuali attributi aggiuntivi (rispetto a quelli definiti come props del componente) passati al rendering del componente.
{{-- $id = 6758 --}}
{{-- rendering nel template --}}
<x-alert type="warning" role="big-alert" data-id="{{ $id }}">{{ $message }}</x-alert>
@props([
'type' => 'info',
])
{{-- $attributes sarà: role="big-alert" data-id="6758" --}}
<div class="alert type-{{ $type }} {{ $attributes }}">
<span>{{ $slot }}</span>
<button>OK</button>
</div>
Non dimenticare che un componente Blade può usare le varie direttive messe a disposizione dal php template engine di Blade.
{{--
$alerts = [
['type' => 'info', 'message' => 'info message one'],
['type' => 'warning', 'message' => 'warning message one'],
['type' => 'error', 'message' => 'error message one'],
['type' => 'info', 'message' => 'info message tho'],
]
--}}
<x-alert-box :alerts="$alerts"><x-alert-box>
{{-- resources/views/components/alert-box.blade.php --}}
<div>
@foreach($alerts as $alert)
<x-alert :type="$alert['type']">{{ $alert['message'] }}</x-alert>
@endforeach
</div>
Componenti anonimi e class based in Blade
Blade mette a disposizione due tipi diversi di componente, class based e anonymous, che possono essere usati in contesti diversi. La scelta dell’uno o dell’altro dipende molto dal tipo di elaborazione che serve tra gli attributi che passiamo al componente nel momento in cui ne chiediamo il rendering e gli effettivi dati che andremo a mostrare all’interno del componente renderizzato.
Gli esempi di componenti visti finora sono stati tutti di componenti anonymous. Al componente veniva passato ciò che effettivamente veniva renderizzato (ad esempio, il testo di un messaggio di alert). Già nell’ultimo esempio, però, ci è stato necessario “elaborare” quanto ricevuto per estrarre informazioni, affidandoci al fatto che sapevamo di ricevere una determinata struttura dati.
Laravel e Blade mettono a disposizione degli sviluppatori php un altro tipo di componente, definito per l’appunto “class based”, per il quale, oltre al file di template blade, è presente anche una classe PHP nella directory app/View/Components di supporto al rendering del componente .
Classe e template sono legati nel seguente modo:
nel momento in cui un template blade chiede il rendering di un componente, l’attributo del componente è passato al costruttore della classe PHP del componente
la classe PHP del componente presenta un metodo render, che implementa il metodo view richiamando il template del componente
tutte le property pubbliche della classe del componente vengono passate in modo implicito all’esecuzione di questo metodo view
Vediamo, con un esempio, come sfruttare queste caratteristiche dei componenti class based.
Esempio di componente class based in Blade
Vogliamo realizzare un componente che ci permetta di mostrare tra quanti anni/mesi/giorni/ore finirà una certa offerta, partendo dalla data in cui l’offerta terminerà.
Per creare un nuovo componente class based è più semplice utilizzare il comando Artisan make:component, che crea sia il template Blade che la classe PHP del componente.
$php artisan make:component DateTime/Countdown
INFO Component [app/View/Components/DateTime/Countdown.php] created successfully.
NOTA: abbiamo deciso di creare il componente in una sottodirectory, quindi il file blade sarà in resources/views/components/date-time/countdown.blade.php e potrà essere inserito in un altro template usando <x-date-time.countdown.
Sappiamo di voler passare al nostro componente una data futura e di voler renderizzare quanti giorni mancano. Traendo ispirazione dal componente Countdown di una libreria di componenti Blade disponibili online, possiamo optare per una soluzione come la seguente:
{{-- nel template della view che richiede il componente, il valore di $date sarà un DateTime --}}
{{-- $date = new DateTime('@' . mktime(23, 59, 59, 12, 31, 2099)) --}}
<x-date-time.countdown :expires="$date"/>
Artisan avrà creato per noi la classe e il template vuoto. Andiamo, quindi, a modificare la classe indicando che al componente verrà passato un argomento $expire di tipo DateTimeInterface, inserendolo come parametro nel costruttore di classe.
// app/View/Components/DateTime/Countdown.php
namespace App\View\Components\DateTime;
use DateInterval;
use DateTimeInterface;
use Illuminate\View\Component;
use Illuminate\Contracts\View\View;
class Countdown extends Component
{
public DateTimeInterface $expires;
public function __construct(DateTimeInterface $expires)
{
$this->expires = $expires;
}
public function render(): View
{
return view('components.date-time.countdown');
}
}
Notare che, trattandosi di un componente class based, è possibile sfruttare in type hinting di PHP, andando, quindi, a scegliere che tipo di dato ci aspettiamo per il valore passato al componente.
Potremmo passare alla template del costruttore la property $expires della classe ma, in questo modo, l’unico vantaggio rispetto a un componente anonimo sarebbe quello di avere il type hinting su tale valore.
Qui interviene la caratteristica principale di un componente class based, ossia quello di rendere disponibile al template collegato le property e i metodi pubblici della classe corrispondente.
Possiamo, quindi, aggiungere direttamente nella classe Countdown i vari metodi necessari a calcolare quanti mesi/giorni/ore mancano alla scadenza indicata rispetto a now().
// nella classe Countdown in app/View/Components/DateTime/Countdown.php
// ...
private function difference(): DateInterval
{
return $this->expires->diff(now());
}
public function days(): string
{
return sprintf('%02d', $this->difference()->d);
}
public function hours(): string
{
return sprintf('%02d', $this->difference()->h);
}
// ...
Infine, possiamo utilizzare i metodi della classe direttamente nel template del componente:
{{-- resources/views/components/date-time/countdown.blade.php --}}
<div>
<span>Will end in </span>
<span>{{ $years() }} years </span> :
<span>{{ $months() }} months </span> :
<span>{{ $days() }} days </span> :
<span>{{ $hours() }} hours </span> :
<span>{{ $minutes() }} minutes </span> :
<span>{{ $seconds() }} seconds </span>
</div>
Notare come l’attributo expires che è necessario passare per richiedere il rendering del componente nel template di una view, non è, poi, utilizzato all’interno del template del componente, ma rimane “confinato” all’interno della classe PHP.
I componenti Blade class based sono, quindi, particolarmente utili nel caso in cui sia necessario o utile effettuare delle elaborazioni prima di passare il dato al template del componente, oppure connettersi ad altre parti della propria applicazione Laravel, per esempio, tramite dependency injection di un service provider
4
Database in Laravel con Eloquent ORM
4.1
Laravel e i database relazionali
Uno dei motivi per cui usare Laravel per la propria applicazione web è anche la versatilità e la semplicità con cui è possibile interagire con uno dei database supportati.
Difficile, infatti, immaginare una applicazione web moderna che non debba interagire con una base dati. Laravel offre agli sviluppatori php diversi approcci, a seconda della necessità, che partono dall’esecuzione diretta di codice SQL, passano per la costruzione di query tramite builder, fino ad arrivare al sistema ORM Eloquent.
Al momento della stesura di questa guida, Laravel (9.x) supporta i seguenti database:
MariaDB 10.3+
MySQL 5.7+
PostgreSQL 10.0+
SQL Server 2017+
SQLite 3.8.8+
Database relazionali
Con database management systems (DBMS) si intende un programma che consente di interagire con un database: scrivere dati, eseguire query, eseguire altri task relativi alla gestione di un database. Per consentire queste interazioni, il DBMS rende disponibile un suo modello interno che definisce come il DBMS stesso organizza i dati memorizzati.
Il modello relazionale è un modo di organizzazione dei dati più comune e diffuso nei DBMS, nonché il primo pensato negli anni ’60.
L’elemento fondamentale del modello relazione sono le relazioni, che nei RDMBS (Relational DBMS) moderni vengono indicate come tabelle. Una relazione è un set di tuple - o righe in una tabella - in cui ciascuna tupla condivide un insieme di attributi - o colonne.
Database - Relazioni
Una colonna è la struttura organizzativa più piccola di un database relazionale; ogni colonna rappresenta un aspetto che definisce i record nella tabella. Alla creazione di una colonna in una tabella viene indicato anche il tipo di dato, che definisce il tipo di voce consentito nella colonna.
Nei database relazionali, ogni tabella contiene almeno una colonna utilizzabile per identificare in modo univoco ogni riga. Tale colonna costituisce la chiave primaria.
Nel caso in cui si volesse creare una associazione tra due tabelle, è possibile utilizzare le cosiddette foreign key: una foreign key è, in pratica, una colonna di una tabella (child) che fa riferimento ai valori univoci - la primary key - di un’altra tabella (parent). In questo modo, due o più tabelle possono essere collegate l’una all’altra (per esempio, non è possibile aggiungere nella tabella child una riga per cui il valore della foreign key non è presente nella tabelle parent).
Per recuperare informazioni da un RDBMS, è possibile inviare una query, cioè una richiesta strutturata di un set di informazioni. I database relazionali usano il linguaggio SQL (Structured Query Language) per definire e inviare tali richieste. Le query consentono di scegliere quali info estrarre e con quali clausole e criteri.
Configurazione e connessione
Nel momento in cui si crea una nuova applicazione Laravel, viene già creato il file di configurazione config/database.php che è pronto per far connettere l’applicazione ad uno qualsiasi dei database supportati.
Non è necessario, però, modificare tale file, a meno di non dover impostare qualche opzione aggiuntiva particolare. Laravel, infatti, permette di impostare i valori esatti della propria connessione a un database tramite il file .env presente nella directory root del progetto.
In tale file possiamo trovare la seguente configurazione:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
La prima variabile ambientale, DB_CONNECTION, indica quale delle connessioni indicate nel file config/database.php verrà utilizzata. Sono pre-configurate diverse connessioni, una per tipo di database supportato (sqlite, mysql per MariaDB e MySQL, pgsql per PostgreSQL, sqlsrv per SQL Server), ognuna con già impostato il driver opportuno e le opzioni aggiuntive dedicate.
Le altre variabili vanno impostate a seconda del tipo di database che utilizzeremo e, ovviamente, con i valori che ci permettono di puntare al nostro database.
Esecuzione di query SQL
Una volta configurata la connessione a un database (e supponendo che sul database siano già state create le opportune tabelle), sarà possibile eseguire query SQL in ogni classe della nostra applicazione tramite i vari metodi offerti dalla facade DB.
use Illuminate\Support\Facades\DB;
class PostController extends Controller
{
public function index()
{
$posts = DB::select('select title from posts where active = :active', ['active' => 1]);
$count = DB::scalar('select count(1) from posts where active = :active', ['active' => 1]);
return view('posts', ['posts' => $posts, 'count' => $count]);
}
}
<div>
Post count is {{ $count }}
<ul>
@foreach($posts as $post)
<li>{{ $post->title }}</li>
@endforeach
</ul>
</div>
I vari metodi della facade DB accettano come primo argomento la query SQL da eseguire. È possibile indicare eventuali parametri variabili della query tramite named binding dei parametri: nella query d’esempio, il valore della colonna active su cui è eseguita la WHERE è passato collegando il “segnaposto” :active con l’array passato come secondo argomento del metodo.
Il metodo DB::select restituisce un array di risultati, ciascuno dei quali è un oggetto PHP stdClass. Il metodo DB::scalar, invece, restituisce un singolo valore scalare, utile per le query di “count”.
È possibile inserire e modificare record con i metodi DB::insert e DB::update.
DB::insert(
'insert into posts (title, content, active) values (?, ?, ?)',
['Another Post', 'Some cool content', 1]
);
// ...
// $affected è in numero di righe cambiate dalla query
$affected = DB::update(
'update posts set active = 0 where title LIKE ?',
['%DRAFT%']
);
Notare il diverso tipo di binding dei parametri della query, realizzato con ?.
È, ovviamente, disponibile la possibilità di eseguire le varie query tramite transazione. Una transazione permette di eseguire diverse operazioni sul database, facendo rollback di tutte quelle eseguite fino a un certo punto se una delle varie operazioni non dovesse riuscire.
DB::transaction(function () {
DB::update('update posts set active = 8');
DB::delete('delete from comments');
});
Query Builder
Scrivere query SQL a mano e poi passare come array gli eventuali parametri variabili è poco pratico, specie quando il numero di colonne di una tabella su cui si vuole aggiungere condizioni non è piccolo.
La facade DB offre un metodo per costruire query utilizzando costrutti PHP.
$posts = DB::table('posts')->where('active', 1)->get('title');
$count = DB::table('posts')->where('active', 1)->count();
Attenzione però: l’ordine con cui si “concatenano” i vari tipi di metodi è importante. Il metodo where, infatti, aggiunge una clausola WHERE al builder, mentre sono effettivamente i metodi get e count a dare il via all’esecuzione della query.
Rimandiamo alla documentazione ufficiale di Laravel per dettagli sulle varie possibilità offerte dal query builder.
Comando Artisan per database
Il comando Artisan db permette di collegarsi al database configurato nell’applicazione Laravel dalla riga di comando. Vari sottocomandi sono disponibili per ricavare informazioni sul database o sulle singole tabelle.
$ php artisan db:show
SQLite ..................................................
Database ....................... database/database.sqlite
Host ....................................................
Port ....................................................
Username ................................................
URL .....................................................
Open Connections ........................................
Tables ................................................ 6
Table ........................................ Size (MiB)
failed_jobs ........................................... —
migrations ............................................ —
password_resets ....................................... —
personal_access_tokens ................................ —
posts ................................................. —
users ................................................. —
$ php artisan db:table posts
posts ....................................................
Columns ................................................ 3
Column .............................................. Type
id autoincrement, integer ........................ integer
title text .......................................... text
active integer ................................. 1 integer
Index ....................................................
primary id ............................... unique, primary
4.2
Migrazioni del database in Laravel
Nell’ambito dei database una migrazione è un processo che permette di creare o modificare lo schema di una tabella in modo che sia possibile adattare la struttura dei dati salvati nella tabella di pari passo con le modifiche al codice che usa quelle tabelle. Potendo “versionare” la struttura delle tabelle collegandolo alla specifica versione del codice, le migrazioni facilitano le eventuali azioni di rollback di un deploy.
È importante notare che questa modalità di aggiornamento dello schema del database è facilitata nel caso in cui un database “appartenga” ad una sola applicazione, che è la sola ad avere la possibilità di modificare a piacimento la sua struttura.
Abitualmente, specie per i servizi web, la migrazione avviene nel momento in cui si effettua il deploy di una nuova versione dell’applicazione, immediatamente prima o nelle prime fasi del riavvio del servizio alla versione aggiornata.
Una migrazione “ben scritta” permette anche di effettuare l’opposto: in caso di problemi con la nuova versione, si potrebbe decidere di tornare alla versione precedente, azione indicata come rollback. In questo caso, sapendo che alla versione precedente dell’applicazione era associata una determinata “versione” dello schema del DB, sarà possibile applicare “al contrario” la migrazione.
Laravel offre pieno supporto alle migrazioni, attraverso la facade Schema e vari comandi di Artisan per creare e applicare migrazioni. Le migrazioni create sono salvate nella directory database\migration come classi PHP che estendono la classe Migration.
Creare tabelle in Laravel
La migrazione più semplice che possiamo immaginare di dover realizzare è quella in cui creiamo nuove tabelle. Nell’ottica in cui un database “appartiene” ad un'applicazione, è normale creare, nelle prime fasi di vita di un progetto, le prime tabelle necessarie a salvare e recuperare i dati necessari al progetto stesso.
Per creare una nuova migrazione è possibile affidarsi al comando Artisan make:migration.
$ php artisan make:migration create_posts_table
INFO Migration [database/migrations/2023_01_23_193847_create_posts_table.php] created successfully.
Il file della migrazione avrà nel nome il giorno e l’ora in cui è stata creata la migrazione. Ciò è importante per determinare l’ordine con cui le migrazioni verranno applicate (prima le più vecchie, poi le più recenti).
Il nome indicato per la migrazione (create_user_table) verrà usato da Laravel per tentare di capire qual è l’intento della migrazione e la tabella che si andrà a toccare, riempiendo, quindi, in maniera opportuna il codice generato. Nel caso in esempio, il file di migrazione creato conterrà da subito quanto segue:
// database/migrations/2023_01_23_193847_create_posts_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down()
{
Schema::dropIfExists('posts');
}
};
Una classe di migrazione contiene due metodi:
up: utilizzato per indicare le modifiche da applicare quando la migrazione “va su”; le modifiche ovviamente possono essere sia aggiunte di tabelle / colonne / indici, che rimozioni o cambi.
down: utilizzato per indicare le modifiche da applicare quando si vuole o si devono annullare le modifiche applicate dal metodo up
Laravel ha già inserito nei due metodi le azioni necessarie a creare una tabella con le colonne che ritiene necessarie o utili ($table->id() per l’id univoco di ogni riga, $table->timestamps() per le colonne created_at e updated_at).
Notare, in particolare, che il metodo down pre-generato da Laravel effettua il drop della tabella, portando alla perdita di ogni eventuale dato contenuto in essa nel caso in cui dovessimo fare il rollback. È un comportamento corretto, poiché, indicativamente, le migrazioni si interessano del tipo di dato salvato su un database, non del loro esatto contenuto. Il revert della creazione di una tabella è l’eliminazione della tabella.
A questo punto, ci è possibile aggiornare il codice della migrazione, inserendo le colonne che ci interessa avere nel nostro database, specificandone nome e tipo nel modo opportuno.
//...
$table->id();
$table->timestamps();
$table->string('title');
$table->longText('content')->nullable();
$table->enum('status', ['draft', 'published', 'unpublished'])->default('draft');
$table->timestamp('published_at')->nullable();
//...
Siamo, ora, pronti ad eseguire la nostra prima migrazione.
Eseguire migrazioni in Laravel
Per ogni operazione che riguardi l’esecuzione delle migrazioni, Laravel mette a disposizione il comando Artisan migrate e i suoi sotto-comandi.
L’esecuzione di questi comandi controllerà lo stato del database che è stato definito nell’attuale configurazione dell’applicazione e rispetto alle migrazioni presenti nella directory database\migration.
Per prima cosa, potremo vedere quali migrazioni sono già state applicate sul database.
$ php artisan migrate:status
Migration name .......................................... Batch / Status
2014_10_12_000000_create_users_table .................... [1] Ran
2014_10_12_100000_create_password_resets_table .......... [1] Ran
2019_08_19_000000_create_failed_jobs_table .............. [1] Ran
2019_12_14_000001_create_personal_access_tokens_table ... [1] Ran
2023_01_23_193847_create_posts_table .................... Pending
Poi, potremmo vedere quale sarà l’effettiva query che verrà eseguita sul database applicando la o le migrazioni “pending” (cioè non ancora applicate al database).
$ php artisan migrate --pretend
INFO Running migrations.
2023_01_23_193847_create_posts_table ....................
⇂ create table "posts" ("id" bigserial primary key not null, "created_at" timestamp(0) without time zone null, "updated_at" timestamp(0) without time zone null, "title" varchar(255) not null, "content" text null, "status" varchar(255) check ("status" in ('draft', 'published', 'unpublished')) not null default 'draft', "published_at" timestamp(0) without time zone null)
Infine, sarà possibile eseguire la migrazione
php artisan migrate
INFO Running migrations.
2023_01_23_193847_create_posts_table .................... 3ms DONE
Una cosa, in particolare, è importante notare: il risultato dei vari comandi e la query che verrà effettivamente eseguita sul database dipendono dalla connessione al database attualmente configurata. Nel codice PHP della migrazione, infatti, sono stati definiti dei tipi di dato (longText, enum, timestamp) che non corrispondono ai tipi di colonne esattamente supportati dal proprio database. Sarà compito di Laravel convertire questa indicazione di dato nella forma più adeguata per l’attuale database.
Se, per esempio, utilizzassimo per lo sviluppo locale un database SQLite, avremmo per la stessa migrazione:
$ php artisan migrate --pretend
INFO Running migrations.
2023_01_23_193847_create_posts_table ....................
⇂ create table "posts" ("id" integer not null primary key autoincrement, "created_at" datetime, "updated_at" datetime, "title" varchar not null, "content" text, "status" varchar check ("status" in ('draft', 'published', 'unpublished')) not null default 'draft', "published_at" datetime)
NOTA: Laravel mette a disposizione molti metodi dedicati per tipo di dato con cui definire una colonna, per un elenco completo e le varie opzioni disponibili per ogni tipo consultare la documentazione alla pagina.
Una particolarità del comando migrate:rollback è quella di poter indicare quante migrazioni “indietro” si vuole tornare, tramite l’opzione --step=<NUM>.
Primary key e foreign key in Laravel
Nella creazione di tabelle e colonne è possibile dover impostare primary key e foreign key.
Le migrazioni Laravel offrono due modi per creare la colonna che ospita una primary key:
tramite uno dei metodi “id” dedicati, come ad esempio $table->id() che crea una colonna di nome id impostandola come primaria
tramite il metodo primary, che aggiunge una chiava primaria alla colonna indicato $table->primary('my_id')
Anche la creazione di foreign key è resa semplice:
Schema::table('posts', function (Blueprint $table) {
// creo colonna nella tabella posts, senza chiave
$table->unsignedBigInteger('user_id');
// associo la nuova colonna alla colonna esistente nell'altra tabella
$table->foreign('user_id')->references('id')->on('users');
});
Modificare tabelle e colonne in Laravel
Le migrazioni seguono l’evoluzione dell’applicazione; per questo, oltre a creare nuove tabelle, è possibile modificare tabelle e colonne già esistenti.
NOTA: per poter modificare una colonna, è richiesto l’aggiunta del pacchetto doctrine/dbal alla propria applicazione tramite Composer. Questo pacchetto serve per creare la o le query di modifica della colonna in base allo stato attuale.
// ...
Schema::table('users', function (Blueprint $table) {
// cambia il numero di caratteri massimi della colonna title e la rende nullabile
// notare il change() usato alla fine della catena di impostazioni
$table->string('title', 50)->nullable()->change();
// rename di una colonna, from -> to
$table->renameColumn('status', 'publishing_status');
});
Nel momento in cui si cambia una colonna esistente è necessario aggiornare anche tutte le altre parti del codice dell’applicazione in cui quella colonna era utilizzata, per esempio nelle varie query viste nella lezione precedente. La creazione di una migrazione di modifica e le opportune modifiche al codice vanno viste come un tutt’uno, non come due step successivi. Sarà compito del sistema di deploy scelto occuparsi dell’effettivo ordine di avvio.
Eseguire rollback delle migrazioni in Laravel
Anche per rollback delle migrazioni è possibile usare il relativo comando Artisan migrate:rollback, eventualmente con l’opzione --pretend per vedere l’esatta query che verrà eseguita.
$ php artisan migrate:rollback --pretend
INFO Rolling back migrations.
2023_01_23_193847_create_posts_table ....................
⇂ drop table if exists "posts"
$ php artisan migrate:rollback
INFO Rolling back migrations.
2023_01_23_193847_create_posts_table .................... 4ms DONE
È importante non dimenticare che durante il rollback viene eseguito il codice fornito nel metodo down ed è, quindi, necessario fornire una contro-modifica coerente con quanto applicato dal metodo up. Supponendo di voler cambiare nome e tipo alla colonna title della nostra tabella posts
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->tinyText('title')->change();
$table->renameColumn('title', 'subject');
});
}
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->string('subject')->change();
$table->renameColumn('subject', 'title');
});
}
Per poter riportare il database nella situazione precedente è necessario trovare la corretta sequenza di azioni lato codice che Laravel può convertire in query SQL. In questo caso, nella down è necessario prima cambiare il tipo di colonna e poi cambiare nome, riportandolo a quello precedente.
Una sequenza concettualmente simile potrebbe non essere applicabile (ed è per questo che è sempre doveroso sia applicare che fare rollback delle migrazioni in fase di sviluppo.)
// questa sequenza di azioni nella down porta a un errore nel rollback
// non è possibile tornare allo stato precedente
$table->renameColumn('subject', 'title');
$table->string('title')->change();
4.3
Eloquent e Models
Con il termine ORM (Object Relational Mapper) si intende una tecnica di programmazione informatica che permette, nella pratica, di mettere in relazione un database relazionale e un oggetto/classe nel proprio codice.
In Laravel viene fornito l’ORM Eloquent e si basa sulla corrispondenza tra una tabella del database e una classe di tipo “Model”. Nello specifico, implementazione ORM di Eloquent è del tipo “Active Record”: una tabella sul database corrisponde ad una classe PHP e una singola riga della tabella corrisponde ad una istanza della classe.
L’utilizzo di Eloquent e dei Model semplifica molto l’interazione con il database, eliminando, in molti casi, la necessità di scrivere query dedicate.
Creare un model in Laravel
Il collegamento tra tabella e classe con Eloquent è realizzato utilizzando una convenzione sui rispettivi nomi. La classe del modello è indicata al singolare, la tabella collegata al plurale.
È, ovviamente, disponibile un comando Artisan per creare un nuovo modello, make:model, che supporta varie opzioni per creare anche altre classi collegate al model (controller, form, …). Quello che, sicuramente, ci interessa è creare un nuovo modello con l’annessa migrazione, per creare, quindi, automaticamente anche la nuova tabella (opzione -m).
$ php artisan make:model Post -m
INFO Model [app/Models/Post.php] created successfully.
INFO Migration [database/migrations/2023_01_24_211341_create_posts_table.php] created successfully.
// in app/Models/Post.php
class Post extends Model
{
use HasFactory;
}
// database/migrations/2023_01_24_181341_create_posts_table.php
// ...
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
// ...
Alla classe Post è, quindi, collegata e collegabile la tabella posts. Prima di eseguire la migrazione per creare la tabella, aggiungiamo qualche colonna d'esempio al metodo up della migrazione stessa. Non tocchiamo la classe App\Model\Post.
$table->tinyText('title');
$table->enum('status', ['draft', 'published', 'retired'])->default('draft')
$table->timestamp('published_at')->nullable();
Utilizzare un model
Senza ulteriori modifiche al nostro codice, in ogni punto della nostra applicazione Laravel, sarà possibile interagire con la classe Post nei modi opportuni per interagire, di fatto, con la tabella nel database.
// da qualche parte nella nostra app Laravel ...
$post = new App\Model\Post;
$post->title = "Another Post Bites the Dust";
$post->save();
$status = $post->status; // 'draft', il valore di default della colonna sul database
$id = $post->id; // generato automaticamente
$post->status = 'published';
$post->published_at = now();
$post->save()
$post->status = "foo"
$post->save() // genera una Illuminate\Database\QueryException
Notare in particolare che:
possiamo creare istanze della classe ($post) ed eseguire su di esse metodi della classe Model (->save()) che hanno effetto sul database, creando o modificando una riga
possiamo accedere alla colonne della riga tramite attributi dell’istanza che non abbiamo mai definito, ma che Eloquent crea dando loro gli stessi nomi della corrispettiva colonna (status, published_at)
al salvataggio della istanza su tabella, vengono accettati solo valori ammessi dalla colonna
NOTA: è possibile visualizzare gli attributi del proprio modello tramite il comando Artisan php artisan model:show <NOME_MODELLO>
Il nostro modello Post estende la classe astratta Model di Eloquent e da essa riceve molti(ssimi) metodi che ci permettono di interagire con i dati sul database senza dover scrivere alcuna riga di codice.
Oltre, quindi, ai metodi che operano sulla singola istanza/riga, da Model vengono ereditati metodi statici che operano sulla tabella collegata. Questo grazie all’ implementazione “Active Record” citata all’inizio.
// restituisce la riga della tabella `posts` con `id=10`
$anotherPost = Post::find(10);
// restituisce tutte le righe della tabella `posts`
$posts = Post::all();
// crea query tramite il builder e recupera le righe che corrispondono
$thisYearPosts = Post::where('status', 'published')
->where('published_at', '>=', '2022-12-31')
->get()
I vari metodi statici del model restituiscono istanze della classe quando servono recuperate singole righe (per esempio find) oppure istanze della classe Illuminate\Database\Eloquent\Collection nel caso di metodi che intendono recuperare array di righe.
Laravel mette a disposizione la classe Illuminate\Support\Collection come wrapper per interagire in modo agevole con gli array PHP. La specifica classe per Eloquent estende la classe di base con metodi espressamente dedicati per i model.
Convenzioni e funzionalità dei modelli Eloquent in Laravel
Per comodità, riportiamo le principali convenzioni di Eloquent riguardanti i model.
la classe ha il nome della “risorsa” al singolare (es. FavoritePost)
la tabella ha lo stesso nome al plurale e in “snake case” (es. favorite_posts)
la colonna chiave primaria si chiama id, è un intero con autoincrement
esistono le colonne created_at e updated_at di tipo “timestamp”
È possibile, quindi, creare modelli Eloquent che possono collegarsi a tabelle che non rispettano queste convenzioni, indicando nella classe del model opportune property protected.
// in app/Models/Post.php posso indicare cosa non segue la convezione dei Model
class Post extends Model
{
// il nome della tabella
protected $table = 'post_table';
// il nome della colonna primary key
protected $primaryKey = 'post_id';
// la colonna primary key non è auto-increment
public $incrementing = false;
// la colonna primary key non è di tipo intero
protected $keyType = 'string';
// ... cfr Illuminate\Database\Eloquent\Model per altre property
}
È anche possibile attivare particolari funzionalità in un Model, come ad esempio la “soft delete” o l’hidden di determinate colonne.
Con soft delete in Laravel si intende un’operazione che invece di rimuovere una riga da una tabella (hard delete), la contrassegna come non più disponibile. Attivare la soft delete su un Model Eloquent richiede uno sforzo minimo:
// nella `up` della migrazione va dichiarato che è una tabella "soft delete"
// viene in pratica aggiunta una colonna `deleted_at`
$table->softDeletes();
// nella classe del Model dichiaro che il model usa le soft delete
{
use SoftDeletes;
// ...
}
// nel codice dell'app, si continua a usare il metodo standard per rimuovere un record
$post = Post::find(44531);
$post->delete();
// che non comparirà nelle varie richieste
$activePosts = Post::all();
// ma sarà comunque recuperabile tramite metodi dedicati
$softDeletedPosts = Post::::onlyTrashed()->get();
La funzionalità di hidden delle colonne è invece utile nel caso in cui si voglia evitare che i valori di determinate colonne vengano inclusi nella istanza recuperata dal database. Le motivazioni per questa scelta sono le più disparate; l’esempio più immediato ci viene dal Model presente nelle classi fornite direttamente da Laravel quando si crea una nuova applicazione con Composer.
class User extends Model
{
protected $hidden = [
'password',
];
// ...
}
La tabella degli utenti include informazioni sui singoli utenti registati, il model può essere recuperato tali informazioni e farle arrivare ad una view user.profile, ma non vogliamo di certo che l’attuale password dell’utente arrivi, anche se cifrata, fino a una view.
L’attributo $hidden permette di elencare le colonne che, seppure presenti e valorizzate sul database, non verranno incluse come property dell'istanza del model. Ovviamente, sarà possibile accedervi tramite opportuni metodi della classe Model
4.4
Eloquent e l’integrazione con applicazioni Laravel
L’integrazione dell’ORM Eloquent in una applicazione Laravel non si limita alla possibilità di scrivere query verso il database sfruttando la semplicità di una classe che rappresenta la tabella.
Sappiamo che in una applicazione Laravel è possibile usare la dependency injection per includere e accedere semplicemente ad altre parti dell’applicazione. Grazie alla dependency injection i Model Eloquent possono essere integrati in punti chiave dell’applicazione Laravel, quali, per esempio, router, controller e view.
Laravel è, infatti, un framework MVC (Model-View-Controller) e, in questa integrazione tra le varie parti del paradigma MVC, trova uno dei suoi maggiori punti di forza.
Binding Route-Model in Laravel
Uno delle necessità tipiche per cui può risultare necessario sfruttare i meccanismi delle dependency injection per poter accedere alle funzioni offerte da un model, è quello di recuperare dati dalla tabella del database collegata al model.
Riprendiamo un esempio presentato durante l’esposizione dei controller: supponiamo di avere definito una rotta per mostrare il contenuto di un post. La rotta risponde alla URI in cui è indicato l’id del post, come https://www.example.com/posts/23861.
use App\Http\Controllers\PostController;
Route::get('/posts/{id}', [PostController::class, 'show']);
In un’ applicazione Laravel in cui l’accesso al dato è gestito da un model, possiamo riscrivere la stessa rotta nel modo seguente.
use App\Models\Post;
Route::get('/posts/{post}', function (Post $post) {
return view('post.show', $post);
});
Viene realizzato, in questo modo, il cosiddetto implicit binding. Vengono, in pratica, collegate tra loro la variabile $user e la porzione variabile della URI {post}, assumendo implicitamente che il valore che arriva dalla URI attraverso {post} sia il valore della chiave primaria del model indicato. In pratica, la variabile $user con cui si interagisce all’interno della funzione è già una istanza ricavata eseguendo Post::find() con il valore di {post} passato come argomento.
Questa integrazione Model <> Route offre diversi vantaggi e funzionalità aggiuntive interessanti.
È, per esempio, possibile indicare più model (vedremo tra poco come mettere in relazione diversi model), semplicemente facendo corrispondere il model alla porzione variabile di URI con le stesse convenzioni su nomi e ordine.
use App\Models\Author;
use App\Models\Book;
Route::get('/authors/{author}/books/{book}', function (Author $author, Book $book) {
return $book;
});
Sempre in tema di convenzioni, è anche possibile indicare una colonna diversa da id per l'implicit binding, a patto che, ovviamente, tale colonna sia una chiave primaria (ogni riga ha un valore diverso).
use App\Models\Post;
// nel mondo del web uno slug è un identificativo univoco di una pagina,
// è parte della URI completo e di solito indica il contenuto della pagina stessa
Route::get('/posts/{post:slug}', function (Post $post) {
return $post;
});
Model e Controller in Laravel
Anche i controller possono sfruttare meccanismi simili per avere a costo zero una connessione semplificata con il database per mezzo di un model Eloquent.
Laravel permette di creare nuovi controller di tipo resource indicando, sin da subito, a quale model collegarli.
$ php artisan make:controller PostController --model=Post
INFO Controller [app/Http/Controllers/PostController.php] created successfully.
Rispetto all’analogo controller di risorsa mostrato nella lezione sui controller, quello creato indicando il model associato avrà nei vari parametri indicata l’istanza della classe App\Models\Post e non l’intero del suo id
public function show(Post $post)
{
//
}
Le possibilità offerte per collegare route/controller al model sono, quindi, molte.
View e paginazione in Laravel
Nel momento in cui le righe delle nostre tabelle cominciano a diventare molte, non è più sostenibile restituire tutte le righe in una singola chiamata o pagina.
Risulta, quindi, necessario avere la possibilità di dividere i possibili risultati su più pagine che siano collegate in qualche modo tra loro.
Il query builder di Laravel - e quindi anche i modelli Eloquent - offre diversi metodi per aggiungere in modo semplice e quasi trasparente la paginazione a una collection di risultati. Vediamo un esempio per la nostra ipotetica rotta che restituisce l’elenco di tutti i post del nostro blog.
// in routes/web.php
use App\Models\Post;
const RESULTS_PER_PAGE = 10;
Route::get('/posts', function (){
return view('post.index', [
'posts' => Post::paginate(RESULTS_PER_PAGE)
]);
});
{{- resources/views/post/index.blade.php -}}
<div class="container">
<ul>
@foreach ($posts as $post)
<li>
{{ $post->title }} - {{$post->status}}
</li>
@endforeach
</ul>
</div>
{{ $posts->total() }} total posts
{{ $posts->links() }}
Il metodo statico paginate necessita di un solo parametro, che indica il numero di elementi da mostrare in ogni singola pagina di risultati.
A fronte di ciò, offre allo sviluppatore web una seria di comodità, sia dal punto di vista della automazione di alcuni comportamenti - Laravel aggiunge in modo automatico la gestione dell’argomento page in query string per la rotta indicata - sia dal punto di vista di metodi aggiuntivi invocabili sull’array dei risultati - in questo caso, avremo a disposizione per $post nella view non solo le comodità della classe Illuminate\Database\Eloquent\Collection, ma anche di Illuminate\Pagination\Paginator, come, ad esempio, la generazione automatica dei link per spostarsi da una pagina all’altra
4.5
Eloquent e le relazioni tra tabelle in Laravel
Nella introduzione ai database relazionali abbiamo visto che è possibile (e consigliato) creare relazioni tra varie tabelle nel momento in cui esiste una relazione tra le entità che sono rappresentate nelle tabelle. Una tabella movies potrà collegarsi sia alla tabella actors che alla tabella directors.
Esistono diversi tipi di relazioni possibili in un database, ognuna indicata per rappresentare una determinata situazione o modellazione (Clint Eastwood è sia attore che regista … sarà presente in entrambe le tabelle? In entrambe indicheremo giorno e luogo di nascita e in entrambe aggiorneremo eventuali informazioni più recenti?).
Con Laravel e Eloquent è possibile gestire tali relazioni in modo semplice, sia per quanto riguarda la creazione delle tabelle e delle relative foreign key, sia per quanto riguarda la gestione tramite i Model.
Convenzioni e utilizzo base in Laravel
Supponiamo di avere creato nella nostra applicazione due modelli (Author e Book, per ora vuoti) e le relative migrazioni per indicare le colonne delle rispettive tabelle authors e books.
// nel metodo up() della migrazione che ha aggiunto la tabella "authors"
Schema::create('authors', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->text('first_name');
$table->text('last_name');
});
// nel metodo up() della migrazione che ha aggiunto la tabella "books"
Schema::create('books', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('title')
});
Per creare una relazione tra le due tabelle (libro B dell’autore A) e fare in modo che Eloquent possa automaticamente riconoscere questa relazione, è sufficiente seguire alcune convenzioni nella creazione della foreign key che associa autori a libri.
// in Schema::create('books' ..
$table->foreignId('author_id');
$table->foreign('author_id')->references('id')->on('authors');
La colonna con la foreign key ha come nome il nome del modello al singolare e minuscolo (author nel nostro caso) seguito dal suffisso _id.
Una volta creata la colonna per la foreign key, per completare la connessione tra i due model serve solo indicare nel model App\Model\Book la relazione che lo lega al model App\Model\Author. Nel nostro caso, un libro appartiene a un unico singolo autore.
class Book extends Model
{
public function author()
{
return $this->belongsTo(Author::class);
}
}
In questo modo è possibile scrivere una rotta come la seguente nella quale sarà possibile, a partire dal libro recuperato dal database, estrarre le informazioni sul suo autore, grazie alla property dinamica aggiunta dal Eloquent.
Route::get('/books/{book}', function (Book $book){
// property dinamica author aggiunta da Eloquent
$author = $book->author;
// posso accedere a tutte le property di Author
return "$book->title - $author->first_name $author->last_name";
});
Come già visto per i singoli model, è sufficiente seguire le convenzioni previste da Eloquent per nomi di tabelle e colonne e indicare nel model il tipo di relazione che lega le due entità per avere la possibilità di interagire con strutture dati collegate tra loro.
Laravel ed Eloquent si occuperanno di costruire ed eseguire le effettive query necessarie a recuperare l’autore del libro che stiamo chiedendo di visualizzare e restituiranno un’ istanza del model collegato.
Esistono diversi tipi di relazione che possono collegare una tabella a un’altra tramite foreign key. Le due più importanti e frequenti sono One to Many e Many to Many.
Alla base di ogni relazione che è possibile utilizzare con Eloquent vi sono due condizioni particolari:
l’aver rispettato le convenzioni sui nomi di tabelle e colonne, in particolare la necessità di usare <altra_tabella>_id per le colonne foreign key
l’aver dichiarato i metodi “di relazione” nelle classi del model
Relazione One to Many in Laravel
Esiste una relazione one-to-many tra due tabelle A e B quando una riga della tabella A può essere legata con molte righe della tabella B, ma ogni riga della tabella B è legata a solo una riga della tabella A.
È il caso, questo, della relazione tra Autor (A) e Book (B) che abbiamo parzialmente introdotto poco fa. Nell’ultimo esempio, abbiamo esplicitato che un’ istanza/riga di Book è collegata a una sola istanza/riga di Author tramite il metodo belongsTo.
Per poter completare una relazione one-to-many tra due modelli Eloquent è, infatti, necessario che in entrambi i model sia definito il tipo di relazione.
Nel nostro caso, quindi, possiamo indicare che ad una singola istanza di Author possono corrispondere più istanze di Book nel seguente modo:
class Author extends Model
{
public function books()
{
return $this->hasMany(Book::class);
}
}
Sarà, quindi, possibile accedere, partendo da un autore recuperato dal database, all’elenco dei suoi libri nei modi previsti dalle varie convenzioni di Eloquent
Route::get('/author/{author}', function (Author $author){
return view('author.show', [
'author' => $author
]);
});
{{-- resources/views/author/show.blade.php --}}
<div>
{{ $author->first_name }} {{ $author->last_name }}
<ul>
@foreach($author->books as $book)
<li>{{ $book->title }}</li>
@endforeach
</ul>
</div>
Relazioni e query efficienti (lazy-load vs eager) in Laravel
Laravel ed Eloquent permettono di semplificare alcuni passaggi dello sviluppo web, ma è comunque necessario sapere come il framework si comporta in determinate situazioni.
Prendiamo, ad esempio, il caso in cui, con la relazione one-to-many impostata in precedenza, volessimo realizzare una pagina in cui sono elencati tutti i libri con il rispettivo autore.
use App\Models\Book;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
Route::get('/books', function (){
// stampa nel file di log le query effettivamente fatte da Eloquent
// nel creare la riposta per questa richiesta, utile per il debug
DB::listen(function ($query) {
logger($query->sql);
});
return view('book.index', [
'books' => Book::all()
]);
});
<div class="container">
<ul>
@foreach ($posts as $post)
<li>
<a href="/post/{{$post->id}}">{{ $post->title }}</a> by <a
href="/author/{{$post->author->id}}">{{$post->author->first_name}}</a>
</li>
@endforeach
</ul>
</div>
Leggendo il file storage/logs/laravel.log dopo aver aperto la rotta, scopriremo che, per ognuno dei libri salvati sul nostro database, Laravel avrà effettuato una query per estrarre le informazioni del relativo autore.
local.DEBUG: select * from "posts"
local.DEBUG: select * from "authors" where "authors"."id" = ? limit 1
local.DEBUG: select * from "authors" where "authors"."id" = ? limit 1
local.DEBUG: select * from "authors" where "authors"."id" = ? limit 1
...
Nel loro funzionamento normale, infatti, Laravel ed Eloquent applicheranno di default una modalità di recupero delle informazioni definita lazy-loading. Ovvero, Laravel non eseguirà le query legate alle relazioni a meno che non servano.
Il che, di solito, è un bene, perché evita che vengano eseguite query inutili nel caso in cui si volesse avere, ad esempio, solo la lista di tutti i libri presenti nel database per utilizzare i rispettivi titoli.
Nel caso specifico della rotta e della view create, stiamo accedendo anche alle informazioni della tabella authors collegata, quindi Laravel/Eloquent dovrà effettuare le query necessarie nel momento in cui serve il dato (cioè quanto viene chiesto nome e cognome dell’autore da inserire nella view). Ciò scatena l’esecuzione di tutte le altre query necessarie a recuperare il dato mancante.
È opportuno essere molto attenti, nel momento in cui si usa Eloquent e le relazioni tra tabelle, a cosa faremo con l’info che stiamo chiedendo, poiché potrebbero esserci modi più efficienti di recuperare quelle informazioni.
Nel caso specifico dell’esempio, potrebbe essere più efficiente seguire un comportamento diverso da quello lazy-loading.
Sapendo già che per quella rotta e quella view useremo sia le informazioni sul libro che quelle sull’autore collegato, è possibile usare la modalità eager per recuperare subito dal database anche la lista degli autori.
La modalità eager va, quindi, espressamente richiesta, in particolare indicando quale tabella collegata andare a recuperare subito.
Route::get('/books', function (){
DB::listen(function ($query) {
logger($query->sql);
});
return view('book.index', [
// indica al query builder che useremo anche author, quindi può usare la modalità eager
'books' => Book::with('author')->get()
]);
});
Con questa modifica, il builder di query di Laraver riceverà l’indicazione di non usare il lazy-loading per la relazione indicata. Nel log vedremo, quindi, eseguire solo due query a fronte delle stessa richiesta.
local.DEBUG: select * from "posts"
local.DEBUG: select * from "authors" where "authors"."id" in (1, 2, 3, 4, 5, 6, [...])
Relazione Many to Many in Laravel
Altro tipo di relazione particolarmente rilevante durante lo sviluppo software è quella many-to-many, poiché possibile in molte delle entità che il nostro software potrebbe dover rappresentare e manipolare.
Nel modello autore-libro considerato prima, abbiamo dato per assunto che un libro potesse essere scritto da un solo autore. Nella realtà è, invece, possibile che uno stesso libro sia scritto da più autori. In questo caso, abbiamo una relazione many-to-many, poiché una riga della tabella A (Author) può essere legata con molte righe della tabella B (Book) e al tempo stesso una riga della tabella B può essere legata a molte righe della tabella A.
Modelli relazionali
Sono molte le entità che possono essere collegate con una relazione many-to-many: Ordine-Prodotto, Utente-Ruolo, Post-Category, …
Le relazioni many-to-many sono tipicamente implementate in un RDBMS sfruttando una terza tabella, indicata come “tabella associativa” (o join table o anche cross-reference-table). La tabella associativa permette di separare la relazione many-to-many in due distinte relazioni one-to-many
Tabella associativa
L’implementazione di una relazione many-to-many in Eloquent richiede la presenza di tre tabelle. Continuando nell’esempio di autori e libri, necessiteremmo, quindi, delle tabelle books, authors e della tabella associativa author_book.
Per convenzione, Eloquent richiede che:
il nome della tabella associativa sia formato dal nome dei rispettivi modelli al singolare, uniti da un _ e in ordine alfabetico
nella tabella associativa siano presenti le colonne per le due foreign key che puntano a ciascuna tabella, nel formato già visto per la relazione one-to-many (quindi author_id e book_id)
A differenza di quanto fatto nel precedente caso per l’associazione one-to-many tra autore e libro, non saranno presenti foreign key nelle due tabelle books e authors, poiché non vi è una relazione diretta tra le due.
Potremmo, quindi, riassumere visivamente una struttura base come la seguente:
author:
- id = int
- name = string
book:
- id = int
- title = string
author_book:
- id = int
- author_id = foreign_key(author.id)
- book_id = foreign_key(book.id)
Una volta create tabelle e colonne con i dovuti nomi tramite le migrazioni, sarà necessario indicare le relazioni Eloquent nei relativi model. In questo caso, si tratterà di due relazioni belongsToMany per entrambi i model.
class Author extends Model
{
public function books()
{
return $this->belongsToMany(Books::class);
}
}
class Book extends Model
{
public function authors()
{
return $this->belongsToMany(Author::class);
}
}
Sarà, quindi, possibile utilizzare le property dinamiche messe a disposizione da Laravel per accedere all’uno e all’altro model, sulle quali poi applicare ogni altra possibilità messa a disposizione dal sistema di query di Laravel.
use App\Models\Author;
$user = Author::find(1);
foreach ($author->books as $role) {
//
}
use App\Models\Book;
$book = Book::find(100);
foreach ($book->authors as $author) {
//
}
4.6
Eloquent e Mass Assignment in Laravel
I vantaggi di usare i Model di Eloquent in una applicazione Laravel sono molteplici. Rispettando alcune buone regole sulla nomenclatura di tabelle e colonne del database, è possibile accedere direttamente dal proprio codice a tali dati senza dover scrivere codice necessario a convertire elementi del database in classi e relative property.
Nelle lezioni precedenti abbiamo, principalmente, visto quanto offerto dal framework per la lettura di informazioni dal database, ma (per?) un’ applicazione avr(em)ò sicuramente necessità di scrivere anche nuove righe a fronte di un input dell’utente.
Eloquent mette a disposizione metodi specifici per gestire in modo corretto, sicuro e veloce la scrittura di info tramite Model Eloquent.
Model e salvataggio di informazioni in Laravel
Abbiamo visto come i Model eloquent permettono di usare un'istanza del model stesso per inserire nuovi dati o modificare dati esistenti usando le property dinamiche legate alle colonne su database.
use App\Model\Author;
use App\Model\Book;
$author = $author = Author::where('name', 'Randal Munroe')->first();
$book = new Book;
$book->title = "What If?"
$book->author_id = $author->id;
// ...
$book->save();
Anche se il metodo save inserisce automaticamente data di creazione e modifica, nel caso di model con molte property potrebbe diventare scomodo impostare una a una le varie property dinamiche alla creazione di una nuova riga.
È disponibile, in alternativa, il metodo create che accetta un array chiave/valore di tutte le property da cambiare e i rispettivi valori.
use App\Model\Book;
// ...
Book::create([
'title' => 'What If?',
'author_id' => $author->id,
'isbn_13' => '978-0544272996',
'published_at' => '2014-09-02',
// ..
]);
Esiste un metodo analogo per l’aggiornamento di record già presenti su database, update.
use App\Model\Book;
// ...
Book::where('isbn_13', '978-0544272996')->update([
'price' => '27,50/EUR',
'available' => true,
]);
L’utilizzo di questi due metodi presenta vantaggi e svantaggi. Da un lato, infatti, le azioni di creazione di un nuovo record e di update sono più chiaramente leggibili e “compatte” rispetto all’assegnare singolarmente valori alle singole property. Inoltre, non è necessario chiamare esplicitamente il metodo save per far persistere effettivamente i dati sul database (lato “codice” abbiamo un’ istanza che rappresenta un record su database).
Dall’altro, la possibilità di assegnare un valore a tutte le colonne del database pone interessanti questioni di sicurezza. Se provassimo ad eseguire la create nell’esempio precedente in una versione recente di Laravel, riceveremmo un errore.
Illuminate\Database\Eloquent\MassAssignmentException Add [name] to fillable property to allow mass assignment on [App\Models\Author].
Vulnerabilità “mass assignment” in Laravel
Si parla di vulnerabilità mass assignment in Laravel nel caso in cui le richieste HTTP che una applicazione web riceve vengono mappate automaticamente in variabili o oggetti all’interno del proprio codice. Questo legame tra richiesta HTTP e dati interni all’applicazione potrebbe causare problemi o danni.
Facciamo un esempio pratico e ipotetico. Nella nostra applicazione Laravel abbiamo la tabella users con le colonne email, password e role che indica se l’utente è visualizzatore, editor o admin dell’applicazione stessa. La tabella è connessa a un Model che, a sua volta, è connesso a un Controller che, a sua volta, riceve richieste di signup da un Form. Per creare un nuovo utente, esiste un form html e una metodo store in un controller.
<form>
<input name="password" type="text">
<input name="email" text="text">
<input type="submit">
</form>
use App\Model\User;
public function store(UserFormRequest $request)
{
$user = User::create($request->validated());
}
In questa situazione, i dati che arrivano dal form potrebbero essere convertiti automaticamente dal form (html) all’array da passare alla create, finendo, quindi, su database. Se il mapping fosse automatico e basato sulla corrispondenza dei vari nomi delle entità, un utente mavolo potrebbe forzare il form lato HTML, aggiungere un role ed essere automaticamente aggiunto con il role di admin.
Questo tipo di vulnerabilità è dovuta a una serie di automatismi legati a convenzioni sui nomi che causano un legame diretto tra dato sul database e dato nel browser dell’utente, in gran parte introdotto per rendere più semplice la vita degli sviluppatori.
In un applicativo Laravel è possibile applicare metodi di protezione basati su allow-list o su block-list, implementabili a livello di model tramite opportune property: $fillable e $guarded.
Property fillable e property guarded in Laravel
I meccanismi di allow-list e block-list sono regolati in un Model Eloquent tramite la variabile $fillable e la variabile $guarded.
Poiché ogni modello Eloquent creato nella nostra applicazione estende la classe Illuminate\Database\Eloquent\Model, se non specificato altrimenti ogni modello assumerà per queste due variabili i seguenti valori (nessuna fillable, tutte guarded):
$fillable = [];
$guarded = ['*'];
Nei due array php è possibile indicare i nomi delle colonne della tabella associata con effetto opposto. Le colonne elencate in $fillable potranno essere usate come chiavi nell’array passato a una create, le colonne elencate in $guarded verranno ignorate, anche se passate alla create.
Se, quindi, volessimo proteggere la nostra ipotetica tabella users con un metodo allow-list potremmo modificare il nostro model indicando:
class User extends Model
{
protected $fillable= ['email', 'password'];
// ...
}
Se, invece, preferiamo l’approccio black-list, allora va indicata solo la colonna da non scrivere:
class User extends Model
{
protected $guarded = ['role'];
// ...
}
Le property $fillable e $guarded intervengono, ovviamente, come misura di sicurezza per il mass assignment; sarà comunque possibile usare il model e i suoi altri meccanismi per scrivere anche la colonna role nella gestione di quelle richieste che sono autorizzate a modificare il suo valore.
NOTA: è possibile scegliere solo uno dei due approcci, non è possibile cambiare valore per sia per $fillable che per $guarded insieme in un model.
5
Approfondimenti su Laravel
5.1
Form e validazione delle request in Laravel
In questa lezione andremo ad approfondire come inviare dati alla nostra applicazione e come assicurarci che tali dati inviati siano corretti. Nelle applicazioni web, l’invio dei dati è abitualmente realizzato tramite form HTML: i dati inseriti dall’utente nel browser vengono inviati al server e memorizzati sul database.
Laravel rende estremamente semplice collegare i campi di un form HTML con una determinata tabella, seguendo una serie di accortezze legate alla implementazione del modello MVC (model/view/controller).
Particolarmente interessante e funzionale in Laravel è la validazione dei dati in ingresso verso la propria applicazione. Oltre, infatti, a un metodo validate che è possibile applicare a tutte le request che arrivano ai controller dell’applicazione, Laravel mette a disposizione molte regole di validazione che è possibile applicare a tali richieste: presenza di un campo, lunghezza, espressioni regolari.
Form e Input in Laravel
Laravel consente di creare con semplicità pagine e rotte che ci permettono di salvare a database i dati inseriti dall’utente nel browser. Per fare ciò, nel modo più semplice possibile andremo a riprendere quanto già introdotto nelle lezioni precedenti, in particolare Model, View e Controller, proseguendo nel nostro esempio di applicazione che gestisce libri e a autori.
Supponiamo, quindi, di avere disponibile il model App\Model\Book e la sua relativa tabella con migrazione. Per questo esempio considereremo che ogni libro deve essere caratterizzato da un titolo (con una lunghezza massima), un estratto (anch’esso con lunghezza massima, ma non richiesto) e il codice ISBN (che deve essere esattamente di 17 caratteri, deve essere presente e unico per ogni riga della tabella).
// in database/migrations/2023_01_01_create_books_table.php
// ...
public function up()
{
Schema::create('books', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->string('title', 200);
$table->string('excerpt', 500)->nullable();
$table->string('isbn', 17)->unique();
});
}
// ...
// app/Model/Book.php
class Book extends Model
{
protected $fillable = [
'title',
'isbn',
'excerpt'
];
}
Vogliamo permettere agli utenti della nostra applicazione di aggiungere un nuovo libro. Per fare ciò, creeremo una pagina con un form HTML per inserire i dati. Ci servirà, quindi, una view blade per il form HTML e una rotta per accedere a tale form. Per organizzare meglio il tutto, considereremo anche la presenza di un controller BookController.
// in routes/web.php
Route::get('/books/create', [BookController::class, 'create']);
Route::post('/books', [BookController::class, 'store']);
// ...
// app/Http/Controllers/BookController.php
class BookController extends Controller
{
public function create()
{
return view('book.create');
}
public function store()
{
// TODO
}
// ...
}
{{-- resources/views/book/create.blade.php --}}
<div>
<h1>Create a Book</h1>
<form action="/books" method="POST">
@csrf
<label for="title">Title</label>
<input type="text" name="title" id="title">
<label for="isbn">ISBN</label>
<input type="text" name="isbn" id="isbn">
<label for="excerpt">Excerpt</label>
<input type="text" name="excerpt" id="excerpt">
<button type="submit">SUBMIT</button>
</form>
</div>
Qualche dettaglio e annotazione:
aprendo la URI /books/create sulla nostra app verrà chiamato il metodo create del BookController
tale metodo restituisce la view collegata
nella view abbiamo inserito un form HTML con i vari input e il pulsante SUBMIT
gli attributi HTML name e id dei singoli input hanno lo stesso nome delle colonne della tabella `books``
nel momento in cui si fa clic sul pulsante del form, viene mandato il contenuto del form stesso tramite una richiesta POST all’endpoint /books
abbiamo anche aggiunto la rotta che gestisce questa richiesta POST, ma non ne abbiamo ancora implementato il comportamento
nel template blade è presente la direttiva @csrf, necessaria ad aggiungere un campo di sicurezza al form (senza di questo laravel rifiuterà la richiesta di invio dati come non valida)
nel template blade abbiamo volutamente omesso di indicare quali degli input è required perché vogliamo che sia la nostra applicazione a gestire correttamente la validazione (che vedremo poco oltre nel dettaglio)
Ciò che vogliamo è che, una volta compilati gli input del form e fatto clic su SUBMIT, venga creata nel nostro database una nuova riga per un nuovo libro con i dati inseriti.
Dobbiamo, quindi, agire e implementare questo salvataggio nel metodo create del controller. All’interno di tale metodo, infatti, abbiamo a disposizione i dati inviati dalla richiesta POST, dovremo semplicemente farli salvare sul database.
// in app/Http/Controllers/BookController.php
public function store(Request $request)
{
$attributes = $request->all()
Book::create($attributes);
}
Possiamo usare il metodo $request->all() per recuperare tutti gli input collegati alla request gestita e sfruttare il mass assignment del metodo create del Model per salvare a database. Ricordiamo, infatti, che abbiamo usato per i campi input del form gli stessi nomi e id delle colonne della tabella e la chiamata al metodo $request->all() restituirà qualcosa tipo il seguente array associativo.
[
'_token' => 'nAfaKAvaEbC6SExSzFSoFAp2JMu0l2hcX1jCj2nj',
'title' => 'A Book Title',
'isbn' => 'Some ISBN',
'excerpt' => 'Some excerpt from form input, with more details',
]
NOTA per maggiori dettagli sul funzionamento del metodo create di un Model e sulle implicazione di sicurezza di questo metodo fare riferimento alla lezione precedente sul mass assignment di Eloquent
Vale la pena notare che abbiamo implementato nel metodo store del controller solo il salvataggio a database tramite il model. Inviando il form, quindi, il nostro browser invierà una richiesta POST /books e farà il rendering della risposta che, in questo caso, è vuota (avremo, quindi, una pagina bianca). In un’ applicazione completa dovremmo gestire anche la risposta da inviare al browser oltre all’effettivo salvataggio dell’informazione.
In questo momento, però, ci interessa di più approfondire due aspetti legati alla correttezza del dato inviato. Il nostro codice, infatti, non sta applicando alcuna verifica sui dati inviati (title, isbn e excerp possono contenere qualsiasi valore scritto nel browser) e, quindi, l’unico check è effettuato lato database.
Validazione dell’input in Laravel
Laravel permette di convalidare i dati in ingresso alla propria applicazione in diversi modi. Il più comune è tramite il metodo validate che è possibile eseguire su ogni richiesta HTTP in arrivo.
Tramite tale metodo è possibile specificare una o più rule di validazione messe a disposizione da Laravel stesso. È, ovviamente, possibile creare delle proprie rule di validazione che si vanno ad aggiungere a quelle presenti di default nel framework.
Nel nostro caso d’esempio, sappiamo che le tre colonne sulla tabella hanno una lunghezza massima (cfr la migrazione riportata all’inizio). Possiamo, quindi, aggiungere delle regole di validazione che controllano, innanzitutto, la lunghezza dei vari campi inviati dal form.
// in app/Http/Controllers/BookController.php
public function store(Request $request)
{
$attributes = request()->validate([
'title' => 'max:200',
'isbn' => 'size:17',
'excerpt' => 'max:500'
]);
Book::create($attributes);
}
Il metodo validate opera sulla request e, in caso di successo, restituisce l’array degli input arrivati con la request. Nel caso in cui la richiesta sia valida, il codice continuerà a funzionare senza interruzioni. Le regole di validazione sono passate come argomento al metodo stesso sotto forma di array associativo, in cui la chiave è il nome del singolo input da validare e il valore è una delle regole di validazione.
Nel nostro caso d’esempio, abbiamo scelto le regole di validazione max e size che indicano la dimensione richiesta per ciascuno degli input: max:200 verificherà che la lunghezza dell’input non ecceda i 200 caratteri, size:17 invece che la lunghezza sia esattamente 17 caratteri.
Ci sono molte regole di validazione fornite direttamente da Laravel, per l’elenco completo rimandiamo alla documentazione ufficiale.
Volendo rendere la nostra validazione più efficace, dovremo, però, indicare più regole per alcuni input. Dalla definizione delle colonne della nostra tabella sappiamo che il campo title deve essere presente e che il campo isbn deve essere presente e deve essere unico per ogni riga del database. Possiamo, quindi, migliorare la nostra validazione nel modo seguente:
// in app/Http/Controllers/BookController.php
public function store(Request $request)
{
$attributes = $request->validate([
'title' => ['required', 'max:200'],
'isbn' => ['required', 'unique:books', 'size:17'],
'excerpt' => 'max:500'
]);
Book::create($attributes);
}
Possiamo usare le regole di validazione required e unique per indicare che:
un input required deve essere presente e non può essere null o empty
un input unique verifica che il valore passato non sia già presente nella tabella indicata (ovviamente con le usuali convenzioni di Laravel basate sulla corrispondenza per nome tra input e colonna da controllare)
Nel caso in cui la request non soddisfi le regole di validazione indicate, all’invio del form (POST /books) il metodo validate crea una opportuna risposta HTTP che fa redirect verso la precedente pagina (nel nostro esempio una 302 con header Location: /books/create).
Con tale redirect, Laravel ci offre la possibilità di mostrare quali errori di validazione si sono verificati per permettere all’utente di inserire dati corretti. Per fare ciò dobbiamo, però, brevemente introdurre i concetti di sessione e flash.
Sessioni e flash in Laravel
Sappiamo che il protocollo HTTP è un protocollo stateless, ossia che ogni richiesta è indipendente e senza memoria delle altre. Ciò non impedisce, però, che tra client e server sia possibile definire una determinata sessione sfruttando particolari meccanismi.
Nello specifico, un server è in grado di riconoscere le chiamate che provengono da uno specifico browser utilizzando i cookie. Alla prima richiesta effettuata dal browser, il server include nella risposta un cookie, creato a partire da una sessione creata per quel singolo browser e memorizzata sul server. Ad ogni richiesta successiva, il browser includerà tale cookie per indicare che la richiesta arriva da lui, permettendo, quindi, al server di sapere chi lo sta chiamando e come comportarsi. Su questa gestione dei cookie si basa ogni meccanismo che permetta di essere riconosciuti durante la navigazione su un sito, incluso, banalmente, il sapere che si è effettuato login.
Per il nostro caso sulla validazione di un input, quindi, l’applicazione Laravel è in grado di distinguere le richieste che provengono da uno stesso browser, poiché quel browser ha instaurato una sessione con l’applicazione stessa.
Un flash (o flash data) è un dato che viene salvato nella sessione a cui è associato e che sarà reso disponibile solo nella richiesta HTTP immediatamente successiva. È un meccanismo offerto da molti framework web, incluso ovviamente Laravel, pensato principalmente per far arrivare al browser messaggi di stato immediati, come ad esempio quelli degli errori di una validazione. Per il nostro caso sulla validazione di un input, è direttamente il metodo validate a creare il flash e ad aggiungerlo alla sessione. È comunque possibile aggiungere flash personalizzati: $request->session()->flash('status', 'Task was successful!');.
Ricapitolando, nel nostro caso:
un browser richiede per la prima volta la pagina con il form GET /books/create al nostro server
il server crea una nuova sessione per quel browser, prepara la pagina HTML e restituisce al browser il contenuto della pagina e il cookie di sessione
il browser renderizza la pagina con il form, l’utente riempie il form e fa clic sul pulsante
il browser invia una richiesta POST /books con i dati inseriti dall’utente e con il cookie di sessione (per dire “sono sempre io”)
il server riceve la richiesta, vede il cookie di sessione ricevuto, controlla se ci sono flash per quella sessione (nessuno per ora)
effettua la validazione, trova un errore, aggiunge il flash alla sessione collegata, restituisce una redirect a `/books/create``
a fronte della redirect il browser effettua una nuova richiesta a GET /books/create, sempre inviando il cookie di sessione
il server elabora la richiesta, stavolta trova un flash associato, lo aggiunge alla richiesta
il server prepara la pagina HTML aggiungendo i dati presenti nel flash durante tutte le fasi della elaborazione
Errori di validazione e ripopolazione del form in Laravel
Come possiamo, quindi, utilizzare sessioni e flash, nel nostro caso del form, per l’inserimento di un nuovo libro nel database? Tramite la variabile $errors disponibile in tutte le view del middleware web. Il meccanismo dei flash e la validate faranno in modo che in questa variabile siano presenti gli errori di validazione riscontrati.
Possiamo, quindi, modificare la nostra view nel modo seguente:
<div>
<h1>Create a Book</h1>
@if ($errors->any())
<div>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="/books" method="POST">
{{-- non modificato --}}
</form>
</div>
In questo modo, in caso di errori, verrà mostrato l’elenco dei messaggi restituiti dal metodo validate (per esempio The title field is required oppure The isbn has already been taken). Sarà possibile gestire lato view come mostrare nello specifico tali errori.
Oltre agli errori di validazione, nel flash vengono salvati anche gli input forniti nel submit non riuscito. Tali input sono disponibili all’interno della request stessa tramite il metodo old e più semplicemente nei template Blade tramite l’helper old.
In questo modo, possiamo fare in modo che la nostra view faccia il pre-fill degli input già inseriti nel precedente invio non riuscito.
<label for="title">Title</label>
<input type="text" name="title" id="title" value="{{ old('title') }}">
Classi form request in Laravel
Per scenari di validazione più complessi è possibile creare una classe dedicata alla validazione del form. Tali classi estendono la classe Illuminate\Foundation\Http\FormRequest ed implementano i metodi rules per le regole di validazione e authorize per determinare se la richiesta arriva da un utente autorizzato ad eseguirla.
Nel caso del nostro form, per aggiungere un libro avremmo, quindi, potuto creare una form request tramite l’opportuno comando Artisan
php artisan make:request StoreBookRequest
// app/Http/Requests/StoreBookRequest.php
class StoreBookRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => ['required', 'max:200'],
'isbn' => ['required', 'size:17', 'unique:books'],
'excerpt' => ''
];
}
}
e regole di validazione possono essere dichiarate nello stesso modo in cui si passavano al metodo validate, ossia un array associativo con i nomi degli input da validare come chiavi e l’array delle regole attive per ogni input.
La nuova classe StoreBookRequest può essere utilizzata nel controller tramite dependency injection. In particolare, la sua istanza è considerabile come una normale istanza di Request, con la specificità di aver gestito autonomamente la validazione e di offrire metodi dedicati per sapere l’esito della validazione.
// app/Http/Controllers/BookController.php
// ...
public function store(StoreBookRequest $request)
{
// la richiesta in arrivo è valida, posso usarla ...
$attributes = $request->all();
Book::create($attributes);
// ma se servisse posso accedere al risultato della validazione
$validated = $request->validated();
// ...
}
Ogni altro comportamento descritto in precedenza resta, ovviamente, invariato. Il vantaggio di una FormRequest è ovviamente quello di tenere separate la definizione delle regole di validazione dal resto della gestione della request.
5.2
Autenticazione in Laravel
In un applicativo web è importante poter distinguere quali richieste sono effettuate da utenti registrati e quali, invece, provengono da visitatori che non hanno fatto l’accesso. A seconda dei casi, infatti, potremmo voler concedere solo ad alcuni l’accesso a determinate risorse (per esempio solo gli utenti registrati come “editor” possono aggiungere nuovi post a un blog) oppure voler mostrare solo determinati elementi di proprietà dell’utente registrate (per esempio la pagina “Ordini” dovrebbe mostrare solo i miei ordini, non quelli di tutti gli utenti del sito di e-commerce).
Laravel offre una gestione dell’autenticazione completa e flessibile, capace di adattarsi a varie necessità e implementazioni.
In questa lezione, vedremo alcuni concetti generali e una implementazione d’esempio tramite middleware per un’applicazione web che segue il paradigma MVC.
Guards e Providers in Laravel
In Laravel le funzionalità legate all’autenticazione si basano sulla configurazione di due diverse informazioni:
guard - definisce come stabilire se una richiesta HTTP ricevuta è da una “utenza” autenticato
provider - definisce come recuperare gli utenti registrati per l’applicazione
Un’ applicazione Laravel appena creata fornisce una configurazione di guard e provider adatta alle esigenze di una applicazione che eroga solo pagine web e che gestisce interamente lato server la generazione del codice HTML. Infatti, risulta già attiva la guard session - che usa cookie di sessione per stabilire se la richiesta viene da un utente autenticato - e il provider fornito è già configurato per usare il database tramite Eloquent e, in particolare, il model App\Models\User.
Esistono situazioni in cui questa combinazione non è adatta. Ad esempio, il caso in cui l’applicazione Laravel offre delle API RESTful oppure in cui sia il backend di una Single Page Application. In questo caso, è necessario includere e configurare altri servizi di autenticazione offerti dall’ecosistema Laravel. Per esempio, nel caso del server di API, si dovrebbe optare per un guard di tipo token, che consente di riconoscere i client autenticati da uno specifico token presente nella richiesta.
In questa lezione vedremo la configurazione base di Laravel, che offrirà comunque molti spunti per comprendere come Laravel gestisce le richieste autenticate, indipendentemente dal guard e provider impostati.
Modello User come provider di utenti in Laravel
Quando creiamo una nuova applicazione Laravel vengono impostate una serie di configurazioni di default per gestire l’autenticazione.
Tali configurazioni sono definite nel file config/auth.php e ci dicono che il provider di utenti dell’applicazione è la tabella users accedibile tramite il modello App\Models\User di Eloquent.
composer create-project laravel/laravel example-app-auth
Non a caso, infatti, il model App\Models\User fornito in un nuovo progetto non estende direttamente la classe Model, ma la classe Illuminate\Foundation\Auth\User. Ciò permette di individuarlo come model adeguato per essere provider degli utenti dell’applicazione.
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
}
Possiamo, quindi, utilizzare questo model per creare il nostro form di registrazione self-service di nuovi utenti, sfruttando quanto messo già a disposizione da Laravel.
In questo modo, potremo verificare con mano come funzionano i vari meccanismi di autenticazione in un’ applicazione Laravel nei vari punti dello stack MVC.
Registrazione utenti in Laravel
Abbiamo visto, nella lezione sulla validazione degli input, come creare un form e utilizzarlo per inviare nuovi dati al nostro applicativo. Anche la registrazione di un nuovo utente segue uno schema simile. Dovremo, quindi, creare un nuovo form HTML in cui sia possibile inserire i dati da inserire nella tabella users tramite il model User.
Ci servono, quindi, un Controller e una View per poter creare una nuova istanza del model User.
Per il controller e le relative rotte possiamo procedere nel modo seguente, utilizzando le varie convenzioni viste nelle lezioni precedenti.
$ php artisan migrate
$ php artisan make:controller RegisterController
INFO Controller [app/Http/Controllers/RegisterController.php] created successfully.
// app/Http/Controllers/RegisterController.php
class RegisterController extends Controller
{
public function create()
{
return view('register.create');
}
public function store(Request $request)
{
$attributes = $request->validate([
'name' => 'required',
'email' => ['required', 'unique:users', 'email'],
'password' => ['required'],
]);
$user = User::create($attributes);
// utente creato ... e ora?
}
}
// routes/web.php
// ...
Route::get('/register', array(RegisterController::class, 'create'))->named;
Route::post('/register', array(RegisterController::class, 'store'));
Per la view potremo generare un template Blade resources/view/register/create.blade.php che contiene un form con gli input name, email e password e che effettui una POST /register con i dati del form.
{{-- resources/views/register/create.blade.php --}}
<div>
<h1>Register a User</h1>
<form action="/register" method="POST">
@csrf
<label for="name">Name</label>
<input type="text" name="name" id="name">
<label for="email">Email</label>
<input type="text" name="email" id="email">
<label for="password">Password</label>
<input type="password" name="password" id="password">
<button type="submit">REGISTER</button>
</form>
</div>
Con queste poche modifiche siamo in grado di registrare un nuovo utente, cioè aggiungere una nuova riga alla tabella users.
NOTA: la password salvata in questi esempi è, ovviamente, in chiaro; nel caso di utilizzo reale va opportunamente modificato il proprio codice per salvare e poi utilizzare questa informazione in maniera sicura.
Avvio di una sessione alla registrazione e prima autenticazione in Laravel
Ora che abbiamo modo di creare nuovi utenti nel provider dell’authentication, occupiamoci di gestire la parte della guard.
Sappiamo che un’applicazione Laravel appena creata ha già attiva per le richieste di tipo web la guard di tipo session collegata al provider predefinito. In questa gestione dell’autenticazione, l’utente chiede di effettuare login via browser e, se tale login va a buon fine, il server crea un id univoco di sessione e lo rimanda al browser tramite cookie. Il browser potrà, quindi, usare tale cookie di sessione nelle seguenti richieste HTTP per indicare che si tratta dello stesso utente autenticato. Al logout, il cookie viene rimosso e lato server viene cancellato l’id di sessione.
Per creare una nuova sessione nella nostra applicazione d’esempio, possiamo intervenire subito dopo il salvataggio della nuova utenza a database e fare in modo che venga fatta far partire la sessione dell’utente appena registrato.
Nelle applicazioni Laravel viene in aiuto il metodo helper auth() – o l’analoga facade Auth – che ci consente di intervenire su tutto ciò che è relativo alle sessioni autenticate.
Possiamo, quindi, cambiare il nostro controller RegisterController nel modo seguente
public function store(Request $request)
{
$attributes = $request->validate([
'name' => 'required',
'email' => ['required', 'unique:users', 'email'],
'password' => ['required'],
]);
$user = User::create($attributes);
// qui viene effettuata l'autenticazione e creata la sessione
auth()->login($user);
// qui viene creata la risposta che contiene il cookie di sessione appena creata
return redirect('/');
}
L’helper auth di Laravel ci permette di operare in modo trasparente sia con i provider di utenti che con le guard.
Saranno direttamente le varie implementazioni sottostanti del framework Laravel a sapere che l’istanza $user è collegata al provider degli utenti e a occuparsi di creare la sessione e il relativo cookie da aggiungere, in modo da considerare come autenticate le future richieste provenienti da quel browser.
Individuare sessioni autenticate nei controller e nelle view in Laravel
Le richieste HTTP provenienti da un client che ha completato con successo l’autenticazione possono essere individuate in vari punti dello stack MVC di Laravel. Ciò consente di intervenire nei modi opportuni per impedire o modificare determinati comportamenti.
Ad esempio, un utente autenticato non dovrebbe accedere alla pagina di registrazione. Laravel permette di indicare quali rotte devono essere mostrate agli utenti autenticati e quali ai visitatori tramite l’uso dei middleware.
Abbiamo brevemente parlato di middleware quando abbiamo parlato del ciclo di vita di una richiesta HTTP in Laravel. È un meccanismo che permette di distinguere diversi gruppi di rotta e di agire diversamente (o aggiungere azioni) in base al middleware a cui appartiene la singola richiesta. Laravel permette di definire diversi tipi di middleware e ogni richiesta può appartenere a più middleware.
In particolare, per quanto riguarda l’ambito dell’autenticazione, Laravel mette a disposizione due middleware:
guest per le richieste/risposte che provengono/sono inviate a client non autenticati
auth per le richieste/risposte che provengono/sono inviate a client non autenticati
Questi middleware, in particolare, sono impostati come “route-specific” e possono, quindi, essere applicati a ogni rotta, in modo indipendente da altri eventuali middleware.
NOTA: per altri route-specific middleware disponibili, consultare il file app/Http/Kernel.php
Come possiamo, quindi, impedire che un utente già autenticato acceda alle pagine di registrazione? Dichiarando che le richieste collegate alla registrazione possono essere gestite solo se la richiesta stessa appartiene al middleware guest.
// routes/web.php
// ...
Route::get('/register', array(RegisterController::class, 'create'))
->name('register')
->middleware('guest');
Route::post('/register', array(RegisterController::class, 'store'))
->middleware('guest');
NOTA: abbiamo aggiunto un “name” alla rotta, in modo da poter, poi, usare tale nome nella view per ricostruire l’esatta URI senza doverla riscrivere.
Se non si specifica altrimenti il comportamento dell’applicazione, Laravel redirigerà alla pagina “home” definita in app/Providers/RouteServiceProvider.php le richieste di accesso alla pagina di registrazione che fanno parte del middleware auth. Questo comportamento è codificato nel file app/Http/Middleware/RedirectIfAuthenticated.php
A questo punto, potremmo voler modificare la view delle pagine per mostrare un diverso contenuto a seconda che si tratti di un visitatore o di un utente autenticato. Per fare ciò, possiamo di nuovo utilizzare l’helper auth o delle specifiche direttive Blade pensate per scopi simili.
{{-- nel file layout comune a tutte le pagine della nostra applicazione --}}
{{-- ... --}}
<a href="{{ url('/') }}">Home<a>
@auth
<span>Welcome {{ auth()->user()->name }}</span>
@else
<a href="{{ route('login') }}">Login</a>
<a href="{{ route('register') }}">Register</a>
@endauth
{{-- ... --}}
L’helper auth ci permette di accedere a informazioni specifiche della sessione autenticata in corso, quindi, ad esempio, lo User attivo per ricavare le informazioni dalla relativa tabella.
Le direttive @auth e @guest operano come degli if, permettendo quindi di racchiudere al loro interno porzioni della view da renderizzare nel caso in cui la richiesta e il relativo rendering della view siano rispettivamente da un utente autenticato o da un visitatore.
Il controllo del middleware, l’helper auth e le direttive @auth e @guest potranno essere utilizzate in ogni rotta, controller e view della nostra applicazione per costruire contenuti personalizzati o per gestire comportamenti vari legati alla autenticazione.
Login e logout: avviare e terminare una sessione autenticata in Laravel
Per concludere le funzionalità (minime) legate all'autenticazione della nostra applicazione Laravel, mancano all’appello due flussi: quello di login e quello di logout.
Anche in questo caso, andremo ad operare con l’helper auth e andremo ad agire sui due middleware. Ovviamente, ci sono diversi modi in cui è possibile intervenire nello specifico, specie per quanto riguarda la definizione di rotte e controller che gestiscono l’esecuzione effettiva del cambio. In questo esempio, opteremo per un singolo controller che gestisce la “sessione autenticata”
$ php artisan make:controller SessionController
INFO Controller [app/Http/Controllers/SessionController.php] created successfully.
// app/Http/Controllers/SessionController.php
class SessionController extends Controller
{
public function destroy()
{
auth()->logout()
}
}
// routes/web.php
// ...
Route::get('/logout', array(SessionController::class, 'destroy'))
->name('logout')
->middleware('auth');
{{-- nel file layout comune a tutte le pagine della nostra applicazione --}}
{{-- ... --}}
<a href="{{ url('/') }}">Home<a>
@auth
<span>Welcome {{ auth()->user()->name }}</span>
<a href="{{ route('logout') }}">Log Out</>
@else
<a href="{{ route('login') }}">Log In</a>
<a href="{{ route('register') }}">Register</a>
@endauth
{{-- ... --}}
In questo caso abbiamo implementato una soluzione che:
ha il suo “nucleo” nell’esecuzione di auth()->logout(): è la chiamata a questo metodo che esegue, nella pratica, il logout
abbiamo limitato l’esecuzione del metodo del controllo solo alle richieste che fanno parte del middleware auth (inutile far chiamare logout a chi è guest)
abbiamo usato la direttiva @auth per mostrare nella view il link solo a chi è autenticato
Per quanto riguarda, invece, il flusso di login, dovremo, ovviamente, fornire un form e interfacciarci con il model. Possiamo scegliere se creare un controller dedicato o unire il tutto in quello realizzato per il logout (concettualmente, avviamo e terminiamo la sessione).
// app/Http/Controllers/SessionController.php
class SessionController extends Controller
{
public function create()
{
return view('session.create');
}
public function store(Request $request)
{
$attributes = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
// tenta autenticazione con credenziali fornite
if(auth()->attempt($attributes)) {
// login riuscito,
return redirect('/');
}
// gestire errore nel login
return back()->withError(['login-error' => 'Wong credentials!']);
}
}
// routes/web.php
// ...
Route::get('/login', array(SessionController::class, 'create'))
->name('login')
->middleware('guest');
Route::post('/register', array(SessionController::class, 'store'))
->middleware('guest');
5.3
Model, Resource Controller e CRUD in Laravel
In alcuni contesti, come per esempio quello delle API RESTful, potrebbe essere utile unire insieme la “risorsa” presente sul database (intesa come riga di dati salvati) con la “risorsa” intesa come documento sul web accessibile tramite URI (che, per l’appunto, è l’acronimo di Uniform Resource Identifier).
In questo modo è possibile creare un legame 1:1 tra l’istanza di un model e la risorsa esposta esternamente tramite richieste HTTP. Le URI diventano, quindi, il punto di accesso al database, rendendo possibile effettuare operazioni CRUD (Create, Retrieve, Update, Delete) attraverso richieste HTTP. In pratica, si definisce una convenzione che associa la richiesta HTTP all'azione da compiere su una certa entità (cfr la tabella dei nomi di metodi suggeriti).
Se, poi, la nostra applicazione gestisce e salva sul database diversi model/risorse (per esempio Book, Movie e MusicAlbum), potrebbe essere pratico e lungimirante gestire l’accesso a queste risorse in un modo coerente.
Laravel offre una gestione dedicata per questo scenario tipico di sviluppo, offrendo delle rotte e dei controller definiti resource.
Creazione di un controller resource in Laravel
È possibile creare un controller resource in Laravel fornendo l’opzione --resource al comando Artisan make:controller.
php artisan make:controller BookController --resource
Il controller così creato conterrà già la firma di tutti i metodi convenzionali necessari per connettere il controller a una serie di rotte prestabilite.
Abbiamo già elencato alcuni di questi metodi nel capitolo dedicato ai controller.
namespace App\Http\Controllers;
class BookController extends Controller
{
/**
* Mostra l'elenco delle risorse Book presenti nel database
*/
public function index()
{
//
}
/**
* Mostra il form per creare una nuova risorsa Book
*/
public function create()
{
//
}
/**
* Salva una nuova risorsa Book nel databse
*/
public function store(Request $request)
{
//
}
/**
* Mostra la risorsa Book con l'id fornito
*/
public function show($id)
{
//
}
/**
* Mostra il form per modificare la risorsa Book con l'id fornito
*/
public function edit($id)
{
//
}
/**
* Aggiorna la risorsa Book con l'id fornito sul database coi dati inviati
*/
public function update(Request $request, $id)
{
//
}
/**
* Elimina la risorsa Book con l'id fornito sul database
*/
public function destroy($id)
{
//
}
}
I controller di tipo resource possono essere “attivati” registrando una rotta di tipo resource nel file routes/web.php o routes/api.php a seconda che si voglia realizzare un gruppo di rotte di tipo web o api.
use App\Http\Controllers\BookController;
Route::resource('/books', BookController::class);
In questo caso, Laravel realizzerà implicitamente tutte le associazioni necessarie per gestire le varie combinazioni di metodi e path con e senza parametri.
Metodo HTTP
URI
Metodo del controller
GET
/books
index
GET
/books/create
create
POST
/books
store
GET
/books/{book}
show
GET
/books/{book}/edit
edit
PUT/PATCH
/books/{book}
update
DELETE
/books/{book}
destroy
Collegamento a un model in Laravel
Il resource controller mostrato poco sopra non è, però, direttamente collegato ad un model. Il comando make di artisan, infatti, permette di creare la struttura del controller, generando i metodi previsti, ma non inserisce alcuna logica per recuperare le informazioni dal database.
Si è, quindi, liberi di realizzare nel modo più opportuno il codice necessario a recuperare o salvare da database.
Sicuramente, se per la risorsa sul database abbiamo già disponibile un model, risulterà più agevole utilizzare direttamente il model e lasciare che Laravel e Eloquent riuniscano insieme i vari pezzi.
Nel nostro caso, quindi, potremmo aggiornare i vari metodi del controller per avere qualcosa tipo
public function store(Request $request)
{
$arguments = $request->validate([/* regole di validazione qui */])
Book::create($arguments)
}
public function show(Book $movie)
{
return Book::find($movie);
}
Laravel provvederà a fornire, con vari automatismi già visti altrove, tutto ciò che serve per fare in modo che, alla richiesta GET /books/42 venga restituito il contenuto della riga con id 42 della tabella books.
È, ovviamente, possibile attivare e collegare ogni altra funzione messa a disposizione da Laravel ed Eloquent, come, per esempio, la paginazione per i risultati del metodo index oppure l’utilizzo di form request dedicate per le azioni di creazioni e modifica della risorsa.
5.4
Invio email in Laravel
L’invio di email in una applicazione Laravel è realizzato in modo semplice e flessibile e sfrutta tre concetti base: i mailer, i mailable, e la facade Mail.
Ognuno dei tre si occupa di un diverso aspetto del ciclo di vita dell’invio di un messaggio di posta elettronica. I mailer sono le configurazioni dei servizi di invio email (locali o remoti) che può usare l’applicazione. I mailable sono speciali classi che rappresentano un modello di email da inviare. La facade Mail, infine, è il punto d’accesso per chiedere l’effettivo invio di un mailable attraverso un mailer.
Configurazione dei mailer in Laravel
La configurazione dei mailer in Laravel si trova nel file config/mail.php. È possibile definire diversi mailer, ognuno caratterizzato da un nome e da un transport driver
In questo modo, Laravel consente all’applicazione non solo di configurare il server di posta disponibile, ma di utilizzare diversi server o servizi di emailing per inviare determinati messaggi di posta elettronica. Questa possibilità è molto importante per applicazioni che si trovano a dover inviare sia email transazionali (quelle che partono in risposta a una specifica richiesta dell’utente e che è necessario che vengano consegnate quanto prima, come per esempio quelle con i codici per cambiare password o la conferma di un ordine) che email bulk (newsletter e altri invii di marketing, che possono viaggare su canali più lenti).
È possibile configurare il mailer predefinito e dei mailer di failover, nel caso in cui qualcuno degli altri presenti problemi.
Creare un mailable in Laravel
Un mailable è una classe PHP che rappresenta uno specifico tipo di email inviata dalla propria applicazione. È possibile creare una nuova classe mailable tramite il comando Artisan make.
$ php artisan make:mail OrderShipped
INFO Mail [app/Mail/OrderShipped.php] created successfully.
La configurazione di un mailable avviene attraverso i tre metodi envelope, content e attachments, che si occupano rispettivamente di definire le intestazioni, il corpo del messaggio e gli eventuali allegati.
class OrderShipped extends Mailable
{
use Queueable, SerializesModels;
public Order $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function envelope()
{
return new Envelope(
from: new Address('[email protected]', 'ExampleDotCom'),
subject: 'Order Shipped',
);
}
public function content()
{
return new Content(
view: 'emails.orders.shipped',
text: 'emails.orders.shipped-text'
);
}
public function attachments()
{
$code = $this->order->code;
return [
Attachment::fromPath("/path/to/order-$code.pdf"),
];
}
}
Il rendering del contenuto dell’email è, ovviamente, affidato a opportuni template Blade. È possibile recuperare i dati da utilizzare per il rendering della singola email utilizzando le property pubbliche definite dalla classe mailable.
<div>
Code: {{ $order->code }}
Address: {{ $order->address }}
...
</div>
Invio delle email in Laravel
L’invio effettivo di una email avviene tramite la facade Mail e il suo metodo to.
use App\Model\Order
class OrderShipmentController extends Controller
{
public function ship(Order $order)
{
$order = Order::findOrFail($order);
// do stuff to actually start shipping the order
$recipient = auth()->user();
Mail::to($recipient)->send(new OrderShipped($order));
}
}
Il destinatario della email può essere passato come stringa di indirizzo email oppure tramite un oggetto o collezione di oggetti che abbiano le proprietà name ed email.
L’email effettiva da mandare è indicata tramite un mailable, a cui vengono passate le property necessarie per fare il rendering del template Blade associato agli opportuni contenuti.
La facade Mail offre diversi metodi chainable che permettono di definire più nel dettaglio l’esatto invio che si vuole effettuare, sia per quanto riguarda i destinatari (con i metodi cc e bcc) che i tempi e le modalità di invio (queue o later)
5.5
Aprire e salvare file in Laravel
Laravel permette di accedere a file esistenti e salvare file attraverso un’ astrazione del file system che permette di interfacciarsi con file locali o remoti con le stesse modalità.
Ciò consente non solo di leggere e scrivere dal file system locale, FTP o AWS S3 nello stesso modo, ma anche nel caso di scegliere un'opzione diversa a seconda che si stia sviluppando o si stia eseguendo l’applicazione in produzione.
Configurazione dei disk in Laravel
Le applicazioni Laravel gestiscono l’accesso ai file tramite dei disk configurati nel file config/filesystems.php. Ogni disk ha un nome identificativo e un driver che permette di definire il tipo effettivo di file system collegato. In base al driver, poi, saranno configurabili altre opzioni. Per esempio, i disk con driver local richiedono l’impostazione della opzione root per indicare a quale effettiva directory locale corrispondono. Quelli con driver s3 richiedono l’impostazione delle varie key, secret, region e bucket per poter accedere ad AWS S3.
Tra i vari disk configurati di default, è d’interesse quello nominato public. Questo disk è pensato per ospitare quei file che devono essere resi pubblicamente accessibili dall’applicazione Laravel.
Per interagire con uno dei vari disk configurati è possibile usare la facade Storage. È possibile scegliere il disk con cui interagire tramite il metodo disk.
use Illuminate\Support\Facades\Storage;
// interagisce con il disk di default
Storage::put('orders/22341.pdf', $content);
// seleziona il disk
Storage::disk('s3')->put('backup/orders/22341.pdf', $content);
Interagire con i file in Laravel
Nell’esempio precedente abbiamo già mostrato il metodo put della facade Storage che permette di salvare un certo contenuto in un file.
Il contenuto di un file può essere recuperato tramite il metodo get. Il contenuto del file in questo caso è restituito sotto forma di stringa.
$contents = Storage::get('file.pdf');
if (Storage::disk('s3')->exists('file.jpg')) {
// ...
}
È possibile verificare l’esistenza o l’assenza di un file tramite il metodo exist o il metodo missing, così come copiare, spostare o eliminare file con il metodo copy, il metodo move e il metodo delete.
Upload di file da web in Laravel
È possibile utilizzare un disk per permettere di caricare un file nella nostra applicazione. Un file inviato attraverso una request può essere salvato su uno disk disponibili dell’applicazione
class PictureController extends Controller
{
// carica una immagine sul server
public function uploadPicture(Request $request)
{
$path = $request
// recupera della request il file indicato come "picture"
->file('picture')
// lo memorizza sullo disk di default nella directory "pictures"
->store('pictures');
// usare $path del file salvato, per esempio salvarlo a db
// ...
}
}
È importante notare che tramite il metodo store il file recuperato nella request viene salvato sul disk generando anche un ID univoco per il nome del file, mentre l’estensione del file è ricavata dal tipo MIME fornito della request. Il metodo store restituisce il path del file salvato, che può essere, quindi, salvato o utilizzato. Nel caso in cui si voglia specificare il nome del file è possibile usare il metodo storeAs.
È, ovviamente, possibile passare sia a store che a storeAs anche il disk in cui salvare, tra i vari configurati.
6
Laravel e il frontend
6.1
Laravel e il Frontend: come integrare il frontend in Laravel
Laravel è un framework nato in ambito backend e il suo punto di forza è fornire tutte le funzionalità necessarie “lato server” a una moderna applicazione web o servizio di API. D’altro canto, uno degli obiettivi di Laravel è anche poter fornire una esperienza di sviluppo full stack di qualità. Per questo motivo, Laravel offre soluzioni integrate e approcci versatili per poter gestire tutto quello che riguarda la parte frontend di una applicazione web.
Nell’approcciarsi alla gestione del front end con Laravel, è importante distinguere tre diverse aree:
building degli asset
implementazione del frontend con il linguaggio PHP
implementazione del frontend con framework JavaScript
Con building degli asset si intendono tutte le attività necessarie a trasformare una serie di file sorgenti in risorse CSS o JS erogate dalla nostra applicazione Laravel. Più semplicemente, partendo di una serie di file sorgenti Sass o Less e di singoli moduli JavaScript o TypeScript presenti nella directory resources della nostra applicazione, il processo di build crea una serie di file CSS e JavaScript nella directory public ottimizzati per l’erogazione.
Tale processo di build è necessario a entrambe le modalità di implementazione che si potrà scegliere e nelle versioni più recenti di Laravel è gestito da Vite.
La scelta di come affrontare lo sviluppo front end, invece, dipende molto dal tipo di applicazione web che si vuole realizzare. Tale scelta influenzerà molto anche lo stack di tecnologie e librerie che sarà necessario includere nella nostra applicazione.
Frontend con PHP
In questa modalità di implementazione del frontend è l’applicativo Laraver lato server – e quindi PHP – a gestire tutto quello che riguarda la generazione di ogni pagina HTML. In questo contesto, l’interazione tra il browser e il server avviene principalmente per mezzo di submit di form HTML o di altre richieste di risorse specifiche (per esempio la richiesta di una pagina di index con una query string per indicare ordinamento o eventuali filtri).
Abbiamo già visto queste implementazioni nel capitolo dedicato ai template Blade e nella costruzione dei form per creare una nuova utenza nella lezione sull’autenticazione.
Questo approccio, però, presenta alcuni limiti nel momento in cui si voglia offrire un'esperienza d’uso più dinamica nella propria web application, in linea con le modalità di interazione che gli utenti si aspettano di trovare sul web.
Per coloro che non vogliono abbandonare il linguaggio PHP ma hanno la necessità di realizzare dei frontend dinamici e moderni, Laravel mette a disposizione Livewire. Approfondiremo qualche dettaglio su Livewire in una prossima lezione.
Frontend con framework JavaScript
Nel caso in cui si voglia realizzare il proprio frontend utilizzando un framework JavaScript come Vue o React ed avvantaggiarsi dell’ecosistema di pacchetti NPM – ad esempio per realizzare applicazioni single-page app, con il rendering completamente client-side – è necessario indirizzare alcuni problemi che potrebbero presentarsi nel momento in cui dobbiamo far interagire due progetti ben distinti (il back end con Laravel e il front end JavaScript).
Non solo, infatti, potremmo dover tenere sincronizzate e coordinare due diverse codebase, ma dovremmo anche trovare il modo di far convivere due progetti che hanno soluzione e implementazioni molto diverse a problematiche quali il routing, il recupero e utilizzo dei dati per fare il rendering della view (data hydration) e la possibilità di riconoscere le sessioni autenticate.
La soluzione suggerita da Laravel per questi scenari è Intertia, un non framework che si offre come ponte tra il mondo PHP del backend Laravel e il mondo JavaScript del frontend Vue o React.
Intertia va installato come pacchetto Composer nella propria applicazione Laravel e come pacchetto NPM al proprio frontend. In questo modo, sarà possibile mantenere invariata la gestione di rotte e controller nella applicazione Laravel. Tali rotte e controller, però, restituiranno il render di pagina Intertia invece della “classica” view di un template Blade.
class BookController
{
public function index()
{
$users = Book::paginate(RESULTS_PER_PAGE);
return Inertia::render('Books', [
'books' => $books
]);
}
}
Le pagine Intertia altro non sono che componenti realizzati in View o React e salvati nella directory resources/js/Pages della propria applicazione Laravel. I dati passati al metodo render saranno usati per fare hydration delle props del componente JavaScript.
<script setup>
import Layout from './Layout'
import { Link, Head } from '@inertiajs/vue3'
defineProps({ books: Array })
</script>
<template>
<Layout>
<Head title="Books" />
<div v-for="book in books" :key="book.id">
<Link :href="`/book/${book.id}`">
{{ book.title }}
</Link>
<div>{{ book.excerpt }}</div>
</div>
</Layout>
</template>
Per maggiori approfondimenti su Intertia rimandiamo al sito del progetto o a uno dei vari starter kit di Laravel pensati per integrare frontend in React o Vue.
6.2
Build e bundling degli asset in Laravel
Indipendentemente dalla modalità scelta per implementare il proprio frontend – Blade, Livewire oppure Vue / React con Inertia – sarà sempre necessario caricare JavaScript e CSS nel momento in cui il browser apre una pagina erogata dal proprio progetto Laravel, che si tratti di un sito web “old style” o di una moderna web app.
A partire dalla versione 9.x, Laravel utilizza di default Vite per eseguire il bundle degli asset JS e CSS. Vite offre ai progetti Laravel sia la comodità di ricaricare al volo le modifiche ai sorgenti in fase di sviluppo, sia di effettuare l’ottimizzazione di tali asset nel momento in cui si eroga l’applicazione da ambiente di produzione.
NOTA: le versioni di Laravel precedenti alla 9.x utilizzavano Mix per la compilazione degli asset. Mix è un pacchetto nato nella community Laravel per semplificare nei progetti Laravel le stesse attività che descriveremo tra poco. La scelta di sostituire Mix con Vite è legata anche al fatto che quest’ultimo sta diventando il tool di riferimento del mondo front end e, con una semplice integrazione, permette di non “reinventare la ruota”. Vite, inoltre, offre già pieno supporto a tecnologie come React e Vue, permettendo, quindi, di riutilizzare conoscenze e soluzioni.
Caricamento di stili e script in pagina in Laravel
Nel momento in cui si crea una nuova applicazione Laravel con Composer, Vite è già pronto per gestire gli script e gli stili dell’applicazione, anche se risulta necessario un setup iniziale.
Vite utilizza Node JS ed è, quindi, necessario assicurarsi di avere installato questo runtime JavaScript (versione 16+) e il relativo gestore di pacchetti.
node -v
npm -v
npm install
Vite è configurato tramite il file vite.config.js nella root del progetto. La configurazione standard prevede che venga usato il plugin di Vite per Laravel per indicare quali sono gli entry point della propria applicazione. Di default, vengono utilizzati i file resources/css/app.css e resources/js/app.js, ma è possibile indicare anche file preprocessati come TypeScript, JSX, TSX, and Sass.
Ora, supponiamo di modificare questi due file in modo molto semplice, ma impattante, per le nostre pagine:
/* resources/css/app.css */
h1 { color: red }
// resources/js/app.js
import './bootstrap';
alert('Hello Fronted');
Creiamo, quindi, un template Blade, fatto renderizzare da una route opportuna, nel quale usiamo la direttiva @vite per indicare i due file che vogliamo includere come stile e script.
<!doctype html>
<html lang="en">
<head>
<title>Hello Frontend</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
<h1>Hello Frontend</h1>
</body>
</html>
Infine, facciamo partire in due diversi terminali il server di sviluppo di Vite e il nostro server PHP di sviluppo (php artisan serve).
# terminale 1 - vite
$ npm run dev
VITE v4.0.4 ready in 306 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help
LARAVEL v9.48.0 plugin v0.7.3
➜ APP_URL: http://localhost
11:08:59 PM [vite] page reload resources/views/example.blade.php
Se apriamo la pagina erogata dalla nostra nuova rotta (per esempio http://localhost:8000/app) vedremo che l’heading H1 è rosso e che si è aperto un alert. Se ispezioniamo il contenuto della pagine, troveremo che il codice HTML effettivamente caricato conterrà qualcosa di simile al seguente:
<title>Hello Frontend</title>
<script type="module" src="http://[::1]:5173/@vite/client"></script>
<script type="module" src="http://[::1]:5173/resources/js/app.js"></script>
</head>
Il server di sviluppo di Vite sta leggendo e osservando le modifiche ai due file entry point indicati e sta generando in automatico per noi gli opportuni CSS e JS da caricare nella pagina. Nel nostro caso si trattava di semplice direttive, ma per applicazioni più complesse è possibile usare quei due file per l’intera configurazione di stile o per caricare tutti i moduli JavaScript o TypeScript necessari al corretto funzionamento del frontend, realizzando, quindi, il “bundle” di tutti gli asset necessari a far funzionare la web app.
L’applicazione Laravel, tramite la direttiva blade @vite, sta recuperando CSS e JS dal server di sviluppo di Vite, come se fossero ospitati su una macchina remota.
Oltre alla modalità “development”, Vite offre la possibilità di effettuare una build ottimizzata per erogare le stesse risorse in produzione. Interrompendo il server di sviluppo ed eseguendo il comando npm run build, Vite creerà i file public/build/assets/app-<HASH_UNIVOCO>.css e public/build/assets/app-<HASH_UNIVOCO>.js. Nel momento in cui il server di sviluppo di Vite non è in esecuzione, il nostro template Blade includerà questi file nel fare il rendering della view, che verranno, quindi, erogati direttamente dalla nostra app Laravel, in quanto inclusi nella directory public.
$ npm run build
vite v4.0.4 building for production...
✓ 50 modules transformed.
public/build/manifest.json 0.25 kB
public/build/assets/app-4ed993c7.js 0.00 kB │ gzip: 0.02 kB
public/build/assets/app-ef00eeea.js 100.80 kB │ gzip: 37.72 kB
<title>Hello Frontend</title>
<link rel="preload" as="style" href="http://localhost:8000/build/assets/app-67dcdfd2.css" />
<link rel="modulepreload" href="http://localhost:8000/build/assets/app-0363c5ea.js" />
<link rel="stylesheet" href="http://localhost:8000/build/assets/app-67dcdfd2.css" />
<script type="module" src="http://localhost:8000/build/assets/app-0363c5ea.js"></script>
</head>
Build di stili e script in Laravel
Oltre all’effettivo caricamento in pagina di CSS e JS, Vite può gestire anche la compilazione di stili e script partendo dai rispettivi file sorgenti (SCSS, moduli TypeScript e JavaScript, …). Vite è, infatti, un build tool per le tecnologie frontend ed è, quindi, possibile utilizzare tutte le sue feature “stand alone” all’interno di un progetto Laravel.
A titolo d’esempio, vediamo come integrare la libreria Bootstrap all’interno di un progetto Laravel, un caso che ci permette di restare all’interno di una configurazione semplice, ma comunque pratica perché impatta sia CSS che JavaScript.
Laravel mette a disposizione un pacchetto Composer dedicato a fornire preset e utilità per il frontend.
composer require laravel/ui
Una volta installato tale pacchetto, è possibile usare il (nuovo) comando Artisan ui per creare lo scaffolding base di bootstrap.
php artisan ui bootstrap
Questo comando modificherà alcuni file nel nostro progetto Laravel, in particolare:
aggiungerà nuovi pacchetti NPM a package.json, inclusi scss e lo stesso bootstrap; sarà necessario installarli con npm install
aggiungerà i file resources/sass/app.scss e resources/sass/_variables.scss, che saranno i sorgenti SCSS da cui costruire i CSS del progetto
aggiornerà la configurazione di Vite per usare resources/sass/app.scss invece di resources/css/app.css (che quindi non fornirà più gli stili)
aggiornerà il file resources/js/bootstrap.js (il bootstrap degli script JS della nostra app Laravel) per includere Bootstrap (la parte JS della libreria) tramite un import JavaScript
Sarà, quindi, sufficiente aggiornare il nostro template Blade per puntare al nuovo file “sorgente” degli stili.
<title>Hello Frontend</title>
@vite(['resources/sass/app.scss', 'resources/js/app.js'])
Avremo, quindi, che:
avviando il server di sviluppo di Vite (npn run dev), Vite si occuperà di eseguire il build on-fly dei file SASS in CSS e di renderli disponibili nelle nostre pagine mentre si sviluppa
eseguendo la build degli asset per produzione (npm run build), Vite si occuperà di creare due singoli file nella directory public\build\asset con al loro interno tutto ciò che abbiamo indicato nei nostro sorgenti SASS e JS e tutto ciò che abbiamo importato dalla libreria Bootstrap.
npm run build
vite v4.0.4 building for production...
✓ 111 modules transformed.
public/build/manifest.json 0.26 kB
public/build/assets/app-290d7b3a.css 192.30 kB │ gzip: 27.28 kB
public/build/assets/app-b44a8b59.js 183.04 kB │ gzip: 62.36 kB
In modo simile, potremo integrare altre librerie installate via NPM oppure scrivere i nostri moduli JavaScript e importarli nella “app” tramite opportuni import JavaScript.
In ogni caso, grazie all’uso di Vite, il tutto nei modi e con tool già noti e consolidati nel mondo dello sviluppo front end.
6.3
Livewire
Livewire è uno dei progetti nell’ecosistema di Laravel espressamente dedicata alla gestione del frontend. La particolarità di Livewire è quella di permettere lo sviluppo di una web application “moderna” senza la necessità di utilizzare framework JavaScript dedicati.
Con Livewire è possibile sviluppare dei componenti Blade che offrono un livello di dinamicità pari a quello offerto da Vue o React, senza la necessità di dover gestire la complessità di un progetto nettamente diviso tra front end e back end e potendo continuare a sviluppare la propria applicazione entro i confini di Laravel e dei template Blade.
Come funziona Livewire
Livewire è un pacchetto Composer che è possibile aggiungere a un progetto Laravel. Va, poi, attivato su ogni pagina HTML (o la pagina, nel caso in cui si voglia realizzare una Single Page Application) tramite opportune direttive Blade e vanno scritti i vari componenti (costituiti da classe PHP e file Blade).
Nel momento in cui il browser chiede di accedere a una pagina in cui è usato Livewire, succede quanto segue:
la pagina viene renderizzata con gli stati iniziali del componente, come una qualsiasi pagina realizzata tramite Blade
nel momento in cui sul browser avviene un'interazione gestita con Livewire, viene effettuata una chiamata AJAX a un’ opportuna rotta indicando componente e interazione occorsa, più lo stato del componente
i dati vengono elaborati alla parte PHP del componente, che effettua il nuovo render in conseguenza dell’interazione e la rimanda al browser
il DOM della pagina viene mutato in accordo con le modifiche ricevute dal server
Un meccanismo molto simile a quello dei già citati Vue e React, in cui viene realizzato un data binding tra backend e frontend.
Come installare Livewire
L’installazione di Livewire è assolutamente minimale. È sufficiente installare il pacchetto Composer nel progetto Laravel e aggiungere le necessarie direttive Blade a tutte le pagine (o al layout comune da cui derivano tutti i template Blade del progetto).
composer require livewire/livewire
...
@livewireStyles
</head>
<body>
...
@livewireScripts
</body>
</html>
Componenti Livewire
Una volta installato il pacchetto Composer, è disponibile un nuovo sottocomando Artisan make per creare un nuovo componente Livewire. Ogni componente sarà composto da una classe e da una view Blade, in modo analogo a quanto visto per i componenti class based di Blade.
php artisan make:livewire SpyInput
COMPONENT CREATED 🤙
CLASS: app/Http/Livewire/SpyInput.php
VIEW: resources/views/livewire/spy-input.blade.php
Il nostro componente d’esempio “spierà” quello che viene scritto sul browser in un campo input HTML, senza necessità di scrivere codice JavaScript.
Inseriamo, quindi, una property pubblica alla classe del componente.
// app/Http/Livewire/SpyInput.php
class SpyInput extends Component
{
public string $message;
public function render()
{
return view('livewire.spy-input');
}
}
e modifichiamo nel seguente modo la view del componente:
{{-- resources/views/livewire/spy-input.blade.php --}}
<div>
<label>Type here:
<input type="text" wire:model="message"/>
</label>
<span>You typed: <span>{{ $message }}</span></span>
</div>
e inseriamo il componente in un template di una pagina
@livewireStyles
</head>
<body>
<livewire:spy-input />
@livewireScripts
</body>
</html>
Sappiamo che le property pubbliche della classe possono essere usate nel template blade, e il comportamento dei componenti class based di Blade, quindi in <span>{{ $message }}</span> verrà inserito il valore della property $message. In un normale componente class based, però, ciò avviene solo al primo e unico rendering del componente.
Nel nostro componente Livewire, però, abbiamo utilizzato wire:model="message" negli attributi del <input>. Questo attributo fa in modo che il value dell’input sia legato anch’esso al valore della property $message della classe. Nel momento in cui si scrive nell’input il nuovo value viene mandato al server, che aggiorna il valore di $message ed effettua un nuovo render, rimandandolo al frontend che, quindi, aggiorna il testo in <span>{{ $message }}</span></span>.
Aprendo la scheda Network degli strumenti di sviluppo del browser, noteremo che dando il focus all’input a ogni pressione di tasto sulla tastiera viene effettuata una chiamata al server su una rotta /livewire/message/<NOME-COMPONENTE>. La risposta a ognuna di queste chiamate conterrà il nuovo HTML renderizzato per il componente, che lo script di Livewire caricato lato browser inserirà in pagina al posto della vecchia.
Sono disponibili diverse attributi custom wire: e altre semplificazioni, ad esempio per eseguire un metodo pubblico della classe del componente al clic su un button è possibile scrivere qualcosa come <button wire:click="doSomething">Show/Hide</button> dove doSomething è un metodo pubblico della classe PHP del componente Livewire.
Integrazione con altre funzionalità Laravel
È possibile integrare Livewire con ogni altra funzionalità offerta da Laravel rendendo l’utilizzo della propria applicazione web più dinamica e in linea con le moderne aspettative. La classe PHP collegata al componente si comporta come ogni altra classe PHP di un progetto Laravel, con l’unica differenza di usare il metodo mount invece del classico costruttore di classe __construct per inizializzare le property public della classe. Questo, ovviamente, nel caso in cui, per qualche motivo, non vogliamo usare l’automatismo di Livewire di assegnare il valore iniziale delle public property usando i parametri aggiuntivi usati nel tag <livewire:
{{-- assegnazione automatica alla property $book della classe ShowBook --}}
<livewire:show-book :book="$book">
class ShowBook extends Component
{
public $title;
public $excerpt;
// mount invece di __constuct
public function mount($book)
{
$this->title = $book->title;
$this->content = $book->content;
}
...
}
È anche possibile indicare eventuali regole di validazione tramite la property protected $rules e chiamare il metodo validate per effettuare la validazione dei dati invitati dal frontend al backend.
class BookForm extends Component
{
public $title;
public $excerpt;
public $isbn;
protected $rules = [
'title' => ['required', 'max:200'],
'isbn' => ['required', 'unique:books', 'size:17'],
'excerpt' => 'max:500'
];
public function saveBook()
{
$arguments = $this->validate();
Book::create($arguments);
}
}
<form wire:submit.prevent="saveBook">
<input type="text" wire:model="title">
@error('title') <span class="error">{{ $message }}</span> @enderror
<input type="text" wire:model="excerpt">
@error('excerpt') <span class="error">{{ $message }}</span> @enderror
<input type="text" wire:model="isbn">
@error('isbn') <span class="error">{{ $message }}</span> @enderror
<button type="submit">Save Book</button>
</form>
In generale, ogni componente Livewire si comporta nei modi che uno sviluppatore Laravel si aspetta da una classe PHP che estende una delle classi di Laravel e da un template Blade, permettendo, quindi, la realizzazione di interfacce web dinamiche senza la necessità di separare (e poi ricollegare) gli sviluppi tra Laravel e Vue/React.
Ad esempio, nel caso precedente di validazione della submit di un nuovo elemento da aggiungere a un Model, si potrebbero sfruttare gli hook di Livewire per effettuare la validazione sui singoli campi che si sta modificando anche prima dell’effettivo submit.
// ...
public function updated($propertyName)
{
$this->validateOnly($propertyName);
}
public function saveContact()
{
$arguments = $this->validate();
Book::create($arguments);
}
// ...
CONTENUTI GRATUITI IN EVIDENZA
Guide per aspiranti programmatori 👨🏻🚀
Vuoi muovere i primi passi nel Digital e Tech? Abbiamo preparato alcune guide per aiutarti a orientarti negli ambiti più richiesti oggi.