Laravel: obtener objeto de la colección por atributo

7 minutos de lectura

avatar de usuario
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.

Gracias de antemano por cualquier orientación.

EDITAR:

Para ser claro, usted pueden llamar ->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
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é.

    – Largo

    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
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 integrados contener 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
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
cuadradotástico

Debo señalar que hay un pequeño pero absolutamente CRÍTICO error en la respuesta de Kalley. Luché con esto durante varias horas antes de darme cuenta:

Dentro de la función, lo que está devolviendo es una comparación y, por lo tanto, algo como esto sería más correcto:

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

  • Sí, gracias por señalar esto. También es importante tener en cuenta que la función de filtro no es diferente de mi foreach() ejemplo en cuanto al rendimiento, porque simplemente hace el mismo tipo de bucle… de hecho, mi foreach() El ejemplo tiene un mejor rendimiento porque se rompe al encontrar el modelo correcto. También… {Collection}->find(24) tomará por clave principal, lo que la convierte en la mejor opción aquí. El filtro propuesto por Kalley es en realidad idéntico al $desired_object = $foods->find(24);.

    – Largo

    16 de mayo de 2014 a las 4:38


  • Nunca he visto el **==** operador, ¿qué hace?

    – Kiradotee

    1 de junio de 2017 a las 12:42

  • @kiradotee Creo que el OP solo intentaba enfatizar el operador de comparación doble igual (==). La respuesta original solo usaba un signo igual, por lo que estaba haciendo una asignación en lugar de una comparación. OP estaba tratando de enfatizar que debería haber dos signos iguales.

    – patricio

    3 de junio de 2017 a las 2:01


  • Comparta más detalles: ¿qué es ese “error crítico”, por qué es crítico y cómo lo resolvió?

    –Nico Haase

    17 de febrero de 2021 a las 8:15

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad