Zapobieganie dodawaniu przez Laravel wielu rekordów do tabeli przestawnej

99

Mam skonfigurowaną i działającą relację wiele do wielu, aby dodać przedmiot do koszyka, którego używam:

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

Powoduje to dodanie elementu do tabeli przestawnej (tak jak powinno), ale jeśli użytkownik ponownie kliknie łącze, aby dodać element, który już dodał, tworzy zduplikowany wpis w tabeli przestawnej.

Czy istnieje wbudowany sposób dodawania rekordu do tabeli przestawnej tylko wtedy, gdy taki rekord jeszcze nie istnieje?

Jeśli nie, jak mogę sprawdzić tabelę przestawną, aby dowiedzieć się, czy pasujący rekord już istnieje?

Glin_
źródło

Odpowiedzi:

78

Możesz sprawdzić obecność istniejącego rekordu, pisząc bardzo prosty warunek, taki jak ten:

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

Lub / i możesz dodać warunek unicity w swojej bazie danych, spowodowałby to wyjątek podczas próby zapisania dubletu.

Powinieneś także spojrzeć na prostszą odpowiedź Barryvdha poniżej.

Alexandre Butynski
źródło
1
Parametr $ id dla metody attach()jest mieszany, może to być int lub instancja modelu;) - patrz github.com/laravel/framework/blob/master/src/Illuminate/ ...
Rob Gordijn
Dziękuję @RobGordijn. Nauczyłem się czegoś dzisiaj! Odpowiedź edytowana.
Alexandre Butynski
1
@bagusflyer containssprawdza, czy klucz jest obecny w jednym obiekcie w kolekcji. Powinieneś mieć błąd w swoim kodzie.
Alexandre Butynski
5
Nie podoba mi się to rozwiązanie, ponieważ wymusza dodatkowe zapytanie ($ cart-> items) Robiłem coś takiego: $cart->items()->where('foreign_key', $foreignKey)->count() Które, cóż, faktycznie wykonuje również dodatkowe zapytanie '^^ Ale nie muszę pobierać i nawadniaj całą kolekcję, chyba że naprawdę tego potrzebuję.
Cisza
2
Zgadza się, Twoje rozwiązanie jest nieco bardziej zoptymalizowane. Możesz nawet użyć tej exists()funkcji zamiast count()dla najlepszej optymalizacji.
Alexandre Butynski
266

Możesz także skorzystać z tej $model->sync(array $ids, $detaching = true)metody i wyłączyć odłączanie (drugi parametr).

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

Aktualizacja: Od Laravel 5.3 lub 5.2.44 możesz także wywołać syncWithoutDetaching:

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

Co robi dokładnie to samo, ale bardziej czytelne :)

Barryvdh
źródło
2
Drugi parametr logiczny zapobiegający odłączaniu identyfikatorów spoza listy nie znajduje się w głównej dokumentacji L5. Dobrze wiedzieć - wydaje się być jedynym sposobem na „dołączenie, jeśli jeszcze nie jest dołączone” w jednym oświadczeniu.
Jason
11
Należy to przyjąć jako odpowiedź, jest to znacznie lepszy sposób na zrobienie tego niż przyjęta odpowiedź
Daniel
4
Dla początkujących jak ja: $cart->items()->sync([1, 2, 3])wybuduje wiele do wielu relacji do identyfikatora użytkownika w danej tablicy 1, 2i 3, i usuń (lub „odłączyć”) wszystkie pozostałe id nie jest w tablicy. W ten sposób w tabeli będą istnieć tylko identyfikatory podane w tablicy. Brilliant @Barryvdh używa nieudokumentowanego drugiego parametru, aby wyłączyć to odłączanie, więc żadne relacje spoza danej tablicy nie są usuwane, ale dołączane są tylko unikalne identyfikatory. Zapoznaj się z dokumentem „Synchronizacja dla wygody”. (Laravel 5.2)
identicon
3
Do Twojej wiadomości, zaktualizowałem dokumentację, więc 5.2 i nowsze : laravel.com/docs/5.2/... I Taylor natychmiast dodał add syncWithoutDetaching(), który wywołuje sync () z fałszem jako drugim parametrem.
Barryvdh
Laravel 5.5 laravel.com/docs/5.5/... syncWithoutDetaching() działał!
Josh LaMar
2

Metoda @alexandre Butynsky działa bardzo dobrze, ale używa dwóch zapytań sql.

Jeden do sprawdzenia, czy koszyk zawiera przedmiot, a drugi do zapisania.

Aby użyć tylko jednego zapytania, użyj tego:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}
Oktawian Ruda
źródło
To właśnie zrobiłem w swoim projekcie. Ale problem polega na tym, że nie wiesz, czy ten wyjątek jest spowodowany przez zduplikowany wpis, czy cokolwiek innego.
Bagusflyer
2
dlaczego miałby rzucać jakikolwiek wyjątek? byłoby, gdyby był jakiś unikalny klucz
Seiji Manoan
1

Wszystkie te odpowiedzi są dobre, ponieważ wypróbowałem je wszystkie, ale jedna rzecz pozostaje bez odpowiedzi lub nie została załatwiona: kwestia aktualizacji wcześniej zaznaczonej wartości (odznaczono zaznaczone pole [es]). Mam coś podobnego do powyższego pytania, spodziewam się, że chcę zaznaczyć i odznaczyć cechy produktów w mojej tabeli cech produktów (tabeli przestawnej). Jestem nowicjuszem i zdałem sobie sprawę, że żaden z powyższych nie zrobił tego. Oba są dobre przy dodawaniu nowych funkcji, ale nie wtedy, gdy chcę usunąć istniejące funkcje (tj. Odznacz je)

Docenię każde oświecenie w tej sprawie.

$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);
}

lub

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);
}

Przepraszam, nie jestem pewien, czy powinienem usunąć to pytanie, ponieważ po samodzielnym znalezieniu odpowiedzi brzmi to trochę głupio, cóż, odpowiedź na powyższe jest tak prosta, jak praca @Barryvdh sync () w następujący sposób; czytając coraz więcej o:

$features = $request->get('features');
if (isset($features) && Count($features)>0){
    $product->features()->sync($features);
}
adeguk Loggcity
źródło
0

Opublikowano już kilka świetnych odpowiedzi. Jednak ten też chciałem wyrzucić.

Odpowiedzi @AlexandreButynski i @Barryvdh są bardziej czytelne niż moja sugestia, co dodaje ta odpowiedź, to pewna efektywność.

Pobiera tylko wpisy dla bieżącej kombinacji (właściwie tylko identyfikator) i dołącza ją, jeśli nie istnieje. Metoda synchronizacji (nawet bez odłączania) pobiera wszystkie aktualnie dołączone identyfikatory. W przypadku mniejszych zestawów z małymi iteracjami nie będzie to żadna różnica, ... rozumiesz.

W każdym razie na pewno nie jest tak czytelny, ale załatwia sprawę.

if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
    $book->authors()->attach($author);
Peter de Kok
źródło