Utilizzo delle relazioni con Eloquent ORM

by NewPortal SuperAdmin on 23/01/2018

Eloquent ORM (Object-Relational Mapping), un'implementazione Laravel del pattern "Active Record", semplifica la gestione e l'utilizzo delle relazioni tra entità attraverso la definizione, nei modelli eloquent, di alcuni semplici metodi sui quali poter concatenare altri metodi di interrogazione ed estrazione dati. Sappiamo che ad ogni modello Laravel corrisponderà una nostra tabella del DB, per es. al model "Customer" corrisponderà la tabella "customers". Per approfondire l'argomento, si consiglia la lettura della documentazione ufficiale Laravel. I tipi di relazione che vedremo in questo articolo sono:

Uno a uno
Uno a molti
Molti a molti
Relazioni polimorfiche uno a molti
Relazioni polimorfiche molti a molti


Uno a uno

E' una relazione tra due tabelle distinte, dove ad ogni record di una tabella, corrisponde uno ed un solo record dell'altra tabella. Ad esempio, considerando le tabelle Clienti e Indirizzi diremo che ad ogni cliente può corrispondere uno solo indirizzo.

// Comandi da eseguire sul terminale bash
php artisan make:model Customer -mc
php artisan make:model Address -mc

// Columns della tabella "customers"
$table->increments('id');
$table->string('name');
$table->timestamps();

// Columns della tabella "address"
$table->increments('id');
$table->string('name');
$table->integer('customer_id')->unsigned()->index()->nullable();
$table->foreign('customer_id')->references('id')->on('customers');
$table->timestamps();

// Nel model "Customer"
public function address() {
    return $this->hasOne(Address::class);
}

// Nel model "Address"
public function customer() {
    return $this->belongsTo(Customer::class); // relazione inversa
}

// nel controller "CustomerController"
// creiamo un cliente
$customer = new Customer;
$customer->name = "Craft Company";
$customer->save();

// creiamo un indirizzo
$address = new Address();
$address->name = "Via De Rosis, 331 - Vicenza";
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$customer->address()->save($address); // salviamo l'indirizzo associandolo al cliente
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Associa un indirizzo con il metodo create
$customer = Customer::find(2);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$customer->address()->create(['name'=> "Via R..., 33 - Milano"]);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// creiamo e salviamo un nuovo indirizzo
$address = new Address();
$address->name = "Via De Rosis, 555 - Parma";
$address->save();

$customer = Customer::findOrFail(2);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$address->customer()->associate($customer)->save(); // associamo l'indirizzo al cliente 2
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$address->customer()->dissociate()->save(); // imposta a null la chiave esterna
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Restituisce l'oggetto Address (istanza del modello di Address)
$customer->address; // es. $address = Customer::find(1)->address;

// Restituisce l'oggetto Customer (istanza del modello di Customer)
$address->customer;

// Accediamo alle proprietà dell'oggetto
$address->customer->name


Uno a molti

Un esempio tipico di relazione uno a molti è quello che lega gli ordini ai clienti. Un cliente può effettuare molti ordini, ma un ordine può appartenere ad un solo cliente.

// Comando da eseguire sul terminale bash
php artisan make:model Order -mc

// Columns della tabella "orders"
$table->increments('id');
$table->string('name');
$table->integer('customer_id')->unsigned()->index()->nullable();
$table->foreign('customer_id')->references('id')->on('customers');
$table->timestamps();

// Nel model "Customer"
public function orders()  {
    return $this->hasMany(Order::class);
}

// Nel model "Order"
public function customer()  {
    return $this->belongsTo(Customer::class); // relazione inversa
}

// Nel controller "CustomerController"
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$customer->orders()->save($order); // salva il singolo ordine
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$customer->orders()->saveMany([$order1,$order2]); // salva più ordini
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$customer->orders()->create(['name' => 'Ordine n.34']);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$customer->orders()->createMany([['name' => 'Ord. n.54'],['name' => 'Ord. n.66']]);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Salvataggio inverso
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->customer()->associate($customer)->save(); 
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->customer()->dissociate()->save(); // imposta a null la chiave esterna
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Restituisce una "Collections" di Order (istanze dei vari modelli Order)
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$orders = App\Customer::find(1)->orders;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Possiamo quindi effettuare un foreach o richiamare i metodi
// della classe Collection - es. ...->orders->each(function(...
foreach ($orders as $order) {
    $listOrder[$order->id] = $order->name;
}
// Possiamo convertire direttamente la Collections in array
$orders = App\Customer::find(1)->orders->toArray();

// Restituisce una istanza della classe HasMany basata su Query Builder
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order = App\Customer::find(1)->orders()
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// orders()->count(), ->first(), ->where() ->pluck('name', 'id'), ->orderBy('name')->get(); etc..

// Restituisce l'oggetto Customer (istanza del modello Customer)
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->customer;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Alcune applicazioni del metodo with()
Customer::with('orders')->findOrFail(1); // l'oggetto cliente e lista degli ordini
Customer::with('orders')->get(); // la lista dei clienti e per ognuno una lista degli ordini


Molti a molti

Ad esempio, ogni ordine può contenere più prodotti e ogni prodotto può essere presente in più ordini. Questo tipo di relazione è possibile solo definendo una terza tabella, chiamata tabella di congiunzione, composta, solitamente, dalle due chiavi esterne.

// Comandi da eseguire nel terminale bash
php artisan make:model Product -mc
php artisan make:migration create_order_product_table

// Columns della tabella "products"
$table->increments('id');
$table->string('name');
$table->timestamps();

// Columns della tabella "order_product"
$table->integer('order_id')->unsigned()->index();
$table->foreign('order_id')->references('id')->on('orders')->onDelete('cascade');
$table->integer('product_id')->unsigned()->index();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->integer('discount');
$table->primary(['order_id','product_id']);

// Nel Model Order
public function products() {
  return $this->belongsToMany(Product::class);
}
// Per interagire con la tabella associativa (pivot) concateniamo a belongsToMany i metodi:
// ->withPivot('discount')->withTimestamps();

// Nel Model Products
public function orders() {
  return $this->belongsToMany(Order::class);
}

// Volendo interagire con la tabella associativa (pivot) modifichiamo il metodo products
public function products() {
  return $this->belongsToMany(Order::class)->withPivot('discount')->withTimestamps();
}

// Aggiunge e rimuove la relazione
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->products()->attach($productId); // ->attach([1,2]), con elementi agg. (1,['discount'=>20])
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->products()->detach($productId); // ->detach([2,3]),  con ->detach(); remove all
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Sincronizzazione dei dati
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->products()->sync([1, 2, 3]); // remove all e aggancia solo i prodotti esistenti nell'array
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->products()->syncWithoutDetaching([1, 2, 3]); // preserva l'esistente
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Se collegato lo stacca e viceversa
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->products()->toggle([1, 2, 3]);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Inserimento e aggiornamento della tabella pivot
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->products()->updateExistingPivot($productiId, ['discount' => 40]);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$order->products()->save($product, ['discount'=>30]);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// è anche possibile utilizzare il metodo inverso, ovvero ->orders()
$product->orders()->attach([2,3]); // ->sync([1,2,3]); ->toggle([2, 3]) etc...

// Restituisce una "Collections" di Product (istanze dei vari modelli Product)
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$products = Order::find(1)->products;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
foreach ($products as $product) { // ->pivot accede alle proprietà della tabella intermedia
    echo $product->name ."-". $product->pivot->discount;
}

// Restituisce una istanza della classe BelongsToMany basata su Query Builder
$products = Order::find(1)->products() //->where(), ->first() etc...

// Alcune applicazioni del potente metodo with()
Customer::with('orders.products')->findOrFail(1); // l'oggetto cliente e lista degli ordini e pro.
// Restituisce la lista dei clienti e per ognuno la lista degli ordini e dei prodotti
Customer::with('orders.products')->get(); 


Relazione Polimorfica uno a molti

Se ad esempio volessimo mettere in relazione (1:m) più entità con la tabella Commenti, le relazioni Polimorfiche ci verrebbero incontro in quanto consentono di creare un'unica tabella Commenti e di gestire dinamicamente la chiave esterne delle diverse tabelle che si relazionano con essa. 

// Comandi da eseguire sul terminale bash
php artisan make:model Content -mc
php artisan make:model Comment -mc

// Columns della tabella "contents"
$table->increments('id')->unsigned();
$table->string('name', 150);
$table->text('content')->nullable();
$table->timestamps();

// Columns della tabella "comments"
$table->increments('id')->unsigned();
$table->text('body')->nullable();
$table->integer('commentable_id')->unsigned()->index(); // chiave esterna
$table->string('commentable_type')->nullable(); // modello esterno es. App\Content
$table->timestamps();

// Nel model Comment
public function commentable() {
    return $this->morphTo();  //relazione polimorfica
}

// Nel model Content
public function comments() {
    return $this->morphMany('App\Comment','commentable'); // metodo inverso
}

// salvataggio commenti
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content = Content::find(1);
$comment = new Comment(); // creo un oggetto commento
$comment->body = 'primo commento!';
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->comments()->save($comment); // salva il commento e lo associa a content
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->comments()->saveMany([$comment1,$comment2]); // salva più commenti
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->comments()->create(['body'=>'secondo commento']); // crea un commento da un array
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->comments()->createMany([['body' => 'terzo comment'],['body' => '...']]); 
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Restituisce una "Collections" di Comment (istanze dei vari modelli Comment)
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$comments = Content::find(1)->comments;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Possiamo quindi effettuare un foreach o richiamare i metodi
// della classe Collection - es. ...->comments->each(function(...  ->toArray() etc...
foreach ($comments as $comment) {
    $listComment[$comment->id] = $comment->body;
}

// Restituisce una istanza della classe MorphMany basata su Query Builder
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$comment = Content::find(1)->comments() 
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ->count(), ->first(), ->where() ->pluck('name', 'id'), ->orderBy('name')->get(); etc...

// restituisce una istanza del modello Content o di un altro tipo a seconda di quello associato
$comment = Comment::find(1);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$commentable = $comment->commentable;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Cancellazione dei commenti associati al content
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->comments()->delete()  // ->where(..)->delete() se si vuole filtrare i commenti
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


Relazione Polimorfica molti a molti

L'utilizzo di una relazione polimorfica molti a molti consente di disporre di un'unica tabella (es. Tag) da relazionare con altre tabelle (es. Webcontent, Post etc...) in un legame molti a molti, quindi con l'uso di una terza tabella associativa contenente le chiavi esterne gestite dinamicamente.

// Comandi da eseguire sul terminale bash
php artisan make:model Tag -mc
php artisan make:migration create_taggables_table

// Columns della Tabella "tags"
$table->increments('id')->unsigned();
$table->string('name')->nullable();
$table->timestamps();

// Columns della tabella "taggables"
$table->integer('tag_id')->unsigned()->index();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->integer('taggable_id')->unsigned()->index();
$table->string('taggable_type')->nullable();
$table->timestamps();

// Nel model Content
public function tags() {
    return $this->morphToMany('App\Tag', 'taggable');
}

// Nel model Tag
public function content()  {
    return $this->morphedByMany('App\Content', 'taggable'); // relazione inversa
}

// Restituisce una "Collections" di Tag (istanze dei vari modelli Tag)
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$tags = Content::find(2)->tags;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Restituisce una istanza della classe MorphToMany basata su Query Builder
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$tag = Content::find(1)->tags()
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// tags()->count(), ->first(), ->where() ->pluck('name', 'id'), ->orderBy('name')->get(); etc...

// Salvataggio commenti
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content = Content::find(1);
$tag = new Tag(); // creo un oggetto Tag
$tag->name = 'tag-1';
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->tags()->save($tag); // salva il tag e lo associa a content
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->tags()->saveMany([$tag1,$tag2]); // salva più tag
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->tags()->create(['name'=>'tag-2']); // crea un tag da un array
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->tags()->createMany([['name' => 'tag-3'],['name' => '...']]);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Aggiunge e rimuove la relazione
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->tags()->attach($tagId); // ->attach([1,2,3]), con elementi agg. (1,[...])
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->tags()->detach($tagId); // ->detach([2,3]),  con ->detach(); remove all
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// Sincronizzazione dei dati
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->tags()->sync([1, 2, 3]); // remove all e aggancia solo i tag esistenti nell'array
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
$content->tags()->syncWithoutDetaching([1, 2, 3]); // preserva l'esistente
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

// E' possibile anche far uso del metodo inverso ->content(), es.
$tag->content()->sync([1,2]); // ->attach(); ->detach(1); etc...

Send Comment