
Laravel e le rotte magiche
alberto • October 22, 2020
focusUna delle feature che preferisco in Laravel è il Route Model Binding. Grazie ad esso possiamo aumentare l'astrazione delle nostre applicazioni rispetto al concetto di rotta; è un ulteriore passo avanti per isolare ulteriormente i nostri componenti software.
Ok, grazie... ma cos'è?
Traducendo il nome in italiano su Google Translate otteniamo associazione del modello di percorso. Google non ci va molto lontano, se associamo il concetto di percorso a quello di url, l'indirizzo web di una pagina.
Parlando in un linguaggio più tecnico, è la possibilità di associare automaticamente uno o più modelli a determinate rotte web, evitando quindi di lavorare a livello di "id", ma potendo lavorare direttamente a partire dai modelli che questi id solitamente rappresentano.
Spesso quello che succede in tutto il web è quello di avere una rotta che identifica un modello, per esempio un blog potrebbe avere una rotta simile a /articoli/1
, dove 1
rappresenta appunto l'id del Post da visualizzare all'utente.
Spesso all'interno del controller che gestisce questa rotta, il primo metodo che viene invocato è un findOrFail
a partire appunto da questo id, per recuperare l'oggetto o per segnalare errore all'utente.
Usando invece il Route Model Binding deleghiamo questa operazione ripetitiva al framework.
Il Binding implicito
Secondo il dizionario italiano, implicito significa che non deve essere espresso, configurato, potremmo dire quasi automatico.
Questa feature in particolare sfrutta il type-hinting di PHP per capire quale è l'intenzione dell'utente:
//routes/web.php
Route::get('articolo/{post}', 'PostController@getPost');
//app/Http/Controllers/PostController.php
public function getPost(Laravello\Models\Post $post)
{
dd($post);
}
In questo semplice snippet possiamo vedere la tipologia base di Binding implicito. Grazie alla dichiarazione all'interno del metodo, dove dichiariamo che $post
deve essere un'istanza della classe Post
, deleghiamo a Laravel il retrieve del modello al partire dall'id, che sarà recuperato dalla url.
In questo caso il comportamento standard di Laravel è quello di utilizzare il metodo findOrFail
cosí da ritornare una risposta 404 all'utente in caso di id non esistente a database.
Ok implicito, ma voglio un po' di controllo in piú
Il comportamento di default di Laravel è quello previsto sopra, ovvero si aspetta di ricevere come parametro la chiave univoca del modello. Ma non sempre voglia questo comportamento. Spesso, per ragioni SEO, si preferisce utilizzare nelle url il cosiddetto slug ovvero una particolare serializzazione di una stringa, spesso il titolo, che sia compatibile con le regole sintattiche delle url.
Mantenendoci nel contesto del Binding implicito possiamo comunicare al framework quale proprietà del modello utilizzare.
Abbiamo due alternative:
- a livello di rotta, cosí che possa variare in base al contesto:
Route::get('articolo/{post:slug}', 'PostController@getPost');
- a livello di modello, cosí che non serva specificarlo tutte le volte:
//app/Models/Post.php
public function getRouteKeyName()
{
return 'slug';
}
Il funzionamento tra le due alternative è esattamente lo stesso, è solamente una questione di comodità.
Il Binding esplicito
Nella modalità implicita, non abbiamo fatto altro che configurare il framework comunicandogli quale fosse la classe da istanziare e quale proprietà utilizzare, se diversa dalla chiave primaria.
Spesso questa personalizzazione è sufficente, ma qualora non lo fosse, il framework ci viene in aiuto permettendoci di configurare anche la logica per ottenere il modello.
Questa configurabilità prende appunto il nome di Binding esplicito ovvero in un modo più preciso, piú configurabile.
Supponiamo che per qualche esigenza particolare, un cliente ci chieda di utilizzare nelle url, per ragioni di sicurezza, il quadrato dell'id del record. Quindi alla url /articoli/1
avremo il dettaglio del Post 1, alla url /articoli/4
il dettaglio del Post 2 e alla url /articoli/9
il dettaglio del Post 3.
Anche in questo caso abbiamo due modalità per procedere.
La prima di queste è rappresentata dal metodo statico bind
all'interno dell'oggetto Route, da utilizzare nel RouteServiceProvider in fase di boot
dell'applicazione:
//app/Providers/RouteServiceProvider.php
public function boot()
{
Route::bind('post', function ($value) {
return App\Models\Post::where('id', sqrt($value))->firstOrFail();
});
}
Mentre la seconda consiste nell'inserire la logica direttamente nel modello, all'interno del metodo specifico resolveRouteBinding
:
//app/Models/Post.php
public function resolveRouteBinding($value, $field = null)
{
return $this->where('id', sqrt($value))->firstOrFail();
}
Ok, chiaro, figo, ma non è un po' superfluo?
Ebbene questa è una domanda che un utilizzatore critico di Laravel potrebbe porsi, e avrebbe senso come domanda. A prima vista il Model Binding potrebbe sembrare una complessità gratuita.
In fin dei conti stiamo creando una sovrastruttura per evitare di scrivere 2 righe in più in ogni controller.
È vero, il risparmio in termini di codice è poco, soprattutto in confronto ad altre feature del framework.
Ma in questo caso la vera funzionalità non è in termini di risparmio, ma di architettura del codice. Abbiamo in qualche modo isolato una funzionalità, quella di nascondere una implementazione di basso livello e portarla ad un livello più alto, ragionando a livello di modello e non più di id.
E questa è una grande cosa!