2014-04-08 29 views
6

Progetto Laravel 4, utilizzando ORM eloquente.Laravel: da uno a molti a molti, recupera valori distinti()

Ho tre tabelle: clienti, ordini e prodotti (+ 1 tabella pivot order_product). I clienti sono collegati uno-a-molti agli ordini. Gli ordini sono collegati molti-a-molti ai prodotti.

Customers 1-->N Orders N<-->N Products 

Mi piacerebbe avere un metodo sul modello Cliente che recuperi un elenco di prodotti che il cliente sta acquistando.

Per capire meglio questo, supponiamo che i prodotti siano consumabili.

Ad esempio clienti # 1 può disporre:

  • Order # 1 per i prodotti A, B e C;
  • Ordine n. 2 per prodotti A, C e D;
  • Ordine n. 3 per prodotti C ed E;

... e il risultato che voglio recuperare è una collezione con i prodotti A, B, C, D ed E.

modelli sono (pseudo-codice al volo):

class Product extends Eloquent { 

    public function orders() 
    { 
     return $this->belongsToMany('Order'); 
    } 

} 

class Orders extends Eloquent { 

    public function customer() 
    { 
     return $this->belongsTo('Customer', 'customer_id'); 
    } 

    public function products() 
    { 
     return $this->belongsToMany('Product'); 
    } 

} 

class Customers extends Eloquent { 

    public function orders() 
    { 
     return $this->hasMany('Orders', 'customer_id'); 
    } 

    public function products() 
    { 
     // What to put here ??? 
    } 

} 
+0

Potrebbe essere possibile utilizzare hasManyThrough e targetizzare la tabella pivot (solitamente implicita). Qualcosa come 'return $ this-> masManyThrough ('Order', 'OrderProduct') -> con ('Product');' Ma se ciò non funziona devi fare un po 'di lavoro manuale in quel metodo e restituire una query. Non sarà troppo difficile (basta creare una query come faresti senza la comodità di una relazione) ma non sarà bella come una relazione standard. – alexrussell

+0

@alexrussell: Devo creare un modello per la tabella pivot per questo? Inoltre, il mio kung-fu con il sistema di query di basso livello Eloquent non è così forte, potresti indicare il codice reale (come risposta, forse)? – St0rM

+0

sì, penso che avresti bisogno di modellare la tabella pivot per usare il mio '-> hasManyThrough() -> con il metodo()', altrimenti Laravel non sa come relazionare la tabella pivot (che è ciò che tu? rimasta con dopo 'hasManyThrough') alla tabella' products' (che è un 'hasMany' della tabella pivot). Per quanto riguarda l'altro modo in cui è probabile che il metodo 'hasManyThrough' non funzioni, vedrò cosa posso inventarti per te. – alexrussell

risposta

2

Grazie alla risposta di @ deczo, sono stato in grado di mettere su un unico metodo query per recuperare gli elementi:

public function items() 
{ 
    $query = DB::table('items')->select('items.*') 
     ->join('item_order', 'item_order.component_id', '=', 'items.id') 
     ->leftJoin('orders', 'item_order.order_id', '=', 'orders.id') 
     ->leftJoin('customers', 'customers.id' , '=', 'orders.customer_id') 
     ->where('customers.id', $this->id) 
     ->distinct() 
     ->orderBy('items.id'); 

    $eloquent = new Illuminate\Database\Eloquent\Builder($query); 
    $eloquent->setModel(new Item); 
    return $eloquent->get(); 
} 
1

non riesco a pensare di facile metodo di relazione per questo, ma ecco una soluzione:

$productsIds = DB::table('customers') 
    ->leftJoin('orders', 'orders.customer_id', '=', 'customers.id') 
    ->join('order_item', 'order_item.order_id', '=', 'orders.id') 
    ->leftJoin('items', 'order_item.item_id' , '=', 'items.id') 
    ->distinct() 
    ->get(['items.id']); 

$productsIds = array_fetch($productsIds, 'id'); 

$productsCollection = Product::whereIn('id', $productsIds); 
+0

Grazie a @deczo, ho intenzione di provarlo. E mi ha anche aiutato a capire meglio come funziona l'oggetto DB :-) – St0rM

+0

Penso che manchi solo un -> dove ('customers.id', $ this-> id). A proposito, c'è un metodo migliore per ottenere una collezione direttamente dall'oggetto DB? Se ho capito bene, questo fa due domande e dopo il primo ho già tutto ciò di cui ho bisogno. – St0rM

+0

Sì, per un determinato utente è necessario aggiungere un'istruzione dove proprio come si parla. Informazioni sulla raccolta Eloquent: dal generatore di query DB non si ottiene risultato Eloquent (modello/collezione di modelli), quindi l'unico modo per farlo in una singola query è utilizzare il modello prodotto dall'inizio. Prova a trovare quella domanda e fammi sapere se hai bisogno di aiuto –

0

@ risposta di deczo probabilmente funziona bene, ed è probabilmente molto più performante come tutta la riduzione dei dati viene eseguita nel database stesso, ma ecco un modo 'pura laravel' che è senza dubbio più leggibile:

use Illuminate\Database\Eloquent\Collection; 

class Customer extends Eloquent 
{ 
    ... 
    public function products() 
    { 
     $products = new Collection; 
     foreach ($this->orders as $order) { 
      $products->merge($order->products); 
     } 

     return $products; 
    } 
} 

Si noti che questo metodo non si comporterà come normali metodi di relazione - per ottenere la collezione risultante si chiama il metodo (es $products = $customer->products();) e non è possibile accedervi come una proprietà come è possibile con le relazioni (ad esempio non è possibile fare $products = $customer->products;).

Inoltre, ho iniziato a comprendere il metodo Illuminate\Database\Eloquent\Collection#merge() in questo modo che fa automaticamente una cosa simile a DISTINCT. In caso contrario, dovrai fare una cosa del genere $collection->unique().

+0

Grazie mille, questo è quello che sto facendo adesso ed è bello avere una conferma. Per quanto ho capito, il problema principale è la performance (penso che generi query N + 1). L'unione effettivamente restituisce una raccolta unica (ma non ordinata), che può essere facilmente ordinata con un SortBy ('id'). – St0rM

+0

Sì, esattamente - non è performante ma è più facile leggere il codice, quindi dipende in qualche modo dalla dimensione dei tuoi dati e dal fatto che tu possa o meno prendere il colpo. – alexrussell

3

Questa è una relazione molti-a-molti, ma con le Ordini tavolo come il tabella pivot.

class Customers extends Eloquent { 

    public function orders() 
    { 
     return $this->hasMany('Orders', 'customer_id'); 
    } 

    public function products() 
    { 
     return $this->belongsToMany('Products', 'orders', 'customer_id', 'product_id'); 
    } 
} 

Ho incluso gli ultimi due parametri, ma se si segue il modello singular_id possono essere lasciati fuori.

+0

Questo è * brillante *. Grazie :-) – St0rM

Problemi correlati