¿Cómo fusionar dos colecciones elocuentes?

86

Tengo una tabla de preguntas y una tabla de etiquetas. Quiero obtener todas las preguntas de las etiquetas de una pregunta determinada. Entonces, por ejemplo, puedo tener las etiquetas "Viajes", "Trenes" y "Cultura" adjuntas a una pregunta determinada. Quiero poder obtener todas las preguntas de esas tres etiquetas. Lo complicado, al parecer, es que la relación de preguntas y etiquetas es de muchos a muchos definida en Eloquent como pertenece a muchos.

Pensé en intentar fusionar las colecciones de preguntas de la siguiente manera:

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

Sin embargo, no parece funcionar. No parece fusionar nada. ¿Estoy intentando esto correctamente? Además, ¿hay quizás una mejor manera de obtener una fila de filas en una relación de muchos a muchos en Eloquent?

Martyn
fuente
¿Revisó la documentación sobre la carga ansiosa y el método with? Su problema podría resolverse fácilmente mediante una consulta más elocuente. Una vez que me ponga detrás de una computadora, escribiré un ejemplo a menos que alguien se me adelante.
Luceos
1
@Luceos withno ayudará. Es lo whereHasque se necesita, como en la respuesta a continuación.
Jarek Tkaczyk
sí, mi error; tienes razón
Luceos

Respuestas:

135

El método merge devuelve la colección fusionada, no muta la colección original, por lo que debe hacer lo siguiente

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

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

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

Aplicando el ejemplo a su código

$related = new Collection();

foreach ($question->tags as $tag)
{
    $related = $related->merge($tag->questions);
}
Ave zancuda
fuente
1
Estaba tratando de construir una lista plana a partir de un árbol, usar push era lo que necesitaba, pero el enfoque foreach realmente me ayudó.
George
Tenga en cuenta que las colecciones de Eloquent no se comportan como las colecciones normales, es decir. que utilizan getKeya resultados de la combinación, por lo queModel::all()->merge(Model::all())->count() === Model::all()->count()
eithed
33

El merge()método en Collectionno modifica la colección en la que fue llamado. Devuelve una nueva colección con los nuevos datos combinados. Necesitaría:

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

Sin embargo, creo que está abordando el problema desde el ángulo equivocado.

Dado que está buscando preguntas que cumplan con ciertos criterios, probablemente sería más fácil realizar consultas de esa manera. Los métodos has()y whereHas()se utilizan para generar una consulta basada en la existencia de un registro relacionado.

Si solo estuviera buscando preguntas que tengan alguna etiqueta, usaría el has()método. Dado que está buscando preguntas con una etiqueta específica, usaría whereHas()para agregar la condición.

Por lo tanto, si desea todas las preguntas que tienen al menos una etiqueta con 'Viajes', 'Trenes' o 'Cultura', su consulta se vería así:

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

Si quisiera todas las preguntas que tuvieran esas tres etiquetas, su consulta se vería así:

$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();
patricus
fuente
1
+, sin embargo, la segunda opción (todas las etiquetas) que sugirió podría simplificarse: stackoverflow.com/a/24706347/784588
Jarek Tkaczyk
pero no puede codificar los nombres de las etiquetas. En este ejemplo, la pregunta tiene esas etiquetas, pero en otras preguntas las etiquetas variarán
Allfarid Morales García
24
$users = User::all();
$associates = Associate::all();

$userAndAssociate = $users->merge($associates);
sh6210
fuente
6
Lea esto (sobrescribiendo): medium.com/@tadaspaplauskas/…
Jeffz
1
@Jeffz es realmente increíble que combine "duplicados" basándose solo en la identificación
andrewtweber
11

Combina dos colecciones elocuentes diferentes en una y algunos objetos tienen la misma identificación, una sobrescribirá a la otra. En su lugar, utilice el método push () o reconsidere su enfoque del problema para evitarlo. Consulte la web

novato2005
fuente
Gracias, esto me puso en el comentario que se encuentra aquí medium.com/@jeffparr_57441/… que parece hacer el trabajo limpiamente sin sobrescribir.
Mark
1

No todos funcionan para mí en colecciones elocuentes , las colecciones elocuentes de laravel usan la clave de los elementos que creo que causan problemas de fusión, necesita recuperar la primera colección como una matriz, ponerla en una colección nueva y luego empujar las demás a la nueva colección;

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

Al crear una nueva colección base para cada colección elocuente, la combinación funciona para mí.

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

En este caso no tiene conflits por sus claves primarias.

João Carlos Junior
fuente