Obtener objeto de la colección por atributo

6 minutos de lectura

Avatar de usuario de Leng
largo

En Laravel, si realizo una consulta:

$foods = Food::where(...)->get();

…después $foods es un Colección iluminar de Food modelar objetos. (Esencialmente una variedad de modelos).

Sin embargo, las claves de esta matriz son simplemente:

[0, 1, 2, 3, ...]

…así que si quiero alterar, digamos, el Food objeto con un id de 24, no puedo hacer esto:

$desired_object = $foods->get(24);
$desired_object->color="Green";
$desired_object->save();

… porque esto simplemente alterará el elemento 25 en la matriz, no el elemento con un id de 24

¿Cómo obtengo uno o varios elementos de una colección por CUALQUIER atributo/columna (como, entre otros, id/color/edad/etc.)?

Por supuesto, puedo hacer esto:

foreach ($foods as $food) {
    if ($food->id == 24) {
        $desired_object = $food;
        break;
    }
}
$desired_object->color="Green";
$desired_object->save();

…pero, eso es asqueroso.

Y, por supuesto, puedo hacer esto:

$desired_object = Food::find(24);
$desired_object->color="Green";
$desired_object->save();

…pero eso es aún más asquerosoporque realiza una consulta adicional innecesaria cuando ya tengo el objeto deseado en el $foods recopilación.

EDITAR:

Para ser claro, usted lata llamada ->find() en una Colección Illuminate sin generar otra consulta, pero solamente acepta una identificación principal. Por ejemplo:

$foods = Food::all();
$desired_food = $foods->find(21);  // Grab the food with an ID of 21

Sin embargo, todavía no hay una forma limpia (sin bucles, sin consultas) de capturar un elemento (s) por un atributo de una colección, como esta:

$foods = Food::all();
$green_foods = $foods->where('color', 'green'); // This won't work.  :(

avatar de usuario de kalley
calle

Puedes usar filteral igual que:

$desired_object = $food->filter(function($item) {
    return $item->id == 24;
})->first();

filter también devolverá un Collectionpero como sabes que solo habrá uno, puedes llamar first en ese Collection.

Ya no necesitas el filtro (o tal vez nunca, no sé esto tiene casi 4 años). solo puedes usar first:

$desired_object = $food->first(function($item) {
    return $item->id == 24;
});

  • ¡Hey gracias! Creo que puedo vivir con eso. Todavía inusualmente detallado en mi opinión para lo que suele ser un marco tan ‘Elocuente’ jaja. Pero sigue siendo mucho más limpio que las alternativas hasta ahora, así que lo tomaré.

    – Longitud

    5 de enero de 2014 a las 7:09


  • Como @squaretastic señala en la otra respuesta, dentro de su cierre está haciendo una asignación y no una comparación (es decir, debe == y no =)

    – Tormenta Elemental

    9 de junio de 2014 a las 12:57

  • En realidad ni siquiera es necesario llamar filter()->first() solo puedes llamar first(function(...))

    – lukasgeiter

    04/02/2015 a las 20:10

  • de la documentación de la Colección Laravel. laravel.com/docs/5.5/colecciones#método-primero collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });

    – Shiro

    12 dic 2017 a las 15:25

  • Puedes hacer lo mismo con la función where. $desired_object = $food->where('id', 24)->first();

    – Bhavin Thummar

    28 de agosto de 2018 a las 11:02

Avatar de usuario de Maksym
Maksim

Laravel proporciona un método llamado keyBy que permite establecer claves por clave dada en el modelo.

$collection = $collection->keyBy('id');

devolverá la colección pero con claves siendo los valores de id atributo de cualquier modelo.

Entonces puedes decir:

$desired_food = $foods->get(21); // Grab the food with an ID of 21

y tomará el elemento correcto sin el lío de usar una función de filtro.

  • Realmente útil, especialmente para el rendimiento, ->first() puede ser lento cuando se llama varias veces (foreach en foreach…) para que pueda “indexar” su colección como: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id; y use ->get($category->id . ' ' . $manufacturer->id) después !

    – François Bretón

    25 de abril de 2016 a las 16:32


  • ¿Se sigue utilizando esta clave cuando se añaden nuevos elementos a la colección? ¿O necesito usar keyBy() cada vez que se inserta un nuevo objeto o matriz en la colección?

    – Jasón

    22 de septiembre de 2016 a las 14:22

  • Lo más probable es que tengas que llamarlo de nuevo ya que keyBy devuelve una nueva colección de lo que recuerdo, aunque no estoy seguro, puedes verificar Illuminate/Support/Collection para averiguarlo (No estoy trabajando en Laravel desde hace bastante tiempo, así que alguien puede corregirme).

    – Maksim

    04/12/2016 a las 20:44

  • Esto no funcionó para mí, devolvió otro elemento, el siguiente elemento, si escribo obtener (1) devolverá el elemento que tiene el número 2 como identificación.

    – Jaqueline Passos

    21 de enero de 2017 a las 16:42


  • Lote cargando una mesa y tardó un día. Usé esta solución y tomó minutos.

    –Jed Lynch

    27 de abril de 2019 a las 4:29

A partir de Laravel 5.5 puedes usar primeroDónde()

En tu caso:

$green_foods = $foods->firstWhere('color', 'green');

  • Esta debería ser la respuesta aceptada después de Laravel 5.5

    – Beerwin

    13 de marzo de 2019 a las 16:13

Utilice los métodos de recopilación incorporados Contiene y encontrar, que buscará por identificadores primarios (en lugar de claves de matriz). Ejemplo:

if ($model->collection->contains($primaryId)) {
    var_dump($model->collection->find($primaryId);
}

contiene () en realidad solo llama a find () y verifica si es nulo, por lo que podría acortarlo a:

if ($myModel = $model->collection->find($primaryId)) {
    var_dump($myModel);
}

Como no necesito recorrer toda la colección, creo que es mejor tener una función auxiliar como esta

/**
 * Check if there is a item in a collection by given key and value
 * @param Illuminate\Support\Collection $collection collection in which search is to be made
 * @param string $key name of key to be checked
 * @param string $value value of key to be checkied
 * @return boolean|object false if not found, object if it is found
 */
function findInCollection(Illuminate\Support\Collection $collection, $key, $value) {
    foreach ($collection as $item) {
        if (isset($item->$key) && $item->$key == $value) {
            return $item;
        }
    }
    return FALSE;
}

avatar de usuario de patricus
patricio

Sé que esta pregunta se hizo originalmente antes de que se lanzara Laravel 5.0, pero a partir de Laravel 5.0, las colecciones admiten el where() método para este propósito.

Para Laravel 5.0, 5.1 y 5.2, el where() método en el Collection solo hará una comparación de iguales. Además, hace una comparación estricta de igualdad (===) por defecto. Para hacer una comparación suelta (==), puede pasar false como el tercer parámetro o utilizar el whereLoose() método.

A partir de Laravel 5.3, el where() método se amplió para trabajar más como el where() para el generador de consultas, que acepta un operador como segundo parámetro. También al igual que el generador de consultas, el operador utilizará de forma predeterminada una comparación igual si no se proporciona ninguna. La comparación predeterminada también se cambió de estricta por defecto a suelta por defecto. Entonces, si desea una comparación estricta, puede usar whereStrict()o simplemente usar === como operador de where().

Por lo tanto, a partir de Laravel 5.0, el último ejemplo de código en la pregunta funcionará exactamente como se esperaba:

$foods = Food::all();
$green_foods = $foods->where('color', 'green'); // This will work.  :)

// This will only work in Laravel 5.3+
$cheap_foods = $foods->where('price', '<', 5);

// Assuming "quantity" is an integer...
// This will not match any records in 5.0, 5.1, 5.2 due to the default strict comparison.
// This will match records just fine in 5.3+ due to the default loose comparison.
$dozen_foods = $foods->where('quantity', '12');

avatar de usuario de softfrog
rana blanda

Solución elegante para encontrar un valor (http://betamode.de/2013/10/17/laravel-4-eloquent-check-if-there-is-a-model-with-certain-key-value-pair-in-a-collection/) se puede adaptar:

$desired_object_key = $food->array_search(24, $food->lists('id'));
if ($desired_object_key !== false) {
   $desired_object = $food[$desired_object_key];
}

¿Ha sido útil esta solución?