graficajam
Relazioni Eloquent e l’efficienza della ‘pigrizia’ in Laravel

Relazioni Eloquent e l’efficienza della ‘pigrizia’ in Laravel

Giamp26/03/2024

Ciao! Oggi facciamo un riepilogo delle relazioni che Eloquent mette a disposizione per gestire le associazioni di dati nel database in modo efficiente e “pigro”.

È importante distinguere due differenti modalità di recupero delle relazioni: proprietà e metodo.

  • Proprietà: recupera direttamente il valore della relazione come se fosse una proprietà del modello.
  • Metodo: permette di invocare il metodo della relazione e concatenare ulteriori metodi del query builder per filtrare o manipolare i dati.
# Proprietà
$profile = User::find($id)->profile;

# Metodo
$profile_ogg = User::find($id)->profile()->where('active', true)->get();

One to One

La relazione One to One collega un record di una tabella con un solo record di un’altra tabella.
Esempio: un utente ha un solo profilo.

# User model
public function profile() {
    return $this->hasOne(Profile::class, 'user_id', 'id'); # chiave esterna Profile.user_id, chiave locale User.id
}

Nota: se non specifichi le chiavi, Eloquent assume id come chiave primaria del modello principale e modello_id come chiave esterna nella tabella secondaria.

Relazione inversa:

# Profile model
public function user() {
    return $this->belongsTo(User::class, 'user_id', 'id'); # chiave locale Profile.user_id, chiave esterna User.id
}

One to Many

La relazione One to Many collega un record di una tabella a più record di un’altra.
Esempio: un autore ha molti post.

# Author model
public function posts() {
    return $this->hasMany(Post::class, 'author_id', 'id'); # chiave esterna Post.author_id, chiave locale Author.id
}

Relazione inversa:

# Post model
public function author() {
    return $this->belongsTo(User::class, 'author_id', 'id'); # chiave locale Post.author_id, chiave esterna User.id
}

Many to Many

La relazione Many to Many collega tanti record a tanti record, usando una tabella pivot.

Struttura tabella pivot:

# tabella pivot post_tag 
# post_tag
# post_id

Esempio: post e tag.

# Post model
public function tags() {
    return $this->belongsToMany(
        Tag::class,       # modello correlato
        'post_tag',       # tabella pivot
        'post_id',        # chiave locale sulla tabella pivot
        'tag_id'          # chiave esterna sulla tabella pivot
    )->withTimestamps();
}

Se volessimo aggiungere I campi di timestamp sulla nostra tabella pivot, possiamo farlo utilizzando withTimestamps(), mentre se abbiamo la necessità di aggiungere altri campi useremo withPivot()

Relazione inversa nel modello Tag:

public function posts() {
    return $this->belongsToMany(
        Post::class,
        'post_tag',
        'tag_id',
        'post_id'
    )->withTimestamps();
}

Associare record alla pivot:
Per gestire l’associazione nel controller dei nostri dati su la tabella pivot, abbiamo a disposizione due metodi: attach() e sync().

Il secondo, in particolare, ci permette di sincronizzare gli id nella tabella pivot, possiamo passare anche degli argomenti per un solo id, oppure o per tutti grazie a syncWithPivotValues .

# Associare più id
$post->tags()->sync([1, 2, 3]);

# Con dati aggiuntivi
$post->tags()->sync([1 => ['expires' => true], 2, 3]);

# Con syncWithPivotValues
$post->tags()->syncWithPivotValues([1,2,3], ['active' => true]);

HasManyThrough

Permette di collegare due modelli indirettamente passando attraverso un terzo modello.
Esempio: Artist → Album → Song.

# Database
# artists: id, name
# albums: id, name, artist_id
# songs: id, name, album_id
Artist model
public function songs() {
    return $this->hasManyThrough(
        Song::class,     # modello finale
        Album::class,    # modello intermedio
        'artist_id',     # chiave esterna su Album
        'album_id',      # chiave esterna su Song
        'id',            # chiave locale su Artist
        'id'             # chiave locale su Album
    );
}

Recupero dati:

$songs = Artist::find(1)->songs; # collezione di tutte le canzoni dell'artista 1

Lazy Loading ed Eager Loading

Eloquent carica le relazioni in modo pigro (lazy loading) per impostazione predefinita. Questo significa che la relazione viene interrogata solo quando viene effettivamente utilizzata.

# Lazy loading
$users = User::all();

foreach ($users as $user) {
    echo $user->profile->name; # query eseguita qui
}

Questo approccio è comodo ma può generare il classico problema N+1 query.

Eager Loading con with()

Per evitare query inutili possiamo caricare le relazioni in anticipo usando with().

# Eager loading
$users = User::with('profile')->get();

foreach ($users as $user) {
    echo $user->profile->name; # nessuna query aggiuntiva
}

Possiamo caricare più relazioni contemporaneamente:

$users = User::with(['profile', 'posts'])->get();

E anche relazioni annidate:

$users = User::with('posts.comments')->get();

Caricamento dinamico con load()

Se abbiamo già recuperato il modello e vogliamo caricare la relazione in un secondo momento, possiamo usare load().

$user = User::find(1);
$user->load('profile');

Questo è utile quando la necessità della relazione dipende da una condizione runtime.

In breve:

  • Lazy loading: comodo ma rischioso sulle performance
  • Eager loading (with): consigliato quando sappiamo già quali relazioni servono
  • load(): eager loading "posticipato"

Grazie per aver letto questo articoletto, alla prossima puntata!

Hai qualcosa da dire?

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

leggi anche...