
Articolo aggiornato il 20 febbraio 2022 con la nuova versione Laravel 9
Laravel Eloquent è una delle funzionalità di punta del framework Laravel, in buona parte grazie al modo in cui gestisce la definizione, la creazione e l’uso delle relazioni tra le tabelle del database. In questo tutorial ti mostro come creare e usare le relazioni Eloquent, così da metterti al lavoro senza alcuna conoscenza pregressa. Ti consiglio anche il tutorial Laravel 9 per principianti, che propone un altro modo per imparare Laravel con Eloquent.
Che cos’è una relazione in un database?
Partiamo dalle basi assolute. Che cos’è esattamente una relazione? Una relazione significa che hai due o più tabelle i cui record sono collegati tra loro. Mettiamo che tu abbia una tabella users e che ogni utente possa avere più posts. Come colleghi le due cose? Di solito aggiungendo una colonna user_id alla tabella posts, così sai subito a quale utente appartiene ogni articolo. È questa «connessione», che ti permette di capire quali record vanno insieme, a chiamarsi relazione.
Quali tipi di relazione Eloquent esistono?
Esistono diversi tipi di relazione Eloquent. Quello che ti serve dipende di solito dal fatto che tu abbia uno o (potenzialmente) più elementi da ciascun lato, nella prima e nella seconda tabella.
Nell’esempio qui sopra abbiamo una tabella users e una tabella posts. Diciamo che ogni articolo possa appartenere a un solo utente: per realizzarlo aggiungiamo una colonna user_id alla tabella posts, e ogni riga di posts punta così al suo autore. Nello scenario opposto, molto meno probabile, se ogni articolo potesse appartenere a più autori (users) e ogni autore contribuire a un solo articolo, aggiungeremmo una colonna post_id alla tabella users. È una relazione piuttosto semplice, di tipo One-To-Many.
Ma che succede se ogni utente può avere più articoli e ogni articolo può avere più autori? Come si risolve? Quella si chiama relazione Many-To-Many. Per un tipo di relazione così non basta aggiungere una colonna a una tabella e chiudere la questione: qui serve una tabella pivot. Non preoccuparti se ti sembra troppo complesso, ci arriviamo tra poco.
In generale si dice che tra i dati esistano i seguenti tipi di relazione:
- Relazione «has one», cioè «uno a uno»
- Relazione «uno a molti» (One-To-Many)
- Relazioni Has-One-Of-Many (per esempio il più recente tra tanti)
- Relazioni HasOneThrough e HasManyThrough
- Relazione Many-To-Many
Relazioni uno a uno con Eloquent
Partiamo dalla relazione più semplice. Una relazione di tipo «Has One», cioè «One-To-One»: un record è collegato a un altro record, e a uno solo.
Continuiamo con l’esempio del blog, con utenti e articoli, e diciamo che ogni utente può avere un profilo. In certi casi potresti tenere tutte le informazioni del profilo dentro il modello User, ma non sarebbe la scelta migliore. Qui voglio salvarle in una tabella separata: se un giorno vorremo trasferire un profilo a un altro utente, ci farà comodo.
Per prima cosa crea un modello Profile, quello User è già generato di default. Le colonne esatte del modello Profile contano poco, ma conviene creare il modello e la sua migrazione insieme:
php artisan make:model Profile -mTrattandosi di una relazione uno a uno, abbiamo la rara possibilità di scegliere se:
- mettere una colonna
user_idsul modelloProfile, oppure - aggiungere una colonna
profile_idsul modelloUser.
Puoi scegliere quello che preferisci, ma tieni presente che la relazione dice che un User ha un Profile. E quando qualcosa ha qualcos’altro, di solito la colonna va sul secondo modello. Si può dire che il primo modello «possiede» il secondo.
È quindi quello che farò:
/** Aggiungi questo alla migrazione della tabella profile oppure crea una nuova migrazione */
$table->foreignId('user_id');Esegui le migrazioni e siamo pronti a implementare la relazione.
Apri il modello User e aggiungi il metodo pubblico seguente. Puoi chiamarlo come vuoi, ma la convenzione è lo snake_case:
public function profile()
{
return $this->hasOne(Profile::class);
}Ora hasOne si aspetta che il metodo profile() usi la colonna user_id del modello Profile. Se la tua colonna si chiama diversamente, passa un secondo argomento a hasOne con il nome giusto:
return $this->hasOne(Profile::class, 'author_id');Detto questo, allontanarsi dalla convenzione non è consigliato.
Hai appena aggiunto la tua prima relazione Eloquent! Scorri la pagina per imparare a usarla.
Aggiungere l’inverso di questa relazione
Nella maggior parte dei casi possiamo anche definire l’inverso di una relazione. Sembra complicato, ma non lo è: aggiungiamo sul modello Profile un metodo che rimanda al modello a cui appartiene (User, in questo caso). Nel tuo modello Profile:
public function user()
{
return $this->belongsTo(User::class, 'author_id');
}Usare una relazione Eloquent
Usare questa relazione Eloquent di tipo One-To-One è semplicissimo. Ogni volta che hai un’istanza dell’oggetto User puoi chiamare $user->profile oppure $user->profile()->enchainerDautreMethodeIci().
Questo perché $user è un’istanza della classe User e su quel modello abbiamo aggiunto il metodo di relazione profile().
Nota bene, ed è molto importante: con questo approccio puoi accedere sia a ->profile come se fosse una proprietà, sia a ->profile() come se fosse un metodo. La differenza è che ->profile restituisce direttamente un’istanza Eloquent, mentre ->profile() ti permette di concatenare altri metodi. Per esempio ->profile()->orderBy('xxx', 'ASC')->get().
In breve, ->profile è una scorciatoia per ->profile()->get().
$user = auth()->user();
$profile = $user->profile;
$name = $user->profile->display_name;
// Crea un profilo
$profile = $user->profile()->create([
//
]);
$user = Profile::find(1)->user;Relazione Eloquent di tipo One-To-Many
Un’altra relazione molto importante, forse la più importante, è la relazione uno a molti. Conosciuta anche come relazione hasMany, descrive il caso in cui un elemento ha molti altri elementi. È molto simile alla precedente.
Restando al nostro blog, diciamo che un profilo ha molti articoli. Apri il modello Profile e aggiungi questo metodo:
public function posts()
{
return $this->hasMany(Post::class);
//Oppure return $this->hasMany(Post::class, 'foreign_key');
}Significa semplicemente che ogni profilo ha molti articoli. Esiste anche l’inverso:
public function profile()
{
return $this->belongsTo(Profile::class);
//Oppure return $this->belongsTo(Profile::class, 'foreign_key');
}Si usa quasi come quella vista sopra, con una differenza: la relazione restituisce più elementi, sotto forma di collection Eloquent.
Ora che hai definito la relazione come metodo sul modello, possiamo di nuovo usarla come proprietà (->posts), che qui restituisce una collection Eloquent invece di un singolo modello. E possiamo usarla anche come metodo (->posts()->where('created_at', '>', now()->subDays(14))->get()).
$posts = Profile::find(1)->posts;
//Oppure $posts = Profile::find(1)->posts()->get();
foreach ($posts as post) {
// TODO
}
$lastPost = Profile::find(1)->posts()->latest()->first();Ottenere il modello più recente o più vecchio tramite una relazione
A volte, quando definisci una relazione hasMany, vuoi recuperare solo il modello più recente oppure solo il modello più vecchio. Laravel mette a disposizione due metodi comodi, ->latestOfMany() e ->oldestOfMany():
public function latestPost()
{
return $this->hasMany(Post::class)->latestOfMany();
}public function oldestPost()
{
return $this->hasMany(Post::class)->oldestOfMany();
}Questi due metodi restituiscono un solo modello invece di una collection.
$latestPost = Profile::find(1)->latestPost;Se l’idea ti convince ma ti servono filtri personalizzati o clausole where() più elaborate, guarda questo esempio nella documentazione di Laravel:
/**
* Ottieni il prezzo attuale del prodotto
*/
public function currentPricing()
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}HasOneThrough e HasManyThrough
Visti questi esempi, spingiamoci un po’ oltre: adesso vogliamo definire una relazione attraverso un altro modello.
Riprendiamo l’esempio precedente, dove ogni User ha un Profile e ogni Profile ha molti Post. Un caso di relazione attraverso un altro modello è quando vogliamo ottenere tutti gli articoli di un utente. Non possiamo aggiungere direttamente un hasMany(Post::class) sul modello User, perché il modello Post non contiene la colonna user_id: ha solo profile_id. È l’esempio perfetto di relazione has many through.
Per definire una relazione così si procede più o meno come sopra. Nel nostro esempio, aggiungi questo al modello User:
public function posts()
{
return $this->hasManyThrough(Post::class, Profile::class);
}Il primo argomento del metodo è il modello a cui vogliamo arrivare e il secondo è il modello intermedio.
Definire una relazione hasOneThough è quasi identico a una hasMany(), con la differenza che qui devi assicurarti che l’elemento finale sia uno solo:
public function first_login()
{
return $this->hasOneThrough(FirstLogin::class, Profile::class);
}Relazioni molti a molti
Viste le relazioni qui sopra, passiamo a qualcosa di più avanzato. Una relazione molti a molti è quella in cui ogni User può avere più Profile, per esempio quando si collabora con altre persone, e ogni Profile può avere più User.
Come si risolve? Non possiamo limitarci ad aggiungere una colonna profile_id alla tabella degli utenti, perché i profili sono potenzialmente più di uno. E non possiamo nemmeno aggiungere una colonna user_id al modello Profile, perché anche gli utenti sono potenzialmente più di uno.
Serve quindi una tabella intermedia. Questa tabella si chiama tabella pivot e nella maggior parte dei casi non fa altro che memorizzare due id sulla stessa riga.
Immagina questo scenario: se la nostra tabella pivot ha una colonna user_id e una colonna profile_id, possiamo collegare un dato User a un dato Profile. E dato che lo stesso user_id può comparire tutte le volte che vogliamo, ogni utente può essere collegato a quanti profile_id ci servono. Al contrario vale lo stesso: lo stesso profile_id può ripetersi quanto vogliamo, associato a tutti gli user_id diversi che ci servono.
Come creare la migrazione di una tabella pivot in Laravel
Per prima cosa creiamo la migrazione del database che ci serve. Esegui il comando qui sotto e nota come si costruisce il nome della tabella: unendo i nomi delle due tabelle (entrambi al singolare) in ordine alfabetico. Puoi forzare un altro nome, ma di solito non ha molto senso.
php artisan make:migration create_profile_user_table --create=profile_userOra apri il file di migrazione e aggiungi due righe al metodo up().
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProfileUserTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('profile_user', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('profile_id');
$table->foreignId('user_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('profile_user');
}
}
Come vedi, la struttura di una tabella pivot è molto semplice. Adesso esegui le migrazioni.
Poi apriamo i due modelli e aggiungiamo i due metodi di relazione. Su entrambi i modelli il metodo ha la forma belongsToMany().
Aggiungi questo al modello User:
/**
* Funzione pubblica per ottenere tutti i profili sotto cui questo utente può pubblicare
*/
public function profiles()
{
return $this->belongsToMany(Profile::class);
}E questo al modello Profile:
/**
* Funzione pubblica per ottenere tutti gli utenti che possono pubblicare sotto questo profilo
*/
public function users()
{
return $this->belongsToMany(User::class);
}Se scegli un nome di tabella diverso da quello di default, puoi passarlo come parametro ai metodi belongsToMany:
return $this->belongsToMany(Profile::class, 'user_role'); // Qui la tabella è in ordine alfabetico inversoPer impostazione predefinita Eloquent si aspetta nella tabella pivot le colonne user_id e profile_id. Se preferisci nomi diversi, passali come terzo e quarto parametro:
return $this->belongsToMany(Profile::class, 'user_role', 'nom_de_la_colonne_qui_represente_l_identifiant_de_ce_modele', 'nom_de_la_colonne_qui_represente_l_identifiant_de_la_cle_etrangere');
// Sembra inutilmente complesso, quindi ecco un esempio.
// Diciamo che lo aggiungiamo sul modello User: di default sarebbe così:
return $this->belongsToMany(Profile::class, 'user_role', 'user_id', 'profile_id');Usare una relazione Eloquent Many-To-Many
Usare questa relazione è molto semplice e sarà simile a quanto visto sopra. Guarda questi piccoli esempi:
$user = User::find(1);
$user->profiles->each(function ($item) use ($user) {
$profile->name = $user->firstname . ' ' . $user->lastname;
$profile->save();
});$profile = Profile::find(1);
$profile->users()->orderBy('created_at', 'desc');Salvare dati nella tabella pivot
L’ultima parte di questo articolo riguarda il salvataggio dei dati nella tabella pivot. Sì, si può fare. Non serve in ogni caso d’uso, ma in alcuni torna decisamente utile.
Facciamo il punto: che cos’è una riga di una tabella pivot? Che cosa rappresenta? Una riga della tabella pivot rappresenta la relazione tra due record. Se cancellassimo quella riga, la relazione esisterebbe ancora? No.
Ecco perché è importante. Se ogni riga della tabella pivot rappresenta una relazione, allora possiamo salvare nella tabella pivot anche informazioni sulla relazione stessa. Possono essere dati piuttosto banali, come la data in cui la relazione è stata creata o aggiornata l’ultima volta (le colonne created_at e updated_at).
Leggere i dati della relazione dalla tabella pivot
Nell’esempio qui sopra abbiamo scritto una migrazione per la tabella pivot. Quella migrazione conteneva già le colonne created_at e updated_at (grazie a $table->timestamps();). Come si accede a questi dati?
Prima di tutto dobbiamo dirlo a Eloquent: il nostro modello pivot ha anche altri attributi a cui voglio accedere, eccoli.
Si fa così:
/**
* Funzione pubblica per ottenere tutti i profili sotto cui questo utente può pubblicare
*/
public function profiles()
{
return $this->belongsToMany(Profile::class)->withPivot('created_at', 'updated_at', 'active');
}Oppure si usa il comodo helper ->withTimestamps(), che dichiara già le colonne created_at e updated_at:
/**
* Funzione pubblica per ottenere tutti i profili sotto cui questo utente può pubblicare
*/
public function profiles()
{
return $this->belongsToMany(Profile::class)->withTimestamps()->withPivot('active');
}A questo punto puoi accedere alla tabella pivot così:
$user = User::find(1);
foreach ($user->profiles as $profile) {
echo $profile->pivot->created_at;
}Comodo, no?
Rinominare l’attributo pivot
C’è anche un altro trucco simpatico, ed è rinominare l’attributo pivot. Eloquent punta su un linguaggio descrittivo ed espressivo. La documentazione porta l’esempio di una relazione tra un podcast e un utente: quella relazione è un abbonamento, quindi usare «pivot» per parlare di un abbonamento suonerebbe strano.
// Non bene:
$user->podcasts()->first()->pivot->price;
// Bene:
$user->podcasts()->first()->subscription->price;Per rinominare l’attributo pivot usa il metodo ->as($name) sulla return $this->belongsToMany():
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();Conclusione
Un articolo lungo. Se sei arrivato fin qui, grazie per avermi seguito. Qui ti ho parlato di tutti i principali tipi di relazione tra modelli Eloquent. Spero ti sia stato utile nel tuo percorso di apprendimento delle relazioni Eloquent.
Le relazioni presentate in questo articolo sono quelle che userai più spesso. E secondo me, per adesso, sono più che sufficienti.
Può però capitarti una situazione in cui queste relazioni non bastano. Qui abbiamo sempre parlato della relazione tra un modello e un altro modello. Pensa però al caso, tutt’altro che raro, in cui vuoi assegnare dei tag sia a un Post sia a una Page: le relazioni viste sopra non bastano più, e non abbiamo trattato le relazioni polimorfiche. Ho però già scritto un’introduzione alle relazioni polimorfiche di Eloquent: leggila per approfondire l’argomento


