Como mesclar duas coleções do Eloquent?

86

Eu tenho uma tabela de perguntas e uma tabela de tags. Eu quero buscar todas as perguntas das tags de uma determinada pergunta. Assim, por exemplo, posso ter as tags "Viagem", "Trens" e "Cultura" anexadas a uma determinada pergunta. Quero poder buscar todas as perguntas para essas três tags. O complicado, ao que parece, é que o relacionamento de perguntas e tags é um muitos-para-muitos definido no Eloquent como belongsToMany.

Pensei em tentar mesclar as Coleções de perguntas conforme abaixo:

foreach ($question->tags as $tag) {
    if (!isset($related)) {
        $related = $tag->questions;
    } else {
        $related->merge($tag->questions);
    }
}

Mas não parece funcionar. Não parece fundir nada. Estou tentando fazer isso corretamente? Além disso, existe talvez uma maneira melhor de buscar uma linha de linhas em um relacionamento muitos para muitos no Eloquent?

Martyn
fonte
Você verificou a documentação sobre o carregamento antecipado e o método with? Seu problema poderia ser facilmente resolvido usando uma consulta mais eloquente. Assim que estiver atrás de um computador, escreverei um exemplo, a menos que alguém chegue antes de mim.
Luceos
1
@Luceos withnão vai ajudar. É whereHasisso que é necessário - como na resposta abaixo.
Jarek Tkaczyk
sim, erro meu; você está correto
Luceos

Respostas:

135

O método merge retorna a coleção mesclada, não modifica a coleção original, portanto, você precisa fazer o seguinte

$original = new Collection(['foo']);

$latest = new Collection(['bar']);

$merged = $original->merge($latest); // Contains foo and bar.

Aplicando o exemplo ao seu código

$related = new Collection();

foreach ($question->tags as $tag)
{
    $related = $related->merge($tag->questions);
}
Wader
fonte
1
Eu estava tentando construir uma lista plana de uma árvore, usando push era o que eu precisava, mas a abordagem foreach realmente ajudou.
George
Tenha em mente que as coleções do Eloquent não se comportam como coleções normais, ou seja. eles usam getKeya resultados de mesclagem, entãoModel::all()->merge(Model::all())->count() === Model::all()->count()
eithed
33

O merge()método no Collectionnão modifica a coleção na qual foi chamado. Ele retorna uma nova coleção com os novos dados mesclados. Você precisaria de:

$related = $related->merge($tag->questions);

No entanto, acho que você está lidando com o problema do ângulo errado.

Como você está procurando perguntas que atendem a determinados critérios, provavelmente seria mais fácil fazer uma consulta dessa maneira. Os métodos has()e whereHas()são usados ​​para gerar uma consulta com base na existência de um registro relacionado.

Se você estivesse apenas procurando perguntas que possuíssem qualquer tag, usaria o has()método. Como você está procurando perguntas com uma tag específica, use o whereHas()para adicionar a condição.

Portanto, se você quiser todas as perguntas que tenham pelo menos uma tag com 'Viagem', 'Trens' ou 'Cultura', sua consulta será parecida com:

$questions = Question::whereHas('tags', function($q) {
    $q->whereIn('name', ['Travel', 'Trains', 'Culture']);
})->get();

Se você quisesse todas as perguntas que tivessem todas essas três tags, sua consulta seria assim:

$questions = Question::whereHas('tags', function($q) {
    $q->where('name', 'Travel');
})->whereHas('tags', function($q) {
    $q->where('name', 'Trains');
})->whereHas('tags', function($q) {
    $q->where('name', 'Culture');
})->get();
patrício
fonte
1
+, no entanto, a segunda opção (todas as tags) que você sugeriu pode ser simplificada: stackoverflow.com/a/24706347/784588
Jarek Tkaczyk
mas você não pode codificar os nomes das tags. Neste exemplo, a pergunta passa a ter essas marcas, mas em outras questões as marcas variam
Allfarid Morales García
24
$users = User::all();
$associates = Associate::all();

$userAndAssociate = $users->merge($associates);
sh6210
fonte
6
Leia isto (sobrescrevendo): medium.com/@tadaspaplauskas/…
Jeffz
1
@Jeffz é realmente inacreditável que mescle "duplicatas" com base apenas na ID
andrewtweber
11

Junte duas coleções eloquentes diferentes em uma e alguns objetos terão o mesmo id, um substituirá o outro. Use o método push () ou repense sua abordagem para o problema para evitar isso. Consulte a web

newbie2005
fonte
Obrigado, isso me colocou no comentário encontrado aqui medium.com/@jeffparr_57441/… que parece fazer o trabalho de forma limpa, sem sobrescrever.
Marcos
1

Nem todas funcionam para mim em coleções eloquentes , coleções eloquentes laravel usam a chave dos itens que eu acho que causa problemas de mesclagem, você precisa obter a primeira coleção de volta como uma matriz, colocá-la em uma nova coleção e, em seguida, empurrar as outras para a nova coleção;

public function getFixturesAttribute()
{
    $fixtures = collect( $this->homeFixtures->all() );
    $this->awayFixtures->each( function( $fixture ) use ( $fixtures ) {
        $fixtures->push( $fixture );
    });
    return $fixtures;
}
Luke Snowden
fonte
0

Criando uma nova coleção base para cada coleção eloquente que a fusão funciona para mim.

$foo = collect(Foo::all());
$bar = collect(Bar::all());
$merged = $foo->merge($bar);

Nesse caso, não há conflitos por suas chaves primárias.

João carlos júnior
fonte