Prologo
Primi Passi
Architettura
Le Basi
- Routing
- Middleware
- Protezione da CSRF
- Controller
- Richieste
- Risposte
- Views
- Blade
- Vite
- URL
- Sessioni
- Validazione
- Errori
- Logging
Approfondimenti
- Artisan
- Broadcasting
- Cache
- Collezioni
- Concorrenza
- Contesto
- Contratti
- Eventi
- File System
- Helpers
- Client HTTP
- Localizzazione
- Notifiche
- Sviluppo di Package
- Processi
- Code
- Rate-limiting
- Stringhe
- Scheduling di Task
Sicurezza
Database
Eloquent ORM
Testing
Package
Relazioni
- Introduzione
- Definizione delle Relazioni
- Relazioni Molti-a-Molti
- Relazioni Polimorfiche
- Interrogare le Relazioni
- Aggregazione dei Modelli Relazionati
- Eager Loading
- Inserimento e Aggiornamento dei Model Correlati
- Toccare i Timestamp del Genitore
Introduzione
Le tabelle del database spesso sono correlate tra loro. Ad esempio, un post del blog può avere molti commenti oppure un ordine potrebbe essere legato all’utente che lo ha effettuato. Eloquent rende semplice gestire e lavorare con queste relazioni e supporta una varietà di relazioni comuni:
- One to One
- One to Many
- Many to Many
- Has One Through
- Has Many Through
- One to One (Polimorfica)
- One to Many (Polimorfica)
- Many to Many (Polimorfica)
Definizione delle Relazioni
Le relazioni in Eloquent sono definite come metodi nelle tue classi modello Eloquent. Poiché le relazioni fungono anche da potenti query builder, definire le relazioni come metodi offre potenti capacità di concatenazione di metodi e di esecuzione delle query. Ad esempio, possiamo aggiungere ulteriori vincoli alla query su questa relazione posts
:
$user->posts()->where('active', 1)->get();
Ma, prima di approfondire l’uso delle relazioni, impariamo come definire ogni tipo di relazione supportata da Eloquent.
One to One / Has One
Una relazione uno a uno è un tipo molto semplice di relazione nel database. Ad esempio, un modello User
potrebbe essere associato a un modello Phone
. Per definire questa relazione, posizioneremo un metodo phone
sul modello User
. Il metodo phone
deve chiamare il metodo hasOne
e restituire il suo risultato. Il metodo hasOne
è disponibile per il tuo modello tramite la classe base Illuminate\Database\Eloquent\Model
del modello:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class User extends Model
{
/**
* Get the phone associated with the user.
*/
public function phone(): HasOne
{
return $this->hasOne(Phone::class);
}
}
Il primo argomento passato al metodo hasOne
è il nome della classe del modello correlato. Una volta definita la relazione, possiamo recuperare il record correlato utilizzando le proprietà dinamiche di Eloquent. Le proprietà dinamiche ti permettono di accedere ai metodi di relazione come se fossero proprietà definite sul modello:
$phone = User::find(1)->phone;
Eloquent determina la chiave esterna della relazione basandosi sul nome del modello padre. In questo caso, si presume automaticamente che il modello Phone
abbia una chiave esterna user_id
. Se desideri sovrascrivere questa convenzione, puoi passare un secondo argomento al metodo hasOne
:
return $this->hasOne(Phone::class, 'foreign_key');
Inoltre, Eloquent presume che la chiave esterna debba avere un valore corrispondente alla colonna della chiave primaria del modello padre. In altre parole, Eloquent cercherà il valore della colonna id
dell’utente nella colonna user_id
del record Phone
. Se desideri che la relazione utilizzi un valore di chiave primaria diverso da id
o dalla proprietà $primaryKey
del tuo modello, puoi passare un terzo argomento al metodo hasOne
:
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
Definire l’inverso della relazione
Quindi, possiamo accedere al modello Phone
dal nostro modello User
. Successivamente, definiamo una relazione sul modello Phone
che ci permetterà di accedere all’utente che possiede il telefono. Possiamo definire l’inverso di una relazione hasOne
usando il metodo belongsTo
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Invocando il metodo user
, Eloquent cercherà di trovare un modello User
il cui id
corrisponde alla colonna user_id
nel modello Phone
.
Eloquent determina il nome della chiave esterna esaminando il nome del metodo di relazione e aggiungendo il suffisso _id
. Quindi, in questo caso, Eloquent presuppone che il modello Phone
abbia una colonna user_id
. Tuttavia, se la chiave esterna nel modello Phone
non è user_id
, puoi passare un nome di chiave personalizzato come secondo argomento al metodo belongsTo
:
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key');
}
Se il modello padre non utilizza id
come chiave primaria, o desideri trovare il modello associato utilizzando una colonna diversa, puoi passare un terzo argomento al metodo belongsTo
specificando la chiave personalizzata della tabella padre:
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
Uno a molti / Has Many
Una relazione uno a molti viene utilizzata per definire relazioni in cui un singolo modello è il genitore di uno o più modelli figli. Per esempio, un post del blog può avere un numero infinito di commenti. Come tutte le altre relazioni di Eloquent, le relazioni uno a molti sono definite creando un metodo sul tuo modello Eloquent:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
}
Ricorda che Eloquent determinerà automaticamente la colonna della chiave esterna corretta per il modello Comment
. Per convenzione, Eloquent prenderà il nome del modello genitore in "snake case" e lo aggiungerà con il suffisso _id
. Quindi, in questo esempio, Eloquent presumerà che la colonna della chiave esterna nel modello Comment
sia post_id
.
Una volta definito il metodo della relazione, possiamo accedere alla collezione di commenti correlati accedendo alla proprietà comments
. Ricorda che, poiché Eloquent fornisce "proprietà di relazione dinamiche", possiamo accedere ai metodi delle relazioni come se fossero proprietà definite sul modello:
use App\Models\Post;
$comments = Post::find(1)->comments;
foreach ($comments as $comment) {
// ...
}
Poiché tutte le relazioni fungono anche da builder di query, puoi aggiungere ulteriori vincoli alla query della relazione chiamando il metodo comments
e continuando a concatenare condizioni sulla query:
$comment = Post::find(1)->comments()
->where('title', 'foo')
->first();
Come il metodo hasOne
, puoi anche sovrascrivere le chiavi esterne e locali passando argomenti aggiuntivi al metodo hasMany
:
return $this->hasMany(Comment::class, 'foreign_key');
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
Idratare automaticamente i modelli genitore sui figli
Anche quando si utilizza l’eager loading di Eloquent, possono sorgere problemi di query "N + 1" se tenti di accedere al modello genitore da un modello figlio mentre cicli attraverso i modelli figli:
$posts = Post::with('comments')->get();
foreach ($posts as $post) {
foreach ($post->comments as $comment) {
echo $comment->post->title;
}
}
Nell’esempio sopra, si è introdotto un problema di query "N + 1" perché, anche se i commenti sono stati eager loaded per ogni modello Post
, Eloquent non idrata automaticamente il genitore Post
su ogni modello figlio Comment
.
Se desideri che Eloquent idrati automaticamente i modelli genitore nei loro figli, puoi invocare il metodo chaperone
quando definisci una relazione hasMany
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Post extends Model
{
/**
* Ottieni i commenti per il post del blog.
*/
public function comments(): HasMany
{
return $this->hasMany(Comment::class)->chaperone();
}
}
Oppure, se preferisci abilitare l’idratazione automatica dei genitori a runtime, puoi invocare il metodo chaperone
quando carichi eager la relazione:
use App\Models\Post;
$posts = Post::with([
'comments' => fn ($comments) => $comments->chaperone(),
])->get();
One to Many (Inverso) / Belongs To
Ora che possiamo accedere a tutti i commenti di un post, definiamo una relazione che permetta a un commento di accedere al suo post padre. Per definire l’inverso di una relazione hasMany
, definisci un metodo di relazione nel modello figlio che chiama il metodo belongsTo
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Comment extends Model
{
/**
* Ottieni il post che possiede il commento.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}
Una volta definita la relazione, possiamo recuperare il post padre di un commento accedendo alla proprietà dinamica di relazione post
:
use App\Models\Comment;
$comment = Comment::find(1);
return $comment->post->title;
Nell’esempio sopra, Eloquent tenterà di trovare un modello Post
che ha un id
che corrisponde alla colonna post_id
nel modello Comment
.
Eloquent determina il nome della chiave esterna predefinita esaminando il nome del metodo di relazione e aggiungendo un _
seguito dal nome della colonna della chiave primaria del modello padre. Quindi, in questo esempio, Eloquent assumerà che la chiave esterna del modello Post
nella tabella comments
sia post_id
.
Tuttavia, se la chiave esterna per la tua relazione non segue queste convenzioni, puoi passare un nome di chiave esterna personalizzato come secondo argomento al metodo belongsTo
:
/**
* Ottieni il post che "possiede" il commento.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key');
}
Se il tuo modello padre non usa id
come chiave primaria, o desideri trovare il modello associato utilizzando una colonna diversa, puoi passare un terzo argomento al metodo belongsTo
specificando la chiave personalizzata della tua tabella padre:
/**
* Ottieni il post che "possiede" il commento.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}
Modelli Predefiniti
Le relazioni belongsTo
, hasOne
, hasOneThrough
e morphOne
ti permettono di definire un modello predefinito che verrà restituito se la relazione specificata è null
. Questo pattern è spesso chiamato Null Object pattern e può aiutare a rimuovere controlli condizionali nel tuo codice. Nell’esempio seguente, la relazione user
restituirà un modello vuoto App\Models\User
se nessun utente è associato al modello Post
:
/**
* Ottieni l'autore del post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault();
}
Per popolare il modello predefinito con attributi, puoi passare un array o una closure al metodo withDefault
:
/**
* Ottieni l'autore del post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Autore Ospite',
]);
}
/**
* Ottieni l'autore del post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) {
$user->name = 'Autore Ospite';
});
}
Interrogare le Relazioni "Belongs To"
Quando interroghi i figli di una relazione "belongs to", puoi costruire manualmente la clausola where
per recuperare i modelli Eloquent corrispondenti:
use App\Models\Post;
$posts = Post::where('user_id', $user->id)->get();
Tuttavia, potrebbe essere più comodo usare il metodo whereBelongsTo
, che determinerà automaticamente la relazione corretta e la chiave esterna per il modello fornito:
$posts = Post::whereBelongsTo($user)->get();
Puoi anche fornire un’istanza di collection al metodo whereBelongsTo
. In questo modo, Laravel recupererà i modelli che appartengono a uno qualsiasi dei modelli genitori nella collezione:
$users = User::where('vip', true)->get();
$posts = Post::whereBelongsTo($users)->get();
Per impostazione predefinita, Laravel determinerà la relazione associata al modello fornito basandosi sul nome della classe del modello; tuttavia, puoi specificare manualmente il nome della relazione fornendolo come secondo argomento al metodo whereBelongsTo
:
$posts = Post::whereBelongsTo($user, 'author')->get();
Has One di Molti
A volte un modello può avere molti modelli correlati, ma desideri recuperare facilmente il modello "più recente" o "più vecchio" della relazione. Ad esempio, un modello User
può essere correlato a molti modelli Order
, ma vuoi definire un modo comodo per interagire con l’ordine più recente che l’utente ha effettuato. Puoi ottenere questo usando il tipo di relazione hasOne
combinato con i metodi ofMany
:
/**
* Ottieni l'ordine più recente dell'utente.
*/
public function latestOrder(): HasOne
{
return $this->hasOne(Order::class)->latestOfMany();
}
Allo stesso modo, puoi definire un metodo per recuperare il modello correlato "più vecchio", o primo, di una relazione:
/**
* Ottieni l'ordine più vecchio dell'utente.
*/
public function oldestOrder(): HasOne
{
return $this->hasOne(Order::class)->oldestOfMany();
}
Per impostazione predefinita, i metodi latestOfMany
e oldestOfMany
recuperano il modello correlato più recente o più vecchio basato sulla chiave primaria del modello, che deve essere ordinabile. Tuttavia, a volte potresti voler recuperare un singolo modello da una relazione più ampia utilizzando un criterio di ordinamento diverso.
Ad esempio, usando il metodo ofMany
, puoi recuperare l’ordine più costoso dell’utente. Il metodo ofMany
accetta la colonna ordinabile come primo argomento e quale funzione di aggregazione (min
o max
) applicare quando si interroga il modello correlato:
/**
* Ottieni l'ordine più grande dell'utente.
*/
public function largestOrder(): HasOne
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}
Poiché PostgreSQL non supporta l’esecuzione della funzione
MAX
sulle colonne UUID, attualmente non è possibile utilizzare relazioni one-of-many in combinazione con colonne UUID di PostgreSQL.
Convertire le relazioni "Many" in relazioni "Has One"
Spesso, quando si recupera un singolo modello utilizzando i metodi latestOfMany
, oldestOfMany
o ofMany
, hai già definita una relazione "has many" per lo stesso modello. Per comodità, Laravel permette di convertire facilmente questa relazione in una relazione "has one" invocando il metodo one
sulla relazione:
/**
* Ottieni gli ordini dell'utente.
*/
public function orders(): HasMany
{
return $this->hasMany(Order::class);
}
/**
* Ottieni l'ordine più grande dell'utente.
*/
public function largestOrder(): HasOne
{
return $this->orders()->one()->ofMany('price', 'max');
}
Relazioni Avanzate "Has One of Many"
È possibile costruire relazioni "has one of many" più avanzate. Ad esempio, un modello Product
può avere molti modelli Price
associati che vengono mantenuti nel sistema anche dopo che sono state pubblicate nuove tariffe. Inoltre, i nuovi dati di prezzo per il prodotto possono essere pubblicati in anticipo per entrare in vigore in una data futura tramite una colonna published_at
.
In sintesi, dobbiamo recuperare l’ultima tariffa pubblicata la cui data di pubblicazione non è nel futuro. Inoltre, se due prezzi hanno la stessa data di pubblicazione, preferiremo il prezzo con l’ID maggiore. Per fare ciò, dobbiamo passare un array al metodo ofMany
che contiene le colonne ordinabili che determinano l’ultimo prezzo. Inoltre, verrà fornita una closure come secondo argomento al metodo ofMany
. Questa closure sarà responsabile di aggiungere ulteriori vincoli sulla data di pubblicazione alla query della relazione:
/**
* Ottieni il prezzo attuale per il prodotto.
*/
public function currentPricing(): HasOne
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function (Builder $query) {
$query->where('published_at', '<', now());
});
}
Has One Through
La relazione "has-one-through" definisce una relazione uno-a-uno con un altro modello. Tuttavia, questa relazione indica che il modello dichiarante può essere associato a un’istanza di un altro modello procedendo attraverso un terzo modello.
Per esempio, in un’applicazione di un’officina meccanica, ogni modello Mechanic
può essere associato a un modello Car
, e ogni modello Car
può essere associato a un modello Owner
. Sebbene il meccanico e il proprietario non abbiano una relazione diretta nel database, il meccanico può accedere al proprietario attraverso il modello Car
. Vediamo le tabelle necessarie per definire questa relazione:
mechanics
id - integer
name - string
cars
id - integer
model - string
mechanic_id - integer
owners
id - integer
name - string
car_id - integer
Ora che abbiamo esaminato la struttura delle tabelle per la relazione, definiamo la relazione nel modello Mechanic
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
class Mechanic extends Model
{
/**
* Ottieni il proprietario dell'auto.
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(Owner::class, Car::class);
}
}
Il primo argomento passato al metodo hasOneThrough
è il nome del modello finale a cui desideriamo accedere, mentre il secondo argomento è il nome del modello intermedio.
Oppure, se le relazioni rilevanti sono già state definite su tutti i modelli coinvolti nella relazione, puoi definire fluentemente una relazione "has-one-through" invocando il metodo through
e fornendo i nomi di quelle relazioni. Per esempio, se il modello Mechanic
ha una relazione cars
e il modello Car
ha una relazione owner
, puoi definire una relazione "has-one-through" che collega il meccanico e il proprietario in questo modo:
// Sintassi basata su stringhe...
return $this->through('cars')->has('owner');
// Sintassi dinamica...
return $this->throughCars()->hasOwner();
Convenzioni delle Chiavi
Verranno utilizzate le convenzioni standard delle chiavi esterne di Eloquent quando si eseguono le query delle relazioni. Se desideri personalizzare le chiavi della relazione, puoi passarle come terzo e quarto argomento al metodo hasOneThrough
. Il terzo argomento è il nome della chiave esterna sul modello intermedio. Il quarto argomento è il nome della chiave esterna sul modello finale. Il quinto argomento è la chiave locale, mentre il sesto argomento è la chiave locale del modello intermedio:
class Mechanic extends Model
{
/**
* Ottieni il proprietario dell'auto.
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(
Owner::class,
Car::class,
'mechanic_id', // Chiave esterna sulla tabella delle auto...
'car_id', // Chiave esterna sulla tabella dei proprietari...
'id', // Chiave locale sulla tabella dei meccanici...
'id' // Chiave locale sulla tabella delle auto...
);
}
}
Oppure, come discusso in precedenza, se le relazioni rilevanti sono già state definite su tutti i modelli coinvolti nella relazione, puoi definire fluentemente una relazione "has-one-through" invocando il metodo through
e fornendo i nomi di quelle relazioni. Questo approccio offre il vantaggio di riutilizzare le convenzioni delle chiavi già definite sulle relazioni esistenti:
// Sintassi basata su stringhe...
return $this->through('cars')->has('owner');
// Sintassi dinamica...
return $this->throughCars()->hasOwner();
Has Many Through
La relazione "has-many-through" offre un modo comodo per accedere a relazioni lontane tramite una relazione intermedia. Ad esempio, supponiamo di costruire una piattaforma di deployment come Laravel Vapor. Un modello Project
potrebbe accedere a molti modelli Deployment
attraverso un modello intermedio Environment
. Usando questo esempio, potresti facilmente raccogliere tutti i deployments per un determinato progetto. Vediamo le tabelle necessarie per definire questa relazione:
projects
id - integer
name - string
environments
id - integer
project_id - integer
name - string
deployments
id - integer
environment_id - integer
commit_hash - string
Ora che abbiamo esaminato la struttura delle tabelle per la relazione, definiamo la relazione sul modello Project
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class Project extends Model
{
/**
* Ottieni tutti i deployments per il progetto.
*/
public function deployments(): HasManyThrough
{
return $this->hasManyThrough(Deployment::class, Environment::class);
}
}
Il primo argomento passato al metodo hasManyThrough
è il nome del modello finale a cui vogliamo accedere, mentre il secondo argomento è il nome del modello intermedio.
Oppure, se le relazioni rilevanti sono già state definite su tutti i modelli coinvolti nella relazione, puoi definire fluentemente una relazione "has-many-through" invocando il metodo through
e fornendo i nomi di quelle relazioni. Ad esempio, se il modello Project
ha una relazione environments
e il modello Environment
ha una relazione deployments
, puoi definire una relazione "has-many-through" che collega il progetto e i deployments in questo modo:
// Sintassi basata su stringhe...
return $this->through('environments')->has('deployments');
// Sintassi dinamica...
return $this->throughEnvironments()->hasDeployments();
Anche se la tabella del modello Deployment
non contiene una colonna project_id
, la relazione hasManyThrough
fornisce accesso ai deployments di un progetto tramite $project->deployments
. Per recuperare questi modelli, Eloquent esamina la colonna project_id
nella tabella del modello intermedio Environment
. Dopo aver trovato gli ID degli environment rilevanti, vengono utilizzati per interrogare la tabella del modello Deployment
.
Convenzioni Chiave
Le convenzioni tipiche delle chiavi esterne Eloquent saranno utilizzate quando si eseguono le query delle relazioni. Se desideri personalizzare le chiavi della relazione, puoi passarle come terzo e quarto argomento al metodo hasManyThrough
. Il terzo argomento è il nome della chiave esterna sul modello intermedio. Il quarto argomento è il nome della chiave esterna sul modello finale. Il quinto argomento è la chiave locale, mentre il sesto argomento è la chiave locale del modello intermedio:
class Project extends Model
{
public function deployments(): HasManyThrough
{
return $this->hasManyThrough(
Deployment::class,
Environment::class,
'project_id', // Chiave esterna sulla tabella environments...
'environment_id', // Chiave esterna sulla tabella deployments...
'id', // Chiave locale sulla tabella projects...
'id' // Chiave locale sulla tabella environments...
);
}
}
Oppure, come discusso in precedenza, se le relazioni rilevanti sono già state definite su tutti i modelli coinvolti nella relazione, puoi definire fluentemente una relazione "has-many-through" invocando il metodo through
e fornendo i nomi di quelle relazioni. Questo approccio offre il vantaggio di riutilizzare le convenzioni delle chiavi già definite sulle relazioni esistenti:
// Sintassi basata su stringa...
return $this->through('environments')->has('deployments');
// Sintassi dinamica...
return $this->throughEnvironments()->hasDeployments();
Relazioni Molti-a-Molti
Le relazioni molti-a-molti sono leggermente più complesse rispetto alle relazioni hasOne
e hasMany
. Un esempio di relazione molti-a-molti è un utente che ha molti ruoli e questi ruoli sono condivisi anche da altri utenti nell’applicazione. Ad esempio, a un utente può essere assegnato il ruolo di "Author" e "Editor"; tuttavia, questi ruoli possono essere assegnati anche ad altri utenti. Quindi, un utente ha molti ruoli e un ruolo ha molti utenti.
Struttura delle tabelle
Per definire questa relazione, sono necessarie tre tabelle nel database: users
, roles
e role_user
. La tabella role_user
deriva dall’ordine alfabetico dei nomi dei modelli correlati e contiene le colonne user_id
e role_id
. Questa tabella funge da collegamento tra utenti e ruoli.
Ricorda, poiché un ruolo può appartenere a molti utenti, non possiamo semplicemente aggiungere una colonna user_id
nella tabella roles
. Ciò significherebbe che un ruolo potrebbe appartenerne solo a un singolo utente. Per permettere che i ruoli siano assegnati a più utenti, è necessaria la tabella role_user
. Possiamo riassumere la struttura delle tabelle della relazione nel seguente modo:
users
id - integer
name - string
roles
id - integer
name - string
role_user
user_id - integer
role_id - integer
Struttura del Modello
Le relazioni molti-a-molti si definiscono creando un metodo che restituisce il risultato del metodo belongsToMany
. Il metodo belongsToMany
è fornito dalla classe base Illuminate\Database\Eloquent\Model
usata da tutti i modelli Eloquent della tua applicazione. Ad esempio, definiamo un metodo roles
nel nostro modello User
. Il primo argomento passato a questo metodo è il nome della classe del modello correlato:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class User extends Model
{
/**
* I ruoli appartenenti all'utente.
*/
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
}
Una volta definita la relazione, puoi accedere ai ruoli dell’utente usando la proprietà dinamica roles
:
use App\Models\User;
$user = User::find(1);
foreach ($user->roles as $role) {
// ...
}
Poiché tutte le relazioni funzionano anche come costruttori di query, puoi aggiungere ulteriori vincoli alla query della relazione chiamando il metodo roles
e concatenando condizioni alla query:
$roles = User::find(1)->roles()->orderBy('name')->get();
Per determinare il nome della tabella intermedia della relazione, Eloquent unirà i nomi dei due modelli correlati in ordine alfabetico. Tuttavia, puoi sovrascrivere questa convenzione passando un secondo argomento al metodo belongsToMany
:
return $this->belongsToMany(Role::class, 'role_user');
Oltre a personalizzare il nome della tabella intermedia, puoi anche personalizzare i nomi delle colonne delle chiavi nella tabella passando ulteriori argomenti al metodo belongsToMany
. Il terzo argomento è il nome della chiave esterna del modello su cui stai definendo la relazione, mentre il quarto argomento è il nome della chiave esterna del modello a cui ti stai collegando:
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
Definire l’Inverso della Relazione
Per definire l’"inverso" di una relazione molti-a-molti, dovresti definire un metodo sul modello correlato che restituisce anche il risultato del metodo belongsToMany
. Per completare il nostro esempio utente / ruolo, definiamo il metodo users
sul modello Role
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}
Come puoi vedere, la relazione è definita esattamente allo stesso modo del suo omologo nel modello User
, ad eccezione del riferimento al modello App\Models\User
. Poiché stiamo riutilizzando il metodo belongsToMany
, tutte le opzioni di personalizzazione per tabelle e chiavi sono disponibili quando si definisce l’"inverso" di relazioni molti-a-molti.
Recupero delle colonne della tabella intermedia
Come hai già imparato, lavorare con relazioni molti-a-molti richiede la presenza di una tabella intermedia. Eloquent fornisce alcuni modi molto utili per interagire con questa tabella. Ad esempio, supponiamo che il nostro modello User
abbia molti modelli Role
a cui è correlato. Dopo aver accesso a questa relazione, possiamo accedere alla tabella intermedia usando l’attributo pivot
sui modelli:
use App\Models\User;
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
Nota che ogni modello Role
che recuperiamo ha automaticamente assegnato un attributo pivot
. Questo attributo contiene un modello che rappresenta la tabella intermedia.
Per impostazione predefinita, solo le chiavi del modello saranno presenti sul modello pivot
. Se la tua tabella intermedia contiene attributi extra, devi specificarli quando definisci la relazione:
return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');
Se desideri che la tua tabella intermedia abbia i timestamp created_at
e updated_at
che vengono mantenuti automaticamente da Eloquent, chiama il metodo withTimestamps
quando definisci la relazione:
return $this->belongsToMany(Role::class)->withTimestamps();
Le tabelle intermedie che utilizzano i timestamp mantenuti automaticamente da Eloquent devono avere entrambe le colonne
created_at
eupdated_at
.
Personalizzare il nome dell’attributo pivot
Come già menzionato, gli attributi della tabella intermedia possono essere acceduti nei modelli tramite l’attributo pivot
. Tuttavia, puoi personalizzare il nome di questo attributo per riflettere meglio il suo scopo nella tua applicazione.
Per esempio, se la tua applicazione ha utenti che possono iscriversi ai podcast, probabilmente hai una relazione molti-a-molti tra utenti e podcast. In questo caso, potresti voler rinominare l’attributo della tabella intermedia in subscription
invece di pivot
. Questo si può fare usando il metodo as
quando definisci la relazione:
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();
Una volta specificato l’attributo personalizzato della tabella intermedia, puoi accedere ai dati della tabella intermedia usando il nome personalizzato:
$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}
Filtrare le Query tramite le Colonne della Tabella Intermedia
Puoi anche filtrare i risultati restituiti dalle query delle relazioni belongsToMany
utilizzando i metodi wherePivot
, wherePivotIn
, wherePivotNotIn
, wherePivotBetween
, wherePivotNotBetween
, wherePivotNull
e wherePivotNotNull
quando definisci la relazione:
return $this->belongsToMany(Role::class)
->wherePivot('approved', 1);
return $this->belongsToMany(Role::class)
->wherePivotIn('priority', [1, 2]);
return $this->belongsToMany(Role::class)
->wherePivotNotIn('priority', [1, 2]);
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNull('expired_at');
return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotNull('expired_at');
Ordinare le Query tramite Colonne della Tabella Intermedia
Puoi ordinare i risultati restituiti dalle query di relazione belongsToMany
utilizzando il metodo orderByPivot
. Nell’esempio seguente, recupereremo tutti i badge più recenti per l’utente:
return $this->belongsToMany(Badge::class)
->where('rank', 'gold')
->orderByPivot('created_at', 'desc');
Definire Modelli Personalizzati per Tabelle Intermedie
Se desideri definire un modello personalizzato per rappresentare la tabella intermedia della tua relazione molti-a-molti, puoi utilizzare il metodo using
quando definisci la relazione. I modelli pivot personalizzati ti permettono di aggiungere comportamenti aggiuntivi al modello pivot, come metodi e cast.
I modelli pivot personalizzati per relazioni molti-a-molti dovrebbero estendere la classe Illuminate\Database\Eloquent\Relations\Pivot
, mentre i modelli pivot polimorfici personalizzati per relazioni molti-a-molti dovrebbero estendere la classe Illuminate\Database\Eloquent\Relations\MorphPivot
. Ad esempio, possiamo definire un modello Role
che utilizza un modello pivot personalizzato RoleUser
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
/**
* Gli utenti che appartengono al ruolo.
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
}
Quando definisci il modello RoleUser
, dovresti estendere la classe Illuminate\Database\Eloquent\Relations\Pivot
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUser extends Pivot
{
// ...
}
I modelli pivot non possono utilizzare il trait
SoftDeletes
. Se hai bisogno di eliminare soft le record pivot, considera di convertire il tuo modello pivot in un vero modello Eloquent.
Modelli Pivot Personalizzati e ID Auto-incrementanti
Se hai definito una relazione molti-a-molti che utilizza un modello pivot personalizzato e quel modello pivot ha una chiave primaria auto-incrementante, devi assicurarti che la tua classe del modello pivot personalizzato definisca una proprietà incrementing
impostata su true
.
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = true;
Relazioni Polimorfiche
Una relazione polimorfica permette al modello figlio di appartenere a più di un tipo di modello usando un’unica associazione. Ad esempio, immagina di creare un’applicazione che permette agli utenti di condividere post del blog e video. In un’applicazione del genere, un modello Comment
potrebbe appartenere sia ai modelli Post
che Video
.
One to One (Polimorfica)
Struttura della Tabella
Una relazione polimorfica uno a uno è simile a una tipica relazione uno a uno; tuttavia, il modello figlio può appartenere a più tipi di modelli utilizzando una singola associazione. Ad esempio, un Post
di un blog e un User
possono condividere una relazione polimorfica con un modello Image
. Utilizzando una relazione polimorfica uno a uno, puoi avere una singola tabella di immagini uniche che possono essere associate sia a post che a utenti. Prima di tutto, esaminiamo la struttura delle tabelle:
posts
id - integer
name - string
users
id - integer
name - string
images
id - integer
url - string
imageable_id - integer
imageable_type - string
Nota le colonne imageable_id
e imageable_type
nella tabella images
. La colonna imageable_id
conterrà il valore dell’ID del post o dell’utente, mentre la colonna imageable_type
conterrà il nome della classe del modello genitore. La colonna imageable_type
viene utilizzata da Eloquent per determinare quale "tipo" di modello genitore restituire quando accedi alla relazione imageable
. In questo caso, la colonna conterrà App\Models\Post
o App\Models\User
.
Struttura del Modello
Vediamo ora le definizioni dei modelli necessarie per creare questa relazione:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Image extends Model
{
/**
* Recupera il modello imageable genitore (user o post).
*/
public function imageable(): MorphTo
{
return $this->morphTo();
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class Post extends Model
{
/**
* Recupera l'immagine del post.
*/
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class User extends Model
{
/**
* Recupera l'immagine dell'utente.
*/
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}
}
Recuperare la Relazione
Una volta che la tua tabella del database e i modelli sono definiti, puoi accedere alle relazioni tramite i tuoi modelli. Ad esempio, per recuperare l’immagine di un post, possiamo accedere alla proprietà dinamica della relazione image
:
use App\Models\Post;
$post = Post::find(1);
$image = $post->image;
Puoi recuperare il genitore del modello polimorfico accedendo al nome del metodo che esegue la chiamata a morphTo
. In questo caso, si tratta del metodo imageable
sul modello Image
. Quindi, accederemo a quel metodo come una proprietà dinamica della relazione:
use App\Models\Image;
$image = Image::find(1);
$imageable = $image->imageable;
La relazione imageable
sul modello Image
restituirà un’istanza di Post
o User
, a seconda del tipo di modello che possiede l’immagine.
Convenzioni Chiave
Se necessario, puoi specificare il nome delle colonne "id" e "type" utilizzate dal tuo modello polimorfico figlio. Se lo fai, assicurati di passare sempre il nome della relazione come primo argomento al metodo morphTo
. Solitamente, questo valore dovrebbe corrispondere al nome del metodo, quindi puoi usare la costante __FUNCTION__
di PHP:
/**
* Ottieni il modello a cui appartiene l'immagine.
*/
public function imageable(): MorphTo
{
return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}
One to Many (Polimorfica)
Struttura delle Tabelle
Una relazione polimorfica uno-a-molti è simile a una relazione uno-a-molti tradizionale; però, il modello figlio può appartenere a più tipi di modelli usando un’unica associazione. Per esempio, immagina che gli utenti della tua applicazione possano "commentare" post e video. Usando relazioni polimorfiche, puoi utilizzare una singola tabella comments
per i commenti sia dei post che dei video. Prima di tutto, vediamo la struttura delle tabelle necessaria per creare questa relazione:
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
Struttura del Modello
Successivamente, esaminiamo le definizioni dei modelli necessarie per costruire questa relazione:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Comment extends Model
{
/**
* Ottieni il modello commentable padre (post o video).
*/
public function commentable(): MorphTo
{
return $this->morphTo();
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Post extends Model
{
/**
* Ottieni tutti i commenti del post.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Video extends Model
{
/**
* Ottieni tutti i commenti del video.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}
Recuperare la Relazione
Una volta che la tua tabella del database e i modelli sono definiti, puoi accedere alle relazioni tramite le proprietà di relazione dinamiche del tuo modello. Ad esempio, per accedere a tutti i commenti di un post, possiamo usare la proprietà dinamica comments
:
use App\Models\Post;
$post = Post::find(1);
foreach ($post->comments as $comment) {
// ...
}
Puoi inoltre recuperare il genitore di un modello figlio polimorfico accedendo al nome del metodo che effettua la chiamata a morphTo
. In questo caso, si tratta del metodo commentable
sul modello Comment
. Quindi, accederemo a quel metodo come proprietà di relazione dinamica per accedere al modello genitore del commento:
use App\Models\Comment;
$comment = Comment::find(1);
$commentable = $comment->commentable;
La relazione commentable
nel modello Comment
restituirà un’istanza di Post
o Video
, a seconda del tipo di modello che è il genitore del commento.
Popolazione Automatica dei Modelli Genitori sui Figli
Anche utilizzando il caricamento eager di Eloquent, possono sorgere problemi di query "N + 1" se tenti di accedere al modello padre da un modello figlio mentre cicli attraverso i modelli figli:
$posts = Post::with('comments')->get();
foreach ($posts as $post) {
foreach ($post->comments as $comment) {
echo $comment->commentable->title;
}
}
Nell’esempio sopra, è stato introdotto un problema di query "N + 1" perché, anche se i commenti sono stati caricati eager per ogni modello Post
, Eloquent non popola automaticamente il modello padre Post
su ogni modello figlio Comment
.
Se desideri che Eloquent popoli automaticamente i modelli genitori sui loro figli, puoi invocare il metodo chaperone
durante la definizione di una relazione morphMany
:
class Post extends Model
{
/**
* Ottieni tutti i commenti del post.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable')->chaperone();
}
}
Oppure, se preferisci attivare la popolazione automatica dei genitori al momento dell’esecuzione, puoi invocare il metodo chaperone
durante il caricamento eager della relazione:
use App\Models\Post;
$posts = Post::with([
'comments' => fn ($comments) => $comments->chaperone(),
])->get();
One to Many (Polimorfica)
A volte un modello può avere molti modelli correlati, ma potresti voler recuperare facilmente il modello correlato "più recente" o "più vecchio" della relazione. Ad esempio, un modello User
può essere collegato a molti modelli Image
, ma vuoi definire un modo comodo per interagire con l’immagine più recente che l’utente ha caricato. Puoi ottenere questo utilizzando il tipo di relazione morphOne
combinato con i metodi ofMany
:
/**
* Ottieni l'immagine più recente dell'utente.
*/
public function latestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->latestOfMany();
}
Allo stesso modo, puoi definire un metodo per recuperare il modello correlato "più vecchio", o il primo, di una relazione:
/**
* Ottieni l'immagine più vecchia dell'utente.
*/
public function oldestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->oldestOfMany();
}
Per impostazione predefinita, i metodi latestOfMany
e oldestOfMany
recuperano il modello correlato più recente o più vecchio basandosi sulla chiave primaria del modello, che deve essere ordinabile. Tuttavia, a volte potresti voler recuperare un singolo modello da una relazione più ampia utilizzando un criterio di ordinamento diverso.
Ad esempio, usando il metodo ofMany
, puoi recuperare l’immagine più "piaciuta" dell’utente. Il metodo ofMany
accetta la colonna da ordinare come primo argomento e quale funzione di aggregazione (min
o max
) applicare durante la query per il modello correlato:
/**
* Ottieni l'immagine più popolare dell'utente.
*/
public function bestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');
}
[!NOTA]
È possibile costruire relazioni "uno tra molti" più avanzate. Per maggiori informazioni, consulta la documentazione has one of many.
Many to Many (Polimorfica)
Struttura delle Tabelle
Le relazioni polimorfiche molti-a-molti sono leggermente più complesse rispetto alle relazioni "morph one" e "morph many". Ad esempio, un modello Post
e un modello Video
potrebbero condividere una relazione polimorfica con un modello Tag
. Utilizzare una relazione polimorfica molti-a-molti in questa situazione permetterebbe alla tua applicazione di avere una singola tabella di tag unici che possono essere associati a post o video. Prima, esaminiamo la struttura delle tabelle necessaria per costruire questa relazione:
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
Prima di approfondire le relazioni polimorfiche molti-a-molti, potrebbe esserti utile leggere la documentazione sulle relazioni molti-a-molti.
Struttura del Modello
Ora siamo pronti a definire le relazioni nei modelli. I modelli Post
e Video
conterranno entrambi un metodo tags
che chiama il metodo morphToMany
fornito dalla classe base del modello Eloquent.
Il metodo morphToMany
accetta il nome del modello correlato e il "nome della relazione". In base al nome che abbiamo assegnato alla nostra tabella intermedia e alle chiavi che contiene, ci riferiremo alla relazione come "taggable":
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class Post extends Model
{
/**
* Ottieni tutti i tag per il post.
*/
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
Definire l’Inverso della Relazione
Successivamente, nel modello Tag
, dovresti definire un metodo per ciascuno dei suoi possibili modelli genitori. In questo esempio, definiremo un metodo posts
e un metodo videos
. Entrambi i metodi devono restituire il risultato del metodo morphedByMany
.
Il metodo morphedByMany
accetta il nome del modello correlato e il "nome della relazione". Sulla base del nome che abbiamo assegnato alla nostra tabella intermedia e delle chiavi che contiene, ci riferiremo alla relazione come "taggable":
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class Tag extends Model
{
/**
* Ottieni tutti i post assegnati a questo tag.
*/
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
/**
* Ottieni tutti i video assegnati a questo tag.
*/
public function videos(): MorphToMany
{
return $this->morphedByMany(Video::class, 'taggable');
}
}
Recuperare la relazione
Una volta definite le tabelle del database e i modelli, puoi accedere alle relazioni tramite i tuoi modelli. Ad esempio, per accedere a tutti i tag di un post, puoi utilizzare la proprietà di relazione dinamica tags
:
use App\Models\Post;
$post = Post::find(1);
foreach ($post->tags as $tag) {
// ...
}
Puoi recuperare il genitore di una relazione polimorfica dal modello figlio polimorfico accedendo al nome del metodo che esegue la chiamata a morphedByMany
. In questo caso, si tratta dei metodi posts
o videos
sul modello Tag
:
use App\Models\Tag;
$tag = Tag::find(1);
foreach ($tag->posts as $post) {
// ...
}
foreach ($tag->videos as $video) {
// ...
}
Tipi Polimorfici Personalizzati
Per impostazione predefinita, Laravel utilizzerà il nome completo della classe per memorizzare il "tipo" del modello correlato. Ad esempio, dato l’esempio di relazione uno-a-molti sopra in cui un modello Comment
può appartenere a un modello Post
o Video
, il valore predefinito per commentable_type
sarà rispettivamente App\Models\Post
o App\Models\Video
. Tuttavia, potresti voler disaccoppiare questi valori dalla struttura interna della tua applicazione.
Ad esempio, invece di utilizzare i nomi dei modelli come "tipo", possiamo usare stringhe semplici come post
e video
. In questo modo, i valori della colonna "type" polimorfica nel nostro database rimarranno validi anche se i modelli vengono rinominati:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);
Puoi chiamare il metodo enforceMorphMap
nel metodo boot
della tua classe App\Providers\AppServiceProvider
o creare un provider di servizio separato se lo desideri.
Puoi determinare l’alias morph di un determinato modello in fase di esecuzione utilizzando il metodo getMorphClass
del modello. Al contrario, puoi determinare il nome completo della classe associata a un alias morph utilizzando il metodo Relation::getMorphedModel
:
use Illuminate\Database\Eloquent\Relations\Relation;
$alias = $post->getMorphClass();
$class = Relation::getMorphedModel($alias);
Quando aggiungi una "morph map" alla tua applicazione esistente, ogni valore della colonna
*_type
morphable nel tuo database che contiene ancora una classe completamente qualificata dovrà essere convertito nel suo nome mappato.
Relazioni Dinamiche
Puoi usare il metodo resolveRelationUsing
per definire relazioni tra modelli Eloquent durante l’esecuzione. Anche se generalmente non è raccomandato per lo sviluppo normale di applicazioni, può essere utile quando sviluppi pacchetti Laravel.
Il metodo resolveRelationUsing
accetta il nome della relazione desiderata come primo argomento. Il secondo argomento deve essere una closure che riceve l’istanza del modello e restituisce una definizione valida di relazione Eloquent. Di solito, dovresti configurare le relazioni dinamiche all’interno del metodo boot di un service provider:
use App\Models\Order;
use App\Models\Customer;
Order::resolveRelationUsing('customer', function (Order $orderModel) {
return $orderModel->belongsTo(Customer::class, 'customer_id');
});
Quando definisci relazioni dinamiche, fornisci sempre argomenti di nome chiave espliciti ai metodi di relazione Eloquent.
Interrogare le Relazioni
Poiché tutte le relazioni di Eloquent sono definite tramite metodi, puoi chiamare questi metodi per ottenere un’istanza della relazione senza eseguire effettivamente una query per caricare i modelli correlati. Inoltre, tutti i tipi di relazioni Eloquent fungono anche da costruttori di query, permettendoti di aggiungere ulteriori vincoli alla query della relazione prima di eseguire finalmente la query SQL sul tuo database.
Ad esempio, immagina un’applicazione blog in cui un modello User
ha molti modelli Post
associati:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Model
{
/**
* Ottieni tutti i post per l'utente.
*/
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
Puoi interrogare la relazione posts
e aggiungere ulteriori vincoli alla relazione in questo modo:
use App\Models\User;
$user = User::find(1);
$user->posts()->where('active', 1)->get();
Puoi usare tutti i metodi del costruttore di query di Laravel sulla relazione, quindi assicurati di esplorare la documentazione del costruttore di query per conoscere tutti i metodi disponibili.
Concatenare clausole orWhere
dopo le relazioni
Come dimostrato nell’esempio sopra, puoi aggiungere ulteriori vincoli alle relazioni quando le interroghi. Tuttavia, fai attenzione quando concatenare clausole orWhere
a una relazione, poiché le clausole orWhere
saranno raggruppate logicamente allo stesso livello del vincolo della relazione:
$user->posts()
->where('active', 1)
->orWhere('votes', '>=', 100)
->get();
L’esempio sopra genererà il seguente SQL. Come puoi vedere, la clausola or
fa sì che la query restituisca qualsiasi post con più di 100 voti. La query non è più limitata a un utente specifico:
select *
from posts
where user_id = ? and active = 1 or votes >= 100
Nella maggior parte delle situazioni, dovresti usare gruppi logici per raggruppare i controlli condizionali all’interno delle parentesi:
use Illuminate\Database\Eloquent\Builder;
$user->posts()
->where(function (Builder $query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();
L’esempio sopra produrrà il seguente SQL. Nota che il raggruppamento logico ha correttamente raggruppato i vincoli e la query rimane limitata a un utente specifico:
select *
from posts
where user_id = ? and (active = 1 or votes >= 100)
Relationship Methods vs. Dynamic Properties
Se non hai bisogno di aggiungere vincoli aggiuntivi a una query di relazione Eloquent, puoi accedere alla relazione come se fosse una proprietà. Ad esempio, continuando a usare i nostri modelli di esempio User
e Post
, possiamo accedere a tutti i post di un utente in questo modo:
use App\Models\User;
$user = User::find(1);
foreach ($user->posts as $post) {
// ...
}
Le proprietà dinamiche delle relazioni eseguono "lazy loading", il che significa che caricheranno i dati della relazione solo quando le accedi effettivamente. Per questo motivo, gli sviluppatori utilizzano spesso eager loading per pre-caricare le relazioni di cui sanno che saranno accessibili dopo il caricamento del modello. L’eager loading consente una riduzione significativa delle query SQL necessarie per caricare le relazioni di un modello.
Verifica dell’Esistenza delle Relazioni
Quando recuperi i record dei modelli, potresti voler limitare i risultati in base all’esistenza di una relazione. Ad esempio, immagina di voler recuperare tutti i post del blog che hanno almeno un commento. Per farlo, puoi passare il nome della relazione ai metodi has
e orHas
:
use App\Models\Post;
// Recupera tutti i post che hanno almeno un commento...
$posts = Post::has('comments')->get();
Puoi anche specificare un operatore e un valore di conteggio per personalizzare ulteriormente la query:
// Recupera tutti i post che hanno tre o più commenti...
$posts = Post::has('comments', '>=', 3)->get();
Le istruzioni has
annidate possono essere costruite usando la notazione "punto". Ad esempio, puoi recuperare tutti i post che hanno almeno un commento che ha almeno un’immagine:
// Recupera i post che hanno almeno un commento con immagini...
$posts = Post::has('comments.images')->get();
Se hai bisogno di ancora più potenza, puoi usare i metodi whereHas
e orWhereHas
per definire ulteriori vincoli alla tua query has
, come ispezionare il contenuto di un commento:
use Illuminate\Database\Eloquent\Builder;
// Recupera i post con almeno un commento che contiene parole come code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
// Recupera i post con almeno dieci commenti che contengono parole come code%...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();
Eloquent attualmente non supporta la verifica dell’esistenza delle relazioni tra database. Le relazioni devono esistere nello stesso database.
Query sull’Esistenza delle Relazioni Inline
Se vuoi verificare l’esistenza di una relazione con una singola condizione where
semplice collegata alla query della relazione, potrebbe essere più comodo usare i metodi whereRelation
, orWhereRelation
, whereMorphRelation
e orWhereMorphRelation
. Ad esempio, possiamo cercare tutti i post che hanno commenti non approvati:
use App\Models\Post;
$posts = Post::whereRelation('comments', 'is_approved', false)->get();
Naturalmente, come le chiamate al metodo where
del query builder, puoi anche specificare un operatore:
$posts = Post::whereRelation(
'comments', 'created_at', '>=', now()->subHour()
)->get();
Interrogare l’assenza di una relazione
Durante il recupero dei record del modello, potresti voler limitare i risultati in base all’assenza di una relazione. Ad esempio, immagina di voler recuperare tutti i post del blog che non hanno commenti. Per fare ciò, puoi passare il nome della relazione ai metodi doesntHave
e orDoesntHave
:
use App\Models\Post;
$posts = Post::doesntHave('comments')->get();
Se hai bisogno di maggiore flessibilità, puoi usare i metodi whereDoesntHave
e orWhereDoesntHave
per aggiungere ulteriori vincoli alla tua query doesntHave
, come esaminare il contenuto di un commento:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::whereDoesntHave('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
Puoi usare la notazione "dot" per eseguire una query su una relazione annidata. Ad esempio, la seguente query recupererà tutti i post che non hanno commenti; tuttavia, i post che hanno commenti da autori non bannati saranno inclusi nei risultati:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::whereDoesntHave('comments.author', function (Builder $query) {
$query->where('banned', 0);
})->get();
Interrogare le Relazioni Morph To
Per verificare l’esistenza delle relazioni "morph to", puoi utilizzare i metodi whereHasMorph
e whereDoesntHaveMorph
. Questi metodi accettano il nome della relazione come primo argomento. Successivamente, accettano i nomi dei modelli correlati che desideri includere nella query. Infine, puoi fornire una closure che personalizza la query della relazione:
use App\Models\Comment;
use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Eloquent\Builder;
// Recupera i commenti associati a post o video con un titolo che inizia con code%...
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();
// Recupera i commenti associati a post con un titolo non simile a code%...
$comments = Comment::whereDoesntHaveMorph(
'commentable',
Post::class,
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();
Occasionalmente potresti aver bisogno di aggiungere vincoli alla query basati sul "tipo" del modello polimorfico correlato. La closure passata al metodo whereHasMorph
può ricevere un valore $type
come secondo argomento. Questo argomento ti permette di esaminare il "tipo" della query che viene costruita:
use Illuminate\Database\Eloquent\Builder;
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query, string $type) {
$column = $type === Post::class ? 'content' : 'title';
$query->where($column, 'like', 'code%');
}
)->get();
A volte potresti voler interrogare i figli del genitore di una relazione "morph to". Puoi farlo utilizzando i metodi whereMorphedTo
e whereNotMorphedTo
, che determinano automaticamente la mappatura del tipo morph corretta per il modello dato. Questi metodi accettano il nome della relazione morphTo
come primo argomento e il modello genitore correlato come secondo argomento:
$comments = Comment::whereMorphedTo('commentable', $post)
->orWhereMorphedTo('commentable', $video)
->get();
Interrogare Tutti i Modelli Correlati
Invece di passare un array di possibili modelli polimorfici, puoi fornire *
come valore jolly. Questo istruisce Laravel a recuperare tutti i tipi polimorfici possibili dal database. Laravel eseguirà una query aggiuntiva per effettuare questa operazione:
use Illuminate\Database\Eloquent\Builder;
$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) {
$query->where('title', 'like', 'foo%');
})->get();
Aggregazione dei Modelli Relazionati
Conteggio dei Modelli Relazionati
A volte potresti voler contare il numero di modelli correlati per una determinata relazione senza caricare effettivamente i modelli. Per fare questo, puoi usare il metodo withCount
. Il metodo withCount
aggiungerà un attributo {relation}_count
ai modelli risultanti:
use App\Models\Post;
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
Passando un array al metodo withCount
, puoi aggiungere i "conteggi" per più relazioni e aggiungere ulteriori vincoli alle query:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'code%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
Puoi anche assegnare un alias al risultato del conteggio della relazione, permettendo più conteggi sulla stessa relazione:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount([
'comments',
'comments as pending_comments_count' => function (Builder $query) {
$query->where('approved', false);
},
])->get();
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;
Caricamento del Conteggio Differito
Usando il metodo loadCount
, puoi caricare il conteggio di una relazione dopo che il modello principale è già stato recuperato:
$book = Book::first();
$book->loadCount('genres');
Se hai bisogno di aggiungere ulteriori vincoli alla query di conteggio, puoi passare un array con le relazioni che vuoi contare. I valori dell’array dovrebbero essere delle closure che ricevono l’istanza del query builder:
$book->loadCount(['reviews' => function (Builder $query) {
$query->where('rating', 5);
}])
Conteggio delle Relazioni e Dichiarazioni Select Personalizzate
Se stai combinando withCount
con una dichiarazione select
, assicurati di chiamare withCount
dopo il metodo select
:
$posts = Post::select(['title', 'body'])
->withCount('comments')
->get();
Altre Funzioni Aggregate
Oltre al metodo withCount
, Eloquent fornisce i metodi withMin
, withMax
, withAvg
, withSum
e withExists
. Questi metodi aggiungeranno un attributo {relation}_{function}_{column}
ai modelli risultanti:
use App\Models\Post;
$posts = Post::withSum('comments', 'votes')->get();
foreach ($posts as $post) {
echo $post->comments_sum_votes;
}
Se desideri accedere al risultato della funzione aggregate con un altro nome, puoi specificare un alias:
$posts = Post::withSum('comments as total_comments', 'votes')->get();
foreach ($posts as $post) {
echo $post->total_comments;
}
Come il metodo loadCount
, sono disponibili anche versioni differite di questi metodi. Queste operazioni aggregate aggiuntive possono essere eseguite su modelli Eloquent già recuperati:
$post = Post::first();
$post->loadSum('comments', 'votes');
Se stai combinando questi metodi aggregate con una dichiarazione select
, assicurati di chiamare i metodi aggregate dopo il metodo select
:
$posts = Post::select(['title', 'body'])
->withExists('comments')
->get();
Conteggio dei Modelli Relazionati nelle Relazioni Morph To
Se vuoi caricare in eager una relazione "morph to", così come il conteggio dei modelli correlati per le diverse entità che possono essere restituite da quella relazione, puoi utilizzare il metodo with
insieme al metodo morphWithCount
della relazione morphTo
.
In questo esempio, supponiamo che i modelli Photo
e Post
possano creare modelli ActivityFeed
. Assumeremo che il modello ActivityFeed
definisca una relazione "morph to" chiamata parentable
che ci permette di recuperare il modello padre Photo
o Post
per una determinata istanza di ActivityFeed
. Inoltre, supponiamo che i modelli Photo
"abbiano molti" modelli Tag
e i modelli Post
"abbiano molti" modelli Comment
.
Ora, immaginiamo di voler recuperare le istanze di ActivityFeed
e caricare in eager i modelli padre parentable
per ogni istanza di ActivityFeed
. Inoltre, vogliamo recuperare il numero di tag associati a ogni photo padre e il numero di commenti associati a ogni post padre:
use Illuminate\Database\Eloquent\Relations\MorphTo;
$activities = ActivityFeed::with([
'parentable' => function (MorphTo $morphTo) {
$morphTo->morphWithCount([
Photo::class => ['tags'],
Post::class => ['comments'],
]);
}])->get();
Caricamento Differito dei Conteggi
Supponiamo di aver già recuperato un insieme di modelli ActivityFeed
e ora vogliamo caricare i conteggi delle relazioni annidate per i vari modelli parentable
associati ai feed di attività. Puoi utilizzare il metodo loadMorphCount
per ottenere questo risultato:
$activities = ActivityFeed::with('parentable')->get();
$activities->loadMorphCount('parentable', [
Photo::class => ['tags'],
Post::class => ['comments'],
]);
Eager Loading
Quando accedi alle relazioni Eloquent come proprietà, i modelli correlati vengono "caricati pigramente". Questo significa che i dati della relazione non vengono effettivamente caricati fino al primo accesso alla proprietà. Tuttavia, Eloquent può "caricare anticipatamente" le relazioni al momento della query sul modello principale. Il caricamento anticipato allevia il problema della query "N + 1". Per illustrare il problema della query N + 1, considera un modello Book
che "appartiene" a un modello Author
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Book extends Model
{
/**
* Ottieni l'autore che ha scritto il libro.
*/
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
}
Ora, recuperiamo tutti i libri e i loro autori:
use App\Models\Book;
$books = Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
Questo ciclo eseguirà una query per recuperare tutti i libri nella tabella del database, poi un’altra query per ogni libro per recuperare l’autore del libro. Quindi, se abbiamo 25 libri, il codice sopra eseguirebbe 26 query: una per i libri originali e 25 query aggiuntive per recuperare l’autore di ciascun libro.
Fortunatamente, possiamo usare il caricamento anticipato (Eager Loading) per ridurre questa operazione a sole due query. Quando costruisci una query, puoi specificare quali relazioni devono essere caricate anticipatamente usando il metodo with
:
$books = Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
Per questa operazione, verranno eseguite solo due query: una per recuperare tutti i libri e una per recuperare tutti gli autori di tutti i libri:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
Eager Loading di più Relazioni
A volte potresti aver bisogno di caricare anticipatamente diverse relazioni. Per farlo, passa semplicemente un array di relazioni al metodo with
:
$books = Book::with(['author', 'publisher'])->get();
Eager Loading Annidato
Per caricare le relazioni delle relazioni, puoi usare la sintassi con il punto. Ad esempio, carichiamo tutti gli autori del libro e tutti i contatti personali degli autori:
$books = Book::with('author.contacts')->get();
In alternativa, puoi specificare relazioni pre-caricate in modo annidato fornendo un array annidato al metodo with
, il che può essere comodo quando carichi in eager loading più relazioni annidate:
$books = Book::with([
'author' => [
'contacts',
'publisher',
],
])->get();
Caricamento Eager Annidato di Relazioni morphTo
Se desideri effettuare un eager load di una relazione morphTo
, così come delle relazioni annidate sulle varie entità che possono essere restituite da quella relazione, puoi usare il metodo with
in combinazione con il metodo morphWith
della relazione morphTo
. Per illustrare questo metodo, consideriamo il seguente modello:
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class ActivityFeed extends Model
{
/**
* Ottieni il genitore del record activity feed.
*/
public function parentable(): MorphTo
{
return $this->morphTo();
}
}
In questo esempio, supponiamo che i modelli Event
, Photo
e Post
possano creare modelli ActivityFeed
. Inoltre, supponiamo che i modelli Event
appartengano a un modello Calendar
, i modelli Photo
siano associati a modelli Tag
e i modelli Post
appartengano a un modello Author
.
Usando queste definizioni di modelli e relazioni, possiamo recuperare istanze del modello ActivityFeed
e effettuare un eager load di tutti i modelli parentable
e delle rispettive relazioni annidate:
use Illuminate\Database\Eloquent\Relations\MorphTo;
$activities = ActivityFeed::query()
->with(['parentable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
}])->get();
Caricamento Preventivo di Colonne Specifiche
Potresti non aver sempre bisogno di ogni colonna dalle relazioni che stai recuperando. Per questo motivo, Eloquent ti permette di specificare quali colonne della relazione desideri recuperare:
$books = Book::with('author:id,name,book_id')->get();
Quando usi questa funzionalità, dovresti sempre includere la colonna
id
e qualsiasi colonna di chiave esterna rilevante nell’elenco delle colonne che desideri recuperare.
Eager Loading di default
A volte potresti voler caricare sempre alcune relazioni quando recuperi un modello. Per fare questo, puoi definire una proprietà $with
nel modello:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Book extends Model
{
/**
* Le relazioni che dovrebbero essere sempre caricate.
*
* @var array
*/
protected $with = ['author'];
/**
* Ottieni l'autore che ha scritto il libro.
*/
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
/**
* Ottieni il genere del libro.
*/
public function genre(): BelongsTo
{
return $this->belongsTo(Genre::class);
}
}
Se vuoi rimuovere un elemento dalla proprietà $with
per una singola query, puoi usare il metodo without
:
$books = Book::without('author')->get();
Se vuoi sovrascrivere tutti gli elementi nella proprietà $with
per una singola query, puoi usare il metodo withOnly
:
$books = Book::withOnly('genre')->get();
Limitare il caricamento anticipato
A volte potresti voler caricare anticipatamente una relazione ma anche specificare condizioni aggiuntive per la query di caricamento anticipato. Puoi fare questo passando un array di relazioni al metodo with
, dove la chiave dell’array è il nome della relazione e il valore dell’array è una closure che aggiunge vincoli aggiuntivi alla query di caricamento anticipato:
use App\Models\User;
use Illuminate\Contracts\Database\Eloquent\Builder;
$users = User::with(['posts' => function (Builder $query) {
$query->where('title', 'like', '%code%');
}])->get();
In questo esempio, Eloquent caricherà anticipatamente solo i post dove la colonna title
del post contiene la parola code
. Puoi chiamare altri metodi del query builder per personalizzare ulteriormente l’operazione di caricamento anticipato:
$users = User::with(['posts' => function (Builder $query) {
$query->orderBy('created_at', 'desc');
}])->get();
Limitare l’Eager Loading delle Relazioni morphTo
Se stai effettuando il caricamento anticipato di una relazione morphTo
, Eloquent eseguirà più query per recuperare ogni tipo di modello correlato. Puoi aggiungere ulteriori vincoli a ciascuna di queste query utilizzando il metodo constrain
della relazione MorphTo
:
use Illuminate\Database\Eloquent\Relations\MorphTo;
$comments = Comment::with(['commentable' => function (MorphTo $morphTo) {
$morphTo->constrain([
Post::class => function ($query) {
$query->whereNull('hidden_at');
},
Video::class => function ($query) {
$query->where('type', 'educational');
},
]);
}])->get();
In questo esempio, Eloquent caricherà anticipatamente solo i post che non sono stati nascosti e i video che hanno un valore type
di "educational".
Limitare i Caricamenti Eager con l’Esistenza di Relazioni
A volte potresti dover verificare l’esistenza di una relazione mentre carichi simultaneamente la relazione stessa basandoti sulle stesse condizioni. Ad esempio, potresti voler recuperare solo i modelli User
che hanno modelli figli Post
che soddisfano una determinata condizione di query, caricando inoltre i post corrispondenti. Puoi ottenere questo utilizzando il metodo withWhereHas
:
use App\Models\User;
$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();
Lazy Eager Loading
A volte potresti aver bisogno di eager loadare una relationship dopo che il parent model è già stato recuperato. Ad esempio, questo può essere utile se devi decidere dinamicamente se caricare i modelli correlati:
use App\Models\Book;
$books = Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
Se hai bisogno di impostare ulteriori vincoli di query sulla query di eager loading, puoi passare un array indicizzato dalle relationships che desideri caricare. I valori dell’array dovrebbero essere istanze di closure che ricevono l’istanza della query:
$author->load(['books' => function (Builder $query) {
$query->orderBy('published_date', 'asc');
}]);
Per caricare una relationship solo quando non è già stata caricata, usa il metodo loadMissing
:
$book->loadMissing('author');
Lazy Eager Loading Annidato e morphTo
Se desideri caricare eager una relazione morphTo
, oltre alle relazioni annidate sulle varie entità che possono essere restituite da quella relazione, puoi usare il metodo loadMorph
.
Questo metodo accetta il nome della relazione morphTo
come primo argomento e un array di coppie modello/relazione come secondo argomento. Per illustrare questo metodo, consideriamo il seguente modello:
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class ActivityFeed extends Model
{
/**
* Ottieni il genitore del record del feed di attività.
*/
public function parentable(): MorphTo
{
return $this->morphTo();
}
}
In questo esempio, supponiamo che i modelli Event
, Photo
e Post
possano creare modelli ActivityFeed
. Inoltre, supponiamo che i modelli Event
appartengano a un modello Calendar
, i modelli Photo
siano associati a modelli Tag
, e i modelli Post
appartengano a un modello Author
.
Usando queste definizioni di modello e relazioni, possiamo recuperare istanze del modello ActivityFeed
e caricare eager tutti i modelli parentable
e le rispettive relazioni annidate:
$activities = ActivityFeed::with('parentable')
->get()
->loadMorph('parentable', [
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
Prevenire il Lazy Loading
Come discusso in precedenza, il caricamento anticipato delle relazioni può spesso offrire significativi vantaggi in termini di prestazioni alla tua applicazione. Pertanto, se lo desideri, puoi istruire Laravel a prevenire sempre il lazy loading delle relazioni. Per fare ciò, puoi invocare il metodo preventLazyLoading
offerto dalla classe base del modello Eloquent. In genere, dovresti chiamare questo metodo all’interno del metodo boot
della classe AppServiceProvider
della tua applicazione.
Il metodo preventLazyLoading
accetta un argomento booleano opzionale che indica se il lazy loading deve essere prevenuto. Ad esempio, potresti voler disabilitare il lazy loading solo negli ambienti non di produzione, in modo che l’ambiente di produzione continui a funzionare normalmente anche se una relazione lazy loaded è presente accidentalmente nel codice di produzione:
use Illuminate\Database\Eloquent\Model;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
Dopo aver prevenuto il lazy loading, Eloquent lancerà un’eccezione Illuminate\Database\LazyLoadingViolationException
quando la tua applicazione tenterà di caricare in modo lazy qualsiasi relazione Eloquent.
Puoi personalizzare il comportamento delle violazioni del lazy loading utilizzando il metodo handleLazyLoadingViolationsUsing
. Ad esempio, usando questo metodo, puoi istruirle a registrare solamente le violazioni del lazy loading invece di interrompere l’esecuzione dell’applicazione con delle eccezioni:
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
$class = $model::class;
info("Attempted to lazy load [{$relation}] on model [{$class}].");
});
Inserimento e Aggiornamento dei Model Correlati
Il metodo save
Eloquent offre metodi comodi per aggiungere nuovi modelli alle relazioni. Ad esempio, potresti dover aggiungere un nuovo commento a un post. Invece di impostare manualmente l’attributo post_id
sul modello Comment
, puoi inserire il commento usando il metodo save
della relazione:
use App\Models\Comment;
use App\Models\Post;
$comment = new Comment(['message' => 'A new comment.']);
$post = Post::find(1);
$post->comments()->save($comment);
Nota che non abbiamo accesso alla relazione comments
come proprietà dinamica. Invece, abbiamo chiamato il metodo comments
per ottenere un’istanza della relazione. Il metodo save
aggiungerà automaticamente il valore post_id
appropriato al nuovo modello Comment
.
Se hai bisogno di salvare più modelli correlati, puoi usare il metodo saveMany
:
$post = Post::find(1);
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);
I metodi save
e saveMany
salveranno le istanze dei modelli forniti, ma non aggiungeranno i modelli appena salvati a eventuali relazioni in memoria già caricate sul modello padre. Se prevedi di accedere alla relazione dopo aver usato i metodi save
o saveMany
, potresti voler usare il metodo refresh
per ricaricare il modello e le sue relazioni:
$post->comments()->save($comment);
$post->refresh();
// Tutti i commenti, incluso il commento appena salvato...
$post->comments;
Salvataggio Ricorsivo di Modelli e Relazioni
Se vuoi save
il tuo modello e tutte le relazioni associate, puoi usare il metodo push
. In questo esempio, il modello Post
verrà salvato insieme ai suoi commenti e agli autori dei commenti:
$post = Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
$post->push();
Il metodo pushQuietly
può essere usato per salvare un modello e le sue relazioni associate senza generare eventi:
$post->pushQuietly();
Il Metodo create
Oltre ai metodi save
e saveMany
, puoi anche usare il metodo create
, che accetta un array di attributi, crea un modello e lo inserisce nel database. La differenza tra save
e create
è che save
accetta un’istanza completa di modello Eloquent mentre create
accetta un semplice array
PHP. Il modello appena creato verrà restituito dal metodo create
:
use App\Models\Post;
$post = Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);
Puoi usare il metodo createMany
per creare più modelli correlati:
$post = Post::find(1);
$post->comments()->createMany([
['message' => 'A new comment.'],
['message' => 'Another new comment.'],
]);
I metodi createQuietly
e createManyQuietly
possono essere usati per creare un modello/i senza inviare eventi:
$user = User::find(1);
$user->posts()->createQuietly([
'title' => 'Post title.',
]);
$user->posts()->createManyQuietly([
['title' => 'First post.'],
['title' => 'Second post.'],
]);
Puoi anche usare i metodi findOrNew
, firstOrNew
, firstOrCreate
e updateOrCreate
per creare e aggiornare modelli nelle relazioni.
Prima di usare il metodo
create
, assicurati di rivedere la documentazione su mass assignment.
Relazioni belongsTo
Se desideri assegnare un modello figlio a un nuovo modello padre, puoi usare il metodo associate
. In questo esempio, il modello User
definisce una relazione belongsTo
con il modello Account
. Questo metodo associate
imposterà la foreign key sul modello figlio:
use App\Models\Account;
$account = Account::find(10);
$user->account()->associate($account);
$user->save();
Per rimuovere un modello padre da un modello figlio, puoi usare il metodo dissociate
. Questo metodo imposterà la foreign key della relazione su null
:
$user->account()->dissociate();
$user->save();
Relazioni Molti a Molti
Aggiungere / Rimuovere
Eloquent fornisce anche metodi per rendere più comodo lavorare con relazioni molti-a-molti. Ad esempio, immaginiamo che un utente possa avere molti ruoli e un ruolo possa avere molti utenti. Puoi usare il metodo attach
per aggiungere un ruolo a un utente inserendo un record nella tabella intermedia della relazione:
use App\Models\User;
$user = User::find(1);
$user->roles()->attach($roleId);
Quando aggiungi una relazione a un modello, puoi anche passare un array di dati aggiuntivi da inserire nella tabella intermedia:
$user->roles()->attach($roleId, ['expires' => $expires]);
A volte può essere necessario rimuovere un ruolo da un utente. Per rimuovere un record di una relazione molti-a-molti, usa il metodo detach
. Il metodo detach
eliminerà il record appropriato dalla tabella intermedia; tuttavia, entrambi i modelli rimarranno nel database:
// Scollega un singolo ruolo dall'utente...
$user->roles()->detach($roleId);
// Scollega tutti i ruoli dall'utente...
$user->roles()->detach();
Per comodità, attach
e detach
accettano anche array di ID come input:
$user = User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([
1 => ['expires' => $expires],
2 => ['expires' => $expires],
]);
Sincronizzare le Associazioni
Puoi anche usare il metodo sync
per costruire associazioni molti-a-molti. Il metodo sync
accetta un array di ID da inserire nella tabella intermedia. Qualsiasi ID non presente nell’array verrà rimosso dalla tabella intermedia. Quindi, dopo che questa operazione è completata, solo gli ID presenti nell’array esisteranno nella tabella intermedia:
$user->roles()->sync([1, 2, 3]);
Puoi anche passare valori aggiuntivi per la tabella intermedia insieme agli ID:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
Se desideri inserire gli stessi valori nella tabella intermedia con ciascuno degli ID sincronizzati, puoi usare il metodo syncWithPivotValues
:
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
Se non vuoi scollegare gli ID esistenti che mancano nell’array fornito, puoi usare il metodo syncWithoutDetaching
:
$user->roles()->syncWithoutDetaching([1, 2, 3]);
Commutare le Associazioni
Il rapporto molti-a-molti offre anche un metodo toggle
che "commuta" lo stato di collegamento degli ID dei modelli correlati forniti. Se l’ID dato è attualmente collegato, verrà scollegato. Allo stesso modo, se è attualmente scollegato, verrà collegato:
$user->roles()->toggle([1, 2, 3]);
Puoi anche passare valori aggiuntivi per la tabella intermedia insieme agli ID:
$user->roles()->toggle([
1 => ['expires' => true],
2 => ['expires' => true],
]);
Aggiornare un Record nella Tabella Intermedia
Se hai bisogno di aggiornare una riga esistente nella tabella intermedia della tua relazione, puoi usare il metodo updateExistingPivot
. Questo metodo accetta la chiave esterna del record intermedio e un array di attributi da aggiornare:
$user = User::find(1);
$user->roles()->updateExistingPivot($roleId, [
'active' => false,
]);
Toccare i Timestamp del Genitore
Quando un modello definisce una relazione belongsTo
o belongsToMany
con un altro modello, come un Comment
che appartiene a un Post
, a volte è utile aggiornare il timestamp del genitore quando il modello figlio viene aggiornato.
Ad esempio, quando un modello Comment
viene aggiornato, potresti voler "toccare" automaticamente il timestamp updated_at
del Post
proprietario in modo che venga impostato sulla data e ora correnti. Per fare ciò, puoi aggiungere una proprietà touches
al tuo modello figlio contenente i nomi delle relazioni il cui updated_at
deve essere aggiornato quando il modello figlio viene aggiornato:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Comment extends Model
{
/**
* Tutte le relazioni da toccare.
*
* @var array
*/
protected $touches = ['post'];
/**
* Ottieni il post a cui il commento appartiene.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}
I timestamp del modello genitore verranno aggiornati solo se il modello figlio viene aggiornato usando il metodo
save
di Eloquent.