Le relazioni di Eloquent tra i vari modelli
Come abbiamo visto nelle lezioni precedenti, uno dei componenti chiave di Laravel è Eloquent, che consente agli sviluppatori di creare modelli che rappresentano diversi tipi di dati all'interno dell'applicazione. Le relazioni tra questi modelli determinano il sistema con cui le informazioni vengono archiviate e recuperate all'interno del sistema.
Esistono vari tipi di relazioni tra i modelli, tra cui le associazioni uno-a-uno, uno-a-molti, molti-a-molti e polimorfiche. Queste relazioni consentono agli sviluppatori di costruire sistemi complessi in grado di gestire un'ampia gamma di dati in modo efficiente e flessibile.
In questo capitolo vedremo le relazioni standard che si possono venire a creare in un database relazionale mentre nel capitolo successivo vedremo le relazioni polimorfiche che sono risolte lato software.
Quindi in questo capitolo tratteremo le seguenti relazioni:
- Has One (uno a uno)
- Has Many (uno a molti)
- Belongs To (inverso di hasOne e hasMany)
- Many To Many
- Has One Of Many
- Has One Through
- Has Many Through
Has One
Per spiegare la relazione 1 a 1, useremo il modello DrivingLicense, per il quale non abbiamo generato dei dati. Rimediamo subito, creando una factory.
php artisan make:factory DrivingLicense
Nel metodo definition() di questa Factory definiamo i campi come segue
public function definition()
{
return [
'number' => fake()->creditCardNumber,
'valided_until_at' => fake()->creditCardExpirationDate()
];
}
Vediamo che non abbiamo impostato nessun valore per il campo user_id in quanto lo gestiremo nel momento della creazione del record. Apriamo il modello User e impostiamo la relazione con il modello DrivingLicense. Useremo la relazione hasOne() dal momento che la foreign key si trova nella tabella driving_licenses.
public function drivingLicense()
{
return $this->hasOne(DrivingLicense::class);
}
Ora apriamo il modello DrivingLicense e definiamo la relazione inversa con l'utente
public function user()
{
return $this->belongsTo(User::class);
}
Voglio fare una precisazione; tutto ciò funzionerà senza ulteriori implementazioni perché io ho rispettato lo standard di Eloquent. Quindi ad una tabella definita con il nome al plurale corrisponde un modello con il nome al singolare. Inoltre ho definito correttamente i nomi delle chiavi primarie e delle foreign key.
Per queste ragioni, non c'è stato bisogno di definire nelle relazioni i valori relativi alla foreign key e alla chiave locale, come nell'esempio seguente.
public function drivingLicense()
{
return $this->hasOne(DrivingLicense::class, 'user_id', 'id');
}
Adesso creiamo un nuovo seeder per generare le patenti degli utenti. Lo chiamiamo GenerateDrivingLicense e definiamo il metodo run() come segue
use App\Models\User;
use App\Models\DrivingLicense;
public function run()
{
$users = User::all();
foreach ($users as $user) {
$user->drivingLicense()->create(DrivingLicense::factory()->make()->toArray());
}
}
Come potete vedere all'interno del ciclo abbiamo invocato il metodo drivingLicense() del modello User che ci restituisce un'istanza del modello DrivingLicense. A quel punto possiamo utilizzare il metodo create() di Eloquent (che già conosciamo) e passargli l’array generato dalla factory.
Facciamo girare il seeder e controlliamo se i dati sono stati inseriti. Per farlo usiamo Tinker.
php artisan tinker
Una volta avviata la console utilizziamo il modello User e il metodo with() di Eloquent che ci permette di agganciare all'oggetto le relazioni che passeremo come parametro.
User::with('drivingLicense')->first()
Come vediamo, l'oggetto User adesso dispone di una nuova proprietà: la relazione uno a uno con il modello DrivingLicense.
È Importante sottolineare che anche senza il metodo with() è possibile accedere a qualsiasi relazione una volta che l'oggetto è stato istanziato.
$user = User::first();
dd($user->drivingLicense);
Con il primo approccio abbiamo optato per l’eager loading, con il secondo abbiamo usato il lazy loading. C'è una differenza sostanziale tra eager loading e lazy loading in Eloquent:
Eager loading recupera tutti i dati correlati nello stesso momento in cui viene effettuata una query al database.
Lazy loading comporta il recupero dei dati dal database solo quando viene esplicitamente richiesto.
Entrambi gli approcci hanno i loro pro e contro; il caricamento eager è ideale per gli scenari in cui il volume dei dati è importante, ma può causare problemi quando ci troviamo di fronte a relazioni complesse e profondamente annidate. Il lazy loading offre maggiore flessibilità, ma può richiedere una maggiore codifica per recuperare i dati relazionati. In definitiva, l'approccio migliore dipende dalle esigenze e preferenze dello sviluppatore.
Has Many
Adesso vediamo come gestire una relazione uno a molti, nello specifico il nostro modello Producer che può essere associato a diverse istanze del modello CarModel. Apriamo il modello Producer e aggiungiamo il metodo per associare i records della tabella car_models
public function carModels()
{
return $this->hasMany(CarModel::class);
}
Definiamo l'inverso nel modello CarModel come illustrato nello snippet seguente
public function producer()
{
return $this->belongsTo(Producer::class);
}
All'interno di questo modello definiamo anche la relazione uno a molti con il modello Car
public function cars()
{
return $this->hasMany(Car::class);
}
Da questo momento siamo in grado di risalire a tutti i modelli associati ad un produttore e viceversa. Arrivati a questo punto del nostro percorso, ritengo che possiamo introdurre il metodo whereHas() di Eloquent, un potente strumento che consente d’interrogare il database sulla base di dati correlati.
Questo metodo accetta due parametri: il nome della relazione e una callback dove definire la subquery. Facciamo un esempio su Tinker, contando tutti i CarModel che hanno una relazione con il Producer con id = 1.
CarModel::with('cars')->whereHas('producer', function($sql){
$sql->where('id', 1);
})->count();
All’interno della closure possiamo anche concatenare ulteriori chiamate a whereHas(), in modo da definire facilmente query complesse. Nell’esempio che segue, voglio contare i Produttori che hanno dei Modelli associati a delle Automobili la cui targa comincia con 05.
Producer::whereHas('carModels', function ($sql) {
$sql->whereHas('cars', function ($sql) {
$sql->where('license_plate', 'like', '05%');
});
})->count();
Possiamo semplificare il costrutto, usando la dot annotation per concatenare il metodo carModels() del modello Producer al metodo cars() del modello CarModel.
Producer::whereHas('carModels.cars', function ($sql) {
$sql->where('license_plate', 'like', '05%');
})->count();
Pertanto, il metodo whereHas() offre un modo comodo ed efficiente per accedere rapidamente ai dati correlati all'interno delle applicazioni Laravel.
Belongs To
Il metodo belongsTo di Eloquent definisce relazioni uno a molti o uno a uno, dove le foreign key sono definite nella tabella del modello da relazionare. Nella nostra applicazione di prova, il modello Car è perfetto per dimostrare il funzionamento di questa relazione, in quanto abbiamo definito nella tabella cars il vincolo relazionale con la tabella car_models.
Apriamo il modello Car e definiamo il relativo metodo di relazionamento
public function carModel()
{
return $this->belongsTo(CarModel::class);
}
Apriamo il nostro comodissimo Tinker e facciamo qualche query
Car::with('carModel')->first();
Anche con il metodo with() possiamo andare più in profondità, ricavando il produttore di questa automobile. La sintassi è la seguente:
Car::with('carModel.producer')->first();
Many To Many
In fase di progettazione, abbiamo definito una tabella pivot per relazionare tutte le macchine che un utente ha posseduto e di conseguenza tutti i proprietari che una macchina ha avuto.
Apriamo il modello Car e andiamo a definire il metodo per gestire la relazione fino al modello User, passando per il modello CarUser
public function users()
{
return $this->belongsToMany(User::class)
->using(CarUser::class)
->withTimestamps()
->withPivot(['created_at', 'ended_at']);
}
Facciamo la stessa cosa con la relazione inversa nel modello User
public function cars()
{
return $this->belongsToMany(Car::class)
->using(CarUser::class)
->withTimestamps()
->withPivot(['created_at', 'ended_at']);
}
Ancora una volta useremo tinker per fare qualche test. Cominciamo con qualcosa di semplice: associamo la macchina con ID 1 a due utenti presi a caso dal database.
$users = User::inRandomOrder()->limit(2)->get()->pluck('id')->toArray();
$car = Car::find(1);
$car->users()->sync($users);
Nella variabile $users ho memorizzato un array che ho ricavato attraverso la concatenazione di diversi metodi di Eloquent. Per rendere il risultato casuale ho usato il metodo inRandomOrder() e per limitare l’offset della query ho utilizzato il metodo limit(). Il metodo get(), come sappiamo restituisce una collezione di dati, quindi per ricavare solo gli id dalla collezione ho usato il metodo pluk() di Eloquent/Collection e infine ho trasformato la collezione in una semplice array.
A questo punto ho potuto utilizzare la relazione molti a molti del modello Car verso il modello User, per sincronizzare la tabella pivot con l’array appena ricavato.
Per riportare i dati alla situazione di partenza, ci basterà passare un array vuoto al metodo sync()
Car::find(1)->users()->sync([]);
Abbiamo anche a disposizione i metodi attach() e detach() che fanno sostanzialmente la stessa cosa del metodo sync(), con la differenza che accettano un singolo elemento (possiamo passare sia l’istanza del Modello, sia l’id).
$user = User::find(1);
Car::find(1)->users()->attach($user);
// equivalente a:
Car::find(1)->users()->attach($user->id);
Adesso diamo un’occhiata più da vicino a quello che ci restituisce Eloquent quando proviamo ad estrarre i dati di una relazione molti a molti
Car::has('users')->with('users')->first();
Come vediamo, il modello Car contiene la collection users, composta dalle istanze del modello User alle quali è stata iniettata un’ulteriore istanza: quella del Pivot CarUser. Questo accade perché noi abbiamo impostato le relazioni belongsToMany, indicando a Eloquent che deve usare quella classe.
Nella pivot è presente un campo, ended_at. Lo abbiamo creato per sapere quando un utente cessa di essere proprietario di una determinata macchina. Per popolare questo campo possiamo usare una sintassi del genere.
Car::has('users')->first()->users()->update(['ended_at' =>now()]);
Prima di continuare facciamo qualche altra associazione random, giusto per avere una situazione nel database un pò più corposa
Filtrare i risultati in base alla tabella Pivot
Nella precedente lezione abbiamo parlato del metodo whereHas() e abbiamo visto come filtrare i records in base ai dati presenti nella tabella relazionata. Nella relazione Many To Many, la situazione è più complessa in quanto c’è una tabella pivot in mezzo. Si pensi ad esempio alla situazione in cui bisogna estrarre dal database le automobili e i relativi proprietari attuali.
Per fortuna Eloquent dispone di un insieme di metodi che filtrano i dati della Pivot. Vediamo quindi come codificare quanto esposto nell’esempio del paragrafo precedente. Apriamo il modello Car e aggiungiamo il seguente metodo
public function owners()
{
return $this->users()->wherePivotNull('ended_at');
}
Adesso, se abbiamo impostato qualche record con il campo ended_at nullo, saremmo in grado di visualizzare i dati che ci interessano utilizzando la seguente sintassi
Car::has('owners')->with('owners')->get();
Per l'elenco completo di tutti i metodi che si possono utilizzare per filtrare i dati di una pivot vi rimando come sempre alla .
Has One of Many
Qualche volta potremmo avere la necessità di mostrare un singolo record a partire da una relazione Has Many. Eloquent ci dà la possibilità di farlo grazie al metodo ofMany() che appartiene alla classe Illuminate\Database\Eloquent\Relations\HasOne.
Nei nostri esempi, abbiamo un modello Producer che ha una relazione 1 a molti con il modello CarModel. Se volessimo ricavare velocemente il CarModel più costoso per ogni Producer, potremmo implementare questo metodo.
public function expensivest()
{
return $this->hasOne(CarModel::class)->ofMany('price', 'max');
}
Analogamente possiamo definire la relazione con il modello più economico, semplicemente cambiando il secondo parametro del metodo ofMany()
public function cheapest()
{
return $this->hasOne(CarModel::class)->ofMany('price', 'min');
}
Vediamo le relazioni che abbiamo appena impostato all’opera
È possibile implementare la query, infatti il metodo ofMany() accetta anche due array come parametri: nel primo specifichiamo delle coppie chiave/valore composte da campo e clausola di aggregazione, mentre nel secondo parametro impostiamo una callback per raffinare la query.
public function cheapest()
{
return $this->hasOne(CarModel::class)
->ofMany([
'price' => 'min'
], function($sql) {
$sql->where('created_at', '<', now());
});
}
Ovviamente questo è un esempio che non ha alcun senso ma ci serve per fissare il concetto.
Relazioni attraverso un modello intermedio
Eloquent ci mette a disposizione anche dei metodi per risalire facilmente a delle relazioni tra modelli non direttamente collegati tra di loro, ma che hanno in comune un modello intermedio.
I due strumenti che abbiamo a disposizione sono Has One Through e Has Many Through, che come possiamo facilmente intuire servono rispettivamente per recuperare uno e più record, passando per il modello intermedio.
Has One Through
La relazione Has One Through, come abbiamo anticipato, definisce una relazione uno a uno con un modello che non è direttamente collegato con il modello dichiarante, ma può essere recuperato attraverso l'istanza di un modello intermedio.
Esaminiamo lo schema del nostro database e vediamo qual è l'esempio più calzante che possiamo prendere per dimostrare questo concetto
Direi che possiamo provare a risalire al produttore di ogni macchina, passando attraverso la tabella car_models
Quindi abbiamo il modello Car e definiamo la relazione in oggetto
public function producer()
{
return $this->hasOneThrough(Producer::class, CarModel::class);
}
Il metodo hasOneThrough() ha bisogno di due parametri obbligatori: il primo è il modello a cui dobbiamo accedere mentre il secondo è il modello intermedio. Apriamo tinker e proviamo ad estrarre il dato
Car::with('producer')->get();
Come vediamo dall’immagine, la console ci ha restituito un errore. Questo perché la struttura del database è diversa dalla logica con cui Eloquent cerca di risolvere la query. Per fortuna abbiamo la possibilità di cambiare la query attraverso dei parametri aggiuntivi del metodo hasOneThrough().
public function producer()
{
return $this->hasOneThrough(Producer::class, CarModel::class,
'id', // Foreign key nella tabella intermedia
'id', // Foreign key nella tabella obiettivo
'car_model_id', // Local key nella tabella di partenza
'producer_id' // Local key nella tabella intermedia
);
}
Nei commenti per ogni elemento dei parametri aggiuntivi ho spiegato a cosa si riferisce l'elemento. Rilanciamo il comando su tinker (dopo averlo riavviato) e controlliamo se l’output è corretto
Has Many Through
Questa relazione funziona in maniera molto simile alla relazione Has One Through, per cui dal modello dichiarante possiamo arrivare ai records di un modello non direttamente relazionato, passando attraverso un modello intermedio. L’unica differenza consiste nel fatto che il risultato non sarà l'istanza di un modello ma bensì una collezione di Eloquent.
Ad esempio dal modello Producer possiamo ottenere i record della modello Car, passando attraverso il modello CarModel.
Quindi apriamo il modello Producer e scriviamo il metodo per arrivare ai records del modello Cars
public function cars()
{
return $this->hasManyThrough(Car::class, CarModel::class);
}
Apriamo tinker e lanciamo una semplice query per vedere se la nostra relazione funziona
Producer::has('cars')->with('cars')->get();
In questo caso la relazione ha funzionato senza dover specificare i nomi dei campi per relazionare le tre tabelle. In ogni modo ricordiamoci che possiamo forzare il Query Builder, impostando i nomi dei campi, esattamente come abbiamo fatto per la relazione Has One Through.
Relazioni Polimorfiche
Le relazioni polimorfiche sono uno dei concetti più interessanti di Eloquent. In pratica consistono in relazioni risolte lato software dove, un dato modello può essere relazionato con altri modelli, senza alterare la struttura delle tabelle.
Ad esempio potremmo avere un tabella tags che può essere associata a tutte le altre tabelle che compongono la nostra applicazione.
Le relazioni polimorfiche che possono essere di tre tipi:
- uno a uno
- uno a molti
- molti a molti
Per le prime due relazioni possiamo definire solo una tabella pivot mentre nell'ultima relazione, quella molti a molti, dobbiamo creare due tabelle. Per queste ragioni, nel nostro tutorial creeremo due entità: Note e Tag. Le note ci serviranno per illustrare le relazioni polimorfiche uno a uno e uno a molti, mentre con i tags vedremo le relazioni di tipo molti a molti.
Relazione polimorfica Uno a Uno
In questa lezione andremo a creare una tabella notes, i cui record potranno essere relazionati a tutte le tabelle che riterremo utili ai nostri scopi. Nell'introduzione di questo capitolo abbiamo specificato che le relazioni polimorfiche, a patto che non siano molti a molti, hanno bisogno di una singola tabella di appoggio. Quindi creiamo attraverso Artisan, modello e migration per questa entità
php artisan make:model Note -m
La migration conterrà pochi campi: un ID autoincrementante, un campo di testo e due campi per identificare tabella e id del record relazionato.
public function up()
{
Schema::create('notes', function (Blueprint $table) {
$table->id();
$table->mediumText('body');
$table->morphs('noteable');
$table->timestamps();
});
}
Il metodo morphs() scriverà due campi nella tabella, usando come prefisso il parametro che gli abbiamo passato. Inoltre creerà anche un indice concatenando i campi per alleggerire le future query.
Lanciamo il comando per far girare le migration e nel database dovremmo avere la nuova tabella
Ora apriamo il modello Note e apportiamo le seguenti implementazioni:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Note extends Model
{
use HasFactory;
protected $guarded = [];
public function noteable()
{
return $this->morphTo();
}
}
Il meccanismo della proprietà $guarded lo abbiamo già spiegato a suo tempo quindi concentriamoci sul metodo notable(). Questo metodo ci serve per ricavare l'istanza del modello dichiarante quando questo invocherà la relazione polimorfica con il modello Note.
Volendo dare una dimostrazione pratica, associamo il modello Note ai modelli Producer e DrivingLicense. Il metodo sarà identico per entrambi, quindi lo riporto una volta sola.
public function note()
{
return $this->morphOne(Note::class, 'noteable');
}
Per testare il funzionamento, creiamo un seeder per associare records della tabella notes ai modelli di cui sopra.
php artisan make:seeder GenerateSomeFakeSingleNote
Visto che ormai siamo esperti nel creare seeders e farli girare, vi riporto solamente l’implementazione del metodo run()
public function run()
{
// Note per i produttori
$producers = Producer::inRandomOrder()->limit(8)->get();
foreach ($producers as $producer) {
$producer->note()->create([
'body' => fake()->sentence()
]);
}
// Note per le patenti
$driving_licenses = DrivingLicense::inRandomOrder()->limit(8)->get();
foreach ($driving_licenses as $driving_license) {
$driving_license->note()->create([
'body' => fake()->sentence()
]);
}
}
Dopo che il seeder ha finito di girare, andiamo a dare un'occhiata ai records della tabella notes.
Le due colonne su cui dobbiamo portare l'attenzione sono noteable_type e noteable_id che, come detto, fanno riferimento al modello relazionato e al suo relativo ID
Abbiamo anche detto che sono relazioni risolte lato software, sebbene sia sempre possibile fare delle Join con SQL, ma avrete sicuramente delle difficoltà per quanto riguarda i backlash. Per il momento Lasciamo perdere questo argomento e vediamo come, attraverso il modello producer, possiamo risalire alla sua relazione polimorfica 1 a 1
Producer::has('note')->with('note')->get();
Se lanciamo questo comando, vedremo che sarà restituita una collection di dati in cui ogni elemento ha la sua relazione polimorfica con il modello Note.
Relazione polimorfica Uno a Molti
La relazione polimorfica uno a molti non è molto diversa dalla relazione che abbiamo esaminato in precedenza. Come possiamo facilmente intuire, la differenza sostanzialmente sta nel fatto che in questo caso ogni modello può avere più di una riga nella morph.
Il database rimarrà invariato, dobbiamo solamente aggiungere nei modelli il metodo per gestire la relazione polimorfica uno a molti. Per cui apriamo il modello Producer e il modello DrivingLicense e sostituiamo il metodo note() con il nuovo metodo notes()
public function notes()
{
return $this->morphMany(Note::class, 'noteable');
}
Adesso, lanciando questo comando su Tinker
Producer::has('notes')->with('notes')->get();
Noteremo che i vari elementi che compongono la Collection hanno al loro interno la proprietà notes, un'altra Collection relativa ai record della tabella polimorfica che sono stati relazionati lanciando il seeder nel capitolo precedente.
Relazione polimorfica Molti a Molti
Adesso le cose si complicano leggermente. Dobbiamo gestire una situazione in cui abbiamo una tabella polimorfica, quindi che accetta modelli diversi tra loro, ma lo stesso record può essere associato a qualsiasi riga delle entità coinvolte in questa relazione.
In sostanza avremo una tabella contenente le informazioni del modello da relazionare e un’ulteriore tabella pivot che gestirà la relazione polimorfica vera e propria.
Lanciamo il comando per ripristinare le migrations al batch precedente, in questo modo cancelleremo la tabella notes
php artisan migrate:rollback
Adesso apriamo il file relativo alla migration della tabella notes e nel metodo up() andremo a creare due tabelle
public function up()
{
Schema::create('notes', function (Blueprint $table) {
$table->id();
$table->mediumText('body');
$table->timestamps();
});
Schema::create('noteables', callback: function (Blueprint $table) {
$table->integer('note_id')->unsigned();
$table->morphs('noteable');
});
}
Ovviamente avrei potuto separare le due dichiarazioni in due migrations distinte, ma personalmente trovo più pratico utilizzare un unico file per gestire questo tipo di relazioni.
Dobbiamo ricordarci d’implementare il metodo down() in modo che droppi entrambe le tabelle
public function down()
{
Schema::dropIfExists('noteables');
Schema::dropIfExists('notes');
}
Facciamo girare le migrations e andiamo a modificare i modelli. Negli esempi precedenti avevamo modificato i modelli Producer e DrivingLicense, quindi interveniamo nel metodo notes() di entrambi
public function notes()
{
return $this->morphToMany(Note::class, 'noteable');
}
Adesso apriamo il modello Note, dove dobbiamo rimuovere il metodo noteable() e implementare tutti i metodi inversi della relazione polimorfica molti a molti.
public function producers()
{
return $this->morphedByMany(Producer::class, 'noteable');
}
public function drivingLicenses()
{
return $this->morphedByMany(DrivingLicense::class, 'noteable');
}
Per testare il tutto, creiamo una Factory per generare i record nella tabella notes.
php artisan make:factory Note
Questo modello ha bisogno di un solo campo valorizzato, quindi la nostra factory sarà molto semplice.
public function definition()
{
return [
'body' => fake()->sentence()
];
}
Creiamo anche un seeder che andremo a commentare insieme in tutti i suoi passaggi
php artisan make:seeder GenerateSomeFakeNotes
E questo è il codice del metodo run()
public function run()
{
// Creo qualche nota fake
Note::factory()->count(10)->create();
// Faccio un ciclo min 10 max 30 iterazioni
for ($i = 0; $i <= random_int(10, 30); $i++) {
// Istanzio un modello a seconda della iterazione
$model = ($i % 2 === 0) ? new Producer() : new DrivingLicense();
// Estraggo una nota a caso
$note = Note::inRandomOrder()->first();
// Associo la nota al modello solo se la relazione non esiste
$items = $model->whereDoesntHave('notes', function ($sql) use ($note) {
$sql->where('id', $note->id);
})->inRandomOrder()->limit(random_int(1, 3))->get();
foreach ($items as $item) {
$item->notes()->attach($note);
}
}
}
Nella prima parte del metodo, attraverso la Factory ho creato qualche record nella tabella notes dopodiché ho impostato un ciclo con un numero di iterazioni variabili. All’interno del ciclo istanzio alternativamente i due modelli relazionati alla tabella polimorfica.
Estraggo una nota random dal database e dei records dalla tabelle producers e driving_licenses che non hanno ancora una relazione con la nota che ho appena estratto. Questo è possibile attraverso il metodo whereDoesntHave(), i cui parametri sono il nome della reazione e la closure per effettuare la subquery.
Una volta ottenuti questi records, li ciclo e sfruttando il metodo notes() dell'oggetto, aggancio l'istanza del modello Note.
I metodi che abbiamo a disposizione per gestire questo tipo di relazione sono gli stessi che abbiamo incontrato finora:
- attach - Relaziona il modello dichiarante con l’oggetto passato come parametro
- sync - Sincronizza la relazione del modello dichiarante con gli elementi passati come parametro
- sync - Slega il modello dichiarante con l’oggetto passato come parametro
- has - Controlla se il modello dichiarante ha una relazione con l’entità passata come parametro
- doesntHave - Controlla se il modello dichiarante ha una relazione con l’entità passata come parametro
- whereHas - Controlla se la suquery passata nei parametri produce risultati
- whereDoesntHave - Controlla se la subquery passata nei parametri NON produce risultati