Evitando que o Laravel adicione vários registros a uma tabela dinâmica

97

Tenho uma relação muitos para muitos configurada e funcionando, para adicionar um item ao carrinho que utilizo:

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

O que adiciona um item à tabela dinâmica (como deveria), mas se o usuário clicar no link novamente para adicionar um item que ele já adicionou, ele cria uma entrada duplicada na tabela dinâmica.

Existe uma maneira embutida de adicionar um registro a uma tabela dinâmica apenas se ainda não existir um?

Se não, como posso verificar a tabela dinâmica para descobrir se já existe um registro correspondente?

Al_
fonte

Respostas:

76

Você pode verificar a presença de um registro existente escrevendo uma condição muito simples como esta:

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

Ou / e você pode adicionar a condição de unicidade em seu banco de dados, isso lançaria uma exceção durante uma tentativa de salvar um dupleto.

Você também deve dar uma olhada na resposta mais direta de Barryvdh logo abaixo.

Alexandre Butynski
fonte
1
O parâmetro $ id para o método attach()é misturado, pode ser um int ou uma instância do modelo;) - consulte github.com/laravel/framework/blob/master/src/Illuminate/…
Rob Gordijn
Obrigado @RobGordijn. Aprendi algo hoje! Resposta editada.
Alexandre Butynski
1
A containsinstrução @bagusflyer verifica se a chave está presente em um objeto da coleção. Você deve ter um erro em seu código.
Alexandre Butynski
4
Eu não gosto dessa solução porque ela força uma consulta extra ($ cart-> items). Tenho feito algo como: $cart->items()->where('foreign_key', $foreignKey)->count() Que, bem, na verdade também executa uma consulta adicional '^^ Mas eu não preciso buscar e hidrate a coleção inteira, a menos que eu realmente precise.
Silêncio
1
Isso mesmo, sua solução está um pouco mais otimizada. Você pode até usar a exists()função em vez de count()para a melhor otimização.
Alexandre Butynski
264

Você também pode usar o $model->sync(array $ids, $detaching = true)método e desativar a desconexão (o segundo parâmetro).

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

Atualização: desde o Laravel 5.3 ou 5.2.44, você também pode chamar syncWithoutDetaching:

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

Que faz exatamente o mesmo, mas mais legível :)

Barryvdh
fonte
2
O segundo parâmetro booleano para evitar a desanexação de IDs não listados não está na documentação principal do L5. É muito bom saber - parece ser a única maneira de "anexar, se já não estiver anexado" em uma única instrução.
Jason,
11
Isso deve ser aceito como a resposta, é uma maneira muito melhor de fazer isso do que a resposta aceita
Daniel
4
Para iniciantes como eu: $cart->items()->sync([1, 2, 3])vai construir muitos-para-muitos relações com o id do na matriz dada, 1, 2, e 3, e delete (ou "desconexão") todos os outros id não é na matriz. Dessa forma, apenas os id's fornecidos no array existirão na tabela. Brilliant @Barryvdh usa um segundo parâmetro não documentado para desabilitar essa desconexão, portanto, nenhum relacionamento que não esteja no array fornecido é excluído, mas apenas ids exclusivos serão anexados. Verifique o documento "sincronização para conveniência". (Laravel 5.2)
identicon
3
Para sua informação, atualizei os documentos, portanto 5.2 e superior: laravel.com/docs/5.2/… E Taylor imediatamente adicionou add syncWithoutDetaching(), que chama o sync () com false como segundo parâmetro.
Barryvdh
Laravel 5.5 laravel.com/docs/5.5/… syncWithoutDetaching() funcionou!
Josh LaMar
2

O método @alexandre Butynsky funciona muito bem, mas usa duas consultas sql.

Um para verificar se o carrinho contém o item e outro para salvar.

Para usar apenas uma consulta, use:

try {
    $cart->items()->save($newItem);
}
catch(\Exception $e) {}
Octavian Ruda
fonte
Isso é o que fiz no meu projeto. Mas o problema é que você não sabe se essa exceção é causada pela entrada duplicada ou qualquer outra coisa.
Bagusflyer de
2
por que lançaria qualquer exceção? seria se houvesse alguma chave única
Seiji Manoan
1

Por melhores que sejam todas essas respostas porque eu tentei todas elas, uma coisa ainda ficou sem resposta ou não foi atendida: o problema de atualizar um valor previamente marcado (desmarcada a caixa marcada [es]). Eu tenho algo semelhante à pergunta acima, mas espero verificar e desmarcar os recursos dos produtos em minha tabela de recursos de produtos (a tabela dinâmica). Eu sou um novato e percebi que nenhuma das opções acima fazia isso. Ambos são bons para adicionar novos recursos, mas não quando eu quero remover recursos existentes (ou seja, desmarcá-lo)

Eu aprecio qualquer esclarecimento sobre isso.

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

Desculpe pessoal, não tenho certeza se devo deletar a pergunta porque tendo descoberto a resposta sozinho, parece um pouco estúpido, bem, a resposta acima é tão simples quanto trabalhar @Barryvdh sync () da seguinte maneira; tendo lido cada vez mais sobre:

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

Você pode apenas usar

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

A partir do Laravel 5.7:

Sincronizando associações Você também pode usar o método de sincronização para construir associações muitos-para-muitos. O método de sincronização aceita uma matriz de IDs para colocar na tabela intermediária. Todos os IDs que não estiverem na matriz fornecida serão removidos da tabela intermediária. Portanto, após a conclusão desta operação, apenas os IDs na matriz fornecida existirão na tabela intermediária:

Shreyansh Panchal
fonte
Esta é a melhor solução
eifersucht
Isso nem mesmo responde à questão, que era como adicionar uma única relação e evitar linhas duplicadas.
hackel de
A sincronização irá adicionar itens e IRÁ evitar a criação de linhas duplicadas.
Shreyansh Panchal
Para completar o comentário de @hackel, ele também removerá todos os outros itens do carrinho.
chrissharkman
0

Já existem ótimas respostas postadas. Eu queria jogar este aqui também.

As respostas de @AlexandreButynski e @Barryvdh são mais legíveis do que minha sugestão, o que essa resposta adiciona é alguma eficiência.

Ele recupera apenas as entradas para a combinação atual (na verdade, apenas o id) e, em seguida, anexa-o se não existir. O método de sincronização (mesmo sem desanexar) recupera todos os IDs atualmente anexados. Para conjuntos menores com pequenas iterações, isso dificilmente fará diferença, ... você entendeu.

De qualquer forma, definitivamente não é tão legível, mas funciona.

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