Impedire a Laravel di aggiungere più record a una tabella pivot


97

Ho una relazione molti a molti impostata e funzionante, per aggiungere un articolo al carrello che utilizzo:

$cart->items()->attach($item);

Che aggiunge un elemento alla tabella pivot (come dovrebbe), ma se l'utente fa nuovamente clic sul collegamento per aggiungere un elemento che ha già aggiunto, crea una voce duplicata nella tabella pivot.

Esiste un modo integrato per aggiungere un record a una tabella pivot solo se non ne esiste già uno?

In caso contrario, come posso controllare la tabella pivot per scoprire se esiste già un record corrispondente?

Risposte:


76

Puoi verificare la presenza di un record esistente scrivendo una condizione molto semplice come questa:

if (! $cart->items->contains($newItem->id)) {
    $cart->items()->save($newItem);
}

Oppure / e puoi aggiungere la condizione di unicità nel tuo database, genererebbe un'eccezione durante un tentativo di salvare un doppietto.

Dovresti anche dare un'occhiata alla risposta più semplice di Barryvdh appena sotto.


1
Il parametro $ id per il metodo attach()è misto, può essere un int o un'istanza di modello;) - vedi github.com/laravel/framework/blob/master/src/Illuminate/…
Rob Gordijn

Grazie @RobGordijn. Ho imparato qualcosa oggi! Risposta modificata.
Alexandre Butynski

1
L' containsistruzione @bagusflyer controlla se la chiave è presente in un oggetto nella raccolta. Dovresti avere un errore nel codice.
Alexandre Butynski

4
Non mi piace questa soluzione perché forza una query extra ($ cart-> items) Ho fatto qualcosa del tipo: $cart->items()->where('foreign_key', $foreignKey)->count() che, beh, in realtà esegue anche una query aggiuntiva '^^ Ma non ho bisogno di recuperare e idratare l'intera collezione a meno che non ne abbia davvero bisogno.
Silenzio

1
Esatto, la tua soluzione è un po 'più ottimizzata. Puoi anche utilizzare la exists()funzione invece di count()per la migliore ottimizzazione.
Alexandre Butynski

264

Puoi anche usare il $model->sync(array $ids, $detaching = true)metodo e disabilitare il distacco (il secondo parametro).

$cart->items()->sync([$item->id], false);

Aggiornamento: a partire da Laravel 5.3 o 5.2.44, puoi anche chiamare syncWithoutDetaching:

$cart->items()->syncWithoutDetaching([$item->id]);

Che fa esattamente lo stesso, ma più leggibile :)


2
Il secondo parametro booleano per impedire il distacco di ID non in elenco non è nella documentazione principale di L5. È molto buono da sapere - sembra essere l'unico modo per "allegare se non già allegato" in una singola dichiarazione.
Jason

11
Questa dovrebbe essere accettata come risposta, è un modo molto migliore per farlo rispetto alla risposta accettata
Daniele

4
Per i neofiti come me: $cart->items()->sync([1, 2, 3])costruiranno molti-a-molti rapporti con l'id del nell'array data, 1, 2, e 3ed eliminare (o "detach") tutti gli altri id non è nella matrice. In questo modo, nella tabella esisteranno solo gli ID forniti nell'array. Brilliant @Barryvdh utilizza un secondo parametro non documentato per disabilitare tale distacco, quindi nessuna relazione non presente nell'array specificato verrà eliminata ma verranno allegati solo ID univoci. Controlla il documento "sincronizzazione per comodità". (Laravel 5.2)
identicon

3
Cordiali saluti, ho aggiornato i documenti, quindi 5.2 e versioni successive: laravel.com/docs/5.2/… E Taylor ha aggiunto immediatamente add syncWithoutDetaching(), che chiama sync () con false come secondo parametro.
Barryvdh

Laravel 5.5 laravel.com/docs/5.5/… ha syncWithoutDetaching() funzionato!
Josh LaMar,

2

Il metodo @alexandre Butynsky funziona molto bene ma usa due query sql.

Uno per controllare se il carrello contiene l'articolo e uno per salvare.

Per utilizzare una sola query usa questo:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}

Questo è quello che ho fatto nel mio progetto. Ma il problema è che non sai se questa eccezione è causata dalla voce duplicata o da qualsiasi altra cosa.
Bagusflyer

2
perché dovrebbe generare un'eccezione? sarebbe se ci fosse una chiave univoca
Seiji Manoan

1

Per quanto tutte queste risposte siano buone perché le avevo provate tutte, una cosa è rimasta senza risposta o non è stata risolta: il problema dell'aggiornamento di un valore precedentemente selezionato (deselezionate le caselle selezionate). Ho qualcosa di simile alla domanda precedente, mi aspetto di voler controllare e deselezionare le caratteristiche dei prodotti nella mia tabella delle caratteristiche del prodotto (la tabella pivot). Sono un principiante e mi sono reso conto che nessuno dei precedenti lo ha fatto. Sono entrambi buoni quando si aggiungono nuove funzionalità ma non quando voglio rimuovere funzionalità esistenti (cioè deselezionarlo)

Apprezzerò ogni chiarimento su questo.

$features = $request->get('features');

if (isset($features) && Count($features)>0){
    foreach ($features as $feature_id){
        $feature = Feature::whereId($feature_id)->first();
        $product->updateFeatures($feature);
    }
}

//product.php (extract)
public function updateFeatures($feature) {
        return $this->features()->sync($feature, false);
}

o

public function updateFeatures($feature) {
   if (! $this->features->contains($features))
        return $this->features()->attach($feature);
}
//where my attach() is:
public function addFeatures($feature) {
        return $this->features()->attach($feature);
}

Scusate ragazzi, non sono sicuro che dovrei cancellare la domanda perché dopo aver capito la risposta da solo, sembra un po 'stupido, beh, la risposta a quanto sopra è semplice come lavorare con @Barryvdh sync () come segue; avendo letto sempre di più su:

$features = $request->get('features');
if (isset($features) && Count($features)>0){
    $product->features()->sync($features);
}

0

Puoi semplicemente usare

$cart->items()->sync($items)

A partire da Laravel 5.7:

Sincronizzazione delle associazioni È inoltre possibile utilizzare il metodo di sincronizzazione per creare associazioni molti-a-molti. Il metodo di sincronizzazione accetta una matrice di ID da posizionare sulla tabella intermedia. Tutti gli ID che non sono nella matrice data verranno rimossi dalla tabella intermedia. Quindi, al termine di questa operazione, nella tabella intermedia saranno presenti solo gli ID nell'array specificato:


Questa è la soluzione migliore
eifersucht

Questo non risponde nemmeno alla domanda, ovvero come aggiungere una singola relazione evitando righe duplicate.
hackel

La sincronizzazione aggiungerà elementi ed eviterà di creare righe duplicate.
Shreyansh Panchal,

Per completare il commento di @hackel, rimuoverà anche tutti gli altri articoli dal carrello.
chrissharkman

0

Ci sono già alcune ottime risposte pubblicate. Però volevo buttare anche questo qui.

Le risposte di @AlexandreButynski e @Barryvdh sono più leggibili del mio suggerimento, ciò che questa risposta aggiunge è una certa efficienza.

Recupera solo le voci per la combinazione corrente (in realtà solo l'id) e lo allega se non esiste. Il metodo di sincronizzazione (anche senza scollegarlo) recupera tutti gli ID attualmente collegati. Per set più piccoli con piccole iterazioni questa difficilmente farà differenza, ... ottieni il mio punto.

Comunque, sicuramente non è così leggibile, ma fa il trucco.

if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
    $book->authors()->attach($author);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.