Verhindern, dass Laravel einer Pivot-Tabelle mehrere Datensätze hinzufügt

97

Ich habe viele, viele Beziehungen aufgebaut und arbeite, um einen Artikel in den Warenkorb zu legen, den ich benutze:

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

Dadurch wird der Pivot-Tabelle ein Element hinzugefügt (wie es sollte). Wenn der Benutzer jedoch erneut auf den Link klickt, um ein bereits hinzugefügtes Element hinzuzufügen, wird ein doppelter Eintrag in der Pivot-Tabelle erstellt.

Gibt es eine integrierte Möglichkeit, einen Datensatz nur dann zu einer Pivot-Tabelle hinzuzufügen, wenn noch keiner vorhanden ist?

Wenn nicht, wie kann ich die Pivot-Tabelle überprüfen, um festzustellen, ob bereits ein übereinstimmender Datensatz vorhanden ist?

Al_
quelle

Antworten:

76

Sie können das Vorhandensein eines vorhandenen Datensatzes überprüfen, indem Sie eine sehr einfache Bedingung wie diese schreiben:

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

Oder / und Sie können Ihrer Datenbank eine Einheitsbedingung hinzufügen, die beim Versuch, ein Dublett zu speichern, eine Ausnahme auslöst.

Sie sollten sich auch die einfachere Antwort von Barryvdh unten ansehen.

Alexandre Butynski
quelle
1
Der $ id-Parameter für die Methode attach()ist gemischt, er kann ein Int oder eine Instanz des Modells sein;) - siehe github.com/laravel/framework/blob/master/src/Illuminate/…
Rob Gordijn
Vielen Dank, dass Sie @RobGordijn. Ich habe heute etwas gelernt! Antwort bearbeitet.
Alexandre Butynski
1
@ bagusflyer- containsAnweisung Überprüfen Sie, ob der Schlüssel in einem Objekt in der Auflistung vorhanden ist. Sie sollten einen Fehler in Ihrem Code haben.
Alexandre Butynski
4
Ich mag diese Lösung nicht, weil sie eine zusätzliche Abfrage erzwingt ($ cart-> items). Ich habe so etwas gemacht: $cart->items()->where('foreign_key', $foreignKey)->count() Was eigentlich auch eine zusätzliche Abfrage ausführt '^^ Aber ich muss nicht abrufen und Hydratieren Sie die gesamte Sammlung, es sei denn, ich brauche sie wirklich.
Schweigen
1
Richtig, Ihre Lösung ist etwas optimierter. Sie können die exists()Funktion sogar anstelle count()der besten Optimierung verwenden.
Alexandre Butynski
264

Sie können auch die $model->sync(array $ids, $detaching = true)Methode verwenden und das Trennen deaktivieren (der zweite Parameter).

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

Update: Seit Laravel 5.3 oder 5.2.44 können Sie auch syncWithoutDetaching aufrufen:

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

Welches macht genau das gleiche, aber besser lesbar :)

Barryvdh
quelle
2
Der zweite boolesche Parameter zum Verhindern des Trennens nicht aufgelisteter IDs befindet sich nicht in der Hauptdokumentation zu L5. Es ist sehr gut zu wissen - scheint die einzige Möglichkeit zu sein, in einer einzigen Anweisung "anzuhängen, wenn nicht bereits angehängt".
Jason
11
Dies sollte als Antwort akzeptiert werden, es ist ein viel besserer Weg, dies zu tun als die akzeptierte Antwort
Daniel
4
Für Anfänger wie ich: $cart->items()->sync([1, 2, 3])viele-zu-viele Beziehungen zu dem IDs in der gegebenen Anordnung konstruieren, 1, 2, und 3, und löscht (oder „detach“) all andere ID ist in dem Array nicht. Auf diese Weise sind nur die im Array angegebenen IDs in der Tabelle vorhanden. Brilliant @Barryvdh verwendet einen undokumentierten zweiten Parameter, um diese Trennung zu deaktivieren, sodass keine Beziehungen gelöscht werden, die nicht im angegebenen Array enthalten sind, sondern nur eindeutige IDs angehängt werden. Lesen Sie das Dokument "Synchronisierung aus Gründen der Benutzerfreundlichkeit". (Laravel 5.2)
Identicon
3
Zu Ihrer Information , ich habe die Dokumente aktualisiert, also 5.2 und höher: laravel.com/docs/5.2/… Und Taylor fügte sofort add hinzu syncWithoutDetaching(), wodurch sync () mit false als zweitem Parameter aufgerufen wird.
Barryvdh
Laravel 5.5 laravel.com/docs/5.5/… hat syncWithoutDetaching() funktioniert!
Josh LaMar
2

Die @ alxandre Butynsky-Methode funktioniert sehr gut, verwendet jedoch zwei SQL-Abfragen.

Eine zum Überprüfen, ob der Warenkorb den Artikel enthält, und eine zum Speichern.

Um nur eine Abfrage zu verwenden, verwenden Sie Folgendes:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}
Octavian Ruda
quelle
Das habe ich in meinem Projekt gemacht. Das Problem ist jedoch, dass Sie nicht wissen, ob diese Ausnahme durch den doppelten Eintrag oder etwas anderes verursacht wird.
Bagusflyer
2
warum sollte es eine Ausnahme werfen? Es wäre, wenn es einen einzigartigen Schlüssel
gäbe
1

So gut all diese Antworten sind, weil ich sie alle ausprobiert habe, eines bleibt immer noch unbeantwortet oder wird nicht erledigt: das Problem der Aktualisierung eines zuvor aktivierten Werts (deaktiviert das Kontrollkästchen [es]). Ich habe etwas Ähnliches wie die obige Frage. Ich möchte die Funktionen von Produkten in meiner Produkt-Feature-Tabelle (der Pivot-Tabelle) aktivieren und deaktivieren. Ich bin ein Neuling und ich habe festgestellt, dass keiner der oben genannten das getan hat. Die sind beide gut, wenn Sie neue Funktionen hinzufügen, aber nicht, wenn ich vorhandene Funktionen entfernen möchte (dh deaktivieren)

Ich werde jede Erleuchtung in diesem Bereich schätzen.

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

oder

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

Sorry Leute, ich bin mir nicht sicher, ob ich die Frage löschen soll, denn nachdem ich die Antwort selbst herausgefunden habe, klingt es ein bisschen dumm. Die Antwort auf das oben Gesagte ist so einfach wie das Arbeiten mit @Barryvdh sync () wie folgt. immer mehr gelesen über:

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

Sie können einfach verwenden

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

Ab Laravel 5.7:

Synchronisieren von Assoziationen Sie können die Synchronisierungsmethode auch verwenden, um viele-zu-viele Assoziationen zu erstellen. Die Synchronisierungsmethode akzeptiert ein Array von IDs, die in die Zwischentabelle eingefügt werden sollen. Alle IDs, die nicht im angegebenen Array enthalten sind, werden aus der Zwischentabelle entfernt. Nach Abschluss dieses Vorgangs sind in der Zwischentabelle nur die IDs des angegebenen Arrays vorhanden:

Shreyansh Panchal
quelle
Dies ist die beste Lösung
eifersucht
Dies beantwortet nicht einmal die Frage, wie eine einzelne Beziehung hinzugefügt werden soll, während doppelte Zeilen vermieden werden.
Hackel
Durch die Synchronisierung werden Elemente hinzugefügt und es werden keine doppelten Zeilen erstellt.
Shreyansh Panchal
Um den Kommentar von @hackel zu vervollständigen, werden auch alle anderen Artikel aus dem Warenkorb entfernt.
Chrissharkman
0

Es gibt bereits einige großartige Antworten. Ich wollte diesen aber auch hier rauswerfen.

Die Antworten von @AlexandreButynski und @Barryvdh sind besser lesbar als mein Vorschlag. Diese Antwort fügt eine gewisse Effizienz hinzu.

Es ruft nur die Einträge für die aktuelle Kombination ab (eigentlich nur die ID) und hängt sie dann an, wenn sie nicht vorhanden ist. Die Synchronisierungsmethode (auch ohne Trennung) ruft alle aktuell angehängten IDs ab. Für kleinere Mengen mit kleinen Iterationen wird dies kaum ein Unterschied sein, ... Sie verstehen, worum es geht.

Wie auch immer, es ist definitiv nicht so lesbar, aber es macht den Trick.

if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
    $book->authors()->attach($author);
Peter de Kok
quelle