Laravel: obtenir un objet de la collection par attribut

91

Dans Laravel, si j'effectue une requête:

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

... $foodsest alors une collection Illuminate d' Foodobjets modèles. (Essentiellement un tableau de modèles.)

Cependant, les clés de ce tableau sont simplement:

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

... donc si je veux modifier, par exemple, l' Foodobjet avec un idde 24, je ne peux pas faire ceci:

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

... car cela modifiera simplement le 25e élément du tableau, pas l'élément avec un idde 24.

Comment obtenir un (des) élément (s) unique (ou plusieurs) d'une collection par TOUT attribut / colonne (tel que, mais sans s'y limiter, id / couleur / âge / etc.)?

Bien sûr, je peux faire ceci:

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

... mais c'est juste dégoûtant.

Et, bien sûr, je peux faire ceci:

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

... mais c'est encore plus grossier , car il effectue une requête supplémentaire inutile lorsque j'ai déjà l'objet souhaité dans la $foodscollection.

Merci d'avance pour tous conseils.

ÉDITER:

Pour être clair, vous pouvez appeler ->find()sur une collection Illuminate sans fraie une autre requête, mais il n'accepte une pièce d' identité primaire. Par exemple:

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

Cependant, il n'y a toujours pas de moyen propre (sans boucle, sans requête) de récupérer un ou plusieurs éléments par un attribut d'une collection, comme ceci:

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

Réponses:

126

Vous pouvez utiliser filter, comme ceci:

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

filterrenverra également un Collection, mais comme vous savez qu'il n'y en aura qu'un, vous pouvez appeler firstcelui-ci Collection.

Vous n'avez plus besoin du filtre (ou peut-être jamais, je ne sais pas que c'est presque 4 ans). Vous pouvez simplement utiliser first:

$desired_object = $food->first(function($item) {
    return $item->id == 24;
});
Kalley
la source
7
Hey, merci! Je pense que je peux vivre avec ça. Encore inhabituellement verbeux à mon avis pour ce qui est généralement un tel cadre «éloquent» haha. Mais c'est encore beaucoup plus propre que les alternatives jusqu'à présent, alors je vais le prendre.
Leng
Comme @squaretastic le souligne dans l'autre réponse, dans votre clôture, vous faites une affectation et non une comparaison (c'est-à-dire que vous devriez == et non =)
ElementalStorm
24
En fait, il n'est même pas nécessaire d'appeler, filter()->first()vous pouvez simplement appelerfirst(function(...))
lukasgeiter
à partir de la documentation de la collection Laravel. laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
Shiro
2
Vous pouvez faire la même chose avec where function. $desired_object = $food->where('id', 24)->first();
Bhavin Thummar
111

Laravel fournit une méthode appelée keyByqui permet de définir des clés par clé donnée dans le modèle.

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

retournera la collection mais avec les clés étant les valeurs d' idattribut de n'importe quel modèle.

Ensuite, vous pouvez dire:

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

et il récupérera l'élément correct sans le désordre d'utiliser une fonction de filtre.

Maksym Cierzniak
la source
2
Vraiment utile, surtout pour les performances, -> first () peut être lent lorsqu'il est appelé plusieurs fois (foreach dans foreach ...) donc vous pouvez "indexer" votre collection comme: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;et utiliser ->get($category->id . ' ' . $manufacturer->id)after!
François Breton
Cette clé continue-t-elle d'être utilisée lorsque de nouveaux éléments sont ajoutés à la collection? Ou dois-je utiliser keyBy () chaque fois qu'un nouvel objet ou tableau est poussé sur la collection?
Jason le
Vous devrez probablement le rappeler car il keyByrenvoie une nouvelle collection de ce dont je me souviens, pas sûr cependant, vous pouvez vérifier Illuminate/Support/Collectionpour le savoir. (Ne travaille pas à Laravel depuis un certain temps donc quelqu'un peut me corriger).
Maksym Cierzniak
Cela n'a pas fonctionné pour moi, il a renvoyé un autre élément, l'élément suivant, si je tape get (1), il retournera l'élément qui a le numéro 2 comme id.
Jaqueline Passos
Lot de chargement d'une table et cela a pris une journée. Utilisé cette solution et cela a pris quelques minutes.
Jed Lynch
23

À partir de Laravel 5.5, vous pouvez utiliser firstWhere ()

Dans votre cas:

$green_foods = $foods->firstWhere('color', 'green');
Victor Timoftii
la source
3
Cela devrait être la réponse acceptée après Laravel 5.5
beerwin
7

Comme je n'ai pas besoin de boucler toute la collection, je pense qu'il est préférable d'avoir une fonction d'aide comme celle-ci

/**
 * 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;
}
Rohith Raveendran
la source
7

Utilisez les méthodes de collection intégrées contain et find , qui effectueront une recherche par ID primaire (au lieu de clés de tableau). Exemple:

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

contains () appelle en fait find () et vérifie la valeur null, vous pouvez donc le raccourcir à:

if ($myModel = $model->collection->find($primaryId)) {
    var_dump($myModel);
}
Ziad Hilal
la source
Nous comprenons que find () accepte un identifiant principal. Ce que nous voulons, c'est une méthode qui accepte n'importe quel attribut, tel que «couleur» ou «âge». Jusqu'à présent, la méthode de Kalley est la seule qui fonctionne pour n'importe quel attribut.
Leng
5

Je sais que cette question a été posée à l'origine avant la sortie de Laravel 5.0, mais à partir de Laravel 5.0, les collections prennent en charge la where()méthode à cet effet.

Pour Laravel 5.0, 5.1 et 5.2, la where()méthode du Collectionne fera qu'une comparaison égale. En outre, il effectue une comparaison stricte égale ( ===) par défaut. Pour faire une comparaison lâche ( ==), vous pouvez passer falsecomme troisième paramètre ou utiliser la whereLoose()méthode.

À partir de Laravel 5.3, la where()méthode a été étendue pour fonctionner davantage comme la where()méthode du générateur de requêtes, qui accepte un opérateur comme deuxième paramètre. De même que le générateur de requêtes, l'opérateur utilisera par défaut une comparaison égale si aucune n'est fournie. La comparaison par défaut est également passée de strict par défaut à loose par défaut. Donc, si vous souhaitez une comparaison stricte, vous pouvez utiliser whereStrict()ou simplement utiliser ===comme opérateur pour where().

Par conséquent, à partir de Laravel 5.0, le dernier exemple de code de la question fonctionnera exactement comme prévu:

$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');
patricus
la source
3

Je dois souligner qu'il y a une petite mais absolument CRITIQUE erreur dans la réponse de Kalley. J'ai lutté avec cela pendant plusieurs heures avant de réaliser:

À l'intérieur de la fonction, ce que vous retournez est une comparaison, et donc quelque chose comme ça serait plus correct:

$desired_object = $food->filter(function($item) {
    return ($item->id **==** 24);
})->first();
squarétastique
la source
1
Oui, merci de l'avoir signalé. Il est également important de noter que la fonction de filtre n'est pas différente de maforeach() exemple en termes de performances, car elle fait simplement le même type de boucle ... en fait, mon foreach()exemple est plus performant car il se rompt lors de la recherche du bon modèle. Aussi ... {Collection}->find(24)saisira par clé primaire, ce qui en fait la meilleure option ici. Le filtre proposé par Kalley est en fait identique à $desired_object = $foods->find(24);.
Leng
1
Jamais vu le **==** opérateur, que fait-il?
kiradotee
@kiradotee Je pense que l'OP essayait juste de mettre l'accent sur l'opérateur de comparaison double égal ( == ). La réponse originale n'utilisait qu'un seul signe égal, donc il s'agissait d'une tâche au lieu d'une comparaison. OP essayait de souligner qu'il devrait y avoir deux signes égaux.
patricus
0

Comme la question ci-dessus lorsque vous utilisez la clause where, vous devez également utiliser la méthode get Or first pour obtenir le résultat.

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

$green_foods = Food::where('color', 'green')->get();
Marco
la source