Empêcher Laravel d'ajouter plusieurs enregistrements à un tableau croisé dynamique

98

J'ai mis en place une relation plusieurs à plusieurs et je travaille pour ajouter un article au panier que j'utilise:

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

Ce qui ajoute un élément au tableau croisé dynamique (comme il se doit), mais si l'utilisateur clique à nouveau sur le lien pour ajouter un élément qu'il a déjà ajouté, cela crée une entrée en double dans le tableau croisé dynamique.

Existe-t-il un moyen intégré d'ajouter un enregistrement à un tableau croisé dynamique uniquement s'il n'en existe pas déjà?

Sinon, comment puis-je consulter le tableau croisé dynamique pour trouver si un enregistrement correspondant existe déjà?

Al_
la source

Réponses:

76

Vous pouvez vérifier la présence d'un enregistrement existant en écrivant une condition très simple comme celle-ci:

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

Ou / et vous pouvez ajouter une condition d'unicité dans votre base de données, cela lèverait une exception lors d'une tentative de sauvegarde d'un doublet.

Vous devriez également jeter un œil à la réponse plus simple de Barryvdh juste en dessous.

Alexandre Butynski
la source
1
Le paramètre $ id pour la méthode attach()est mixte, il peut s'agir d'un int ou d'une instance de model;) - voir github.com/laravel/framework/blob/master/src/Illuminate/…
Rob Gordijn
Merci @RobGordijn. J'ai appris quelque chose aujourd'hui! Réponse modifiée.
Alexandre Butynski
1
L' containsinstruction @bagusflyer vérifie si la clé est présente dans un objet de la collection. Vous devriez avoir une erreur dans votre code.
Alexandre Butynski
4
Je n'aime pas cette solution car elle force une requête supplémentaire ($ cart-> items) J'ai fait quelque chose comme: $cart->items()->where('foreign_key', $foreignKey)->count() Qui, eh bien, exécute aussi une requête supplémentaire '^^ Mais je n'ai pas besoin de chercher et hydratez toute la collection à moins que j'en ai vraiment besoin.
Silence
1
C'est vrai, votre solution est un peu plus optimisée. Vous pouvez même utiliser la exists()fonction au lieu de count()pour la meilleure optimisation.
Alexandre Butynski
265

Vous pouvez également utiliser la $model->sync(array $ids, $detaching = true)méthode et désactiver le détachement (le deuxième paramètre).

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

Mise à jour: depuis Laravel 5.3 ou 5.2.44, vous pouvez également appeler syncWithoutDetaching:

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

Ce qui fait exactement la même chose, mais plus lisible :)

Barryvdh
la source
2
Le deuxième paramètre booléen pour empêcher le détachement des ID non répertoriés ne figure pas dans la documentation principale de L5. Il est très bon de savoir - semble être le seul moyen de «joindre s'il n'est pas déjà joint» dans une seule déclaration.
Jason le
11
Cela devrait être accepté comme la réponse, c'est une bien meilleure façon de le faire que la réponse acceptée
Daniel
4
Pour les débutants comme moi: $cart->items()->sync([1, 2, 3])construiront plusieurs à-plusieurs à de l'identifiant dans le tableau donné, 1, 2et 3, et supprimer (ou « Détacher ») tous les autres id est pas dans le tableau. De cette façon, seuls les identifiants donnés dans le tableau existeront dans la table. Brilliant @Barryvdh utilise un deuxième paramètre non documenté pour désactiver ce détachement, donc aucune relation n'appartenant pas au tableau donné n'est supprimée mais seuls les identifiants uniques seront attachés. Consultez le document «Synchronisation pour la convienence». (Laravel 5.2)
identicon
3
Pour info, j'ai mis à jour les docs, donc 5.2 et plus: laravel.com/docs/5.2/ ... Et Taylor a immédiatement ajouté add syncWithoutDetaching(), qui appelle sync () avec false comme second paramètre.
Barryvdh
Laravel 5.5 laravel.com/docs/5.5/... a syncWithoutDetaching() fonctionné!
Josh LaMar
2

La méthode @alexandre Butynsky fonctionne très bien mais utilise deux requêtes SQL.

Un pour vérifier si le panier contient l'article et un pour sauvegarder.

Pour n'utiliser qu'une seule requête, utilisez ceci:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}
Octavian Ruda
la source
C'est ce que j'ai fait dans mon projet. Mais le problème est que vous ne savez pas si cette exception est causée par l'entrée dupliquée ou autre chose.
Bagusflyer
2
pourquoi jetterait-il une exception? ce serait s'il y avait une clé unique
Seiji Manoan
1

Aussi bonnes que soient toutes ces réponses parce que je les avais toutes essayées, une chose est toujours laissée sans réponse ou non résolue: le problème de la mise à jour d'une valeur précédemment cochée (décoché la case [es]). J'ai quelque chose de similaire à la question ci-dessus, je veux vérifier et décocher les caractéristiques des produits dans mon tableau des caractéristiques des produits (le tableau croisé dynamique). Je suis un débutant et j'ai réalisé que rien de ce qui précède ne faisait cela. Les deux sont bons lors de l'ajout de nouvelles fonctionnalités mais pas lorsque je veux supprimer des fonctionnalités existantes (c'est-à-dire décochez-le)

J'apprécierai tout éclaircissement à ce sujet.

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

ou

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

Désolé les gars, je ne suis pas sûr que je devrais supprimer la question car après avoir trouvé la réponse moi-même, cela semble un peu stupide, eh bien la réponse à ce qui précède est aussi simple que de travailler @Barryvdh sync () comme suit; après avoir lu de plus en plus sur:

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

Il y a déjà de bonnes réponses publiées. Je voulais aussi jeter celui-ci ici.

Les réponses de @AlexandreButynski et @Barryvdh sont plus lisibles que ma suggestion, ce que cette réponse ajoute, c'est une certaine efficacité.

Il récupère uniquement les entrées de la combinaison actuelle (en fait uniquement l'identifiant) et l'attache ensuite si elle n'existe pas. La méthode sync (même sans détacher) récupère tous les identifiants actuellement attachés. Pour les petits ensembles avec de petites itérations, cela ne fera guère de différence, ... vous voyez mon point.

Quoi qu'il en soit, ce n'est certainement pas aussi lisible, mais cela fait l'affaire.

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