Laravel: Objekt aus der Sammlung nach Attribut abrufen

87

Wenn ich in Laravel eine Abfrage durchführe:

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

... dann $foodsist eine Illuminate Collection von FoodModellobjekten. (Im Wesentlichen eine Reihe von Modellen.)

Die Schlüssel dieses Arrays sind jedoch einfach:

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

... Wenn ich also beispielsweise das FoodObjekt mit einer idvon 24 ändern möchte , kann ich dies nicht tun:

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

... weil dies lediglich das 25. Element im Array ändert, nicht das Element mit einem idvon 24.

Wie erhalte ich ein einzelnes (oder mehrere) Element (e) aus einer Sammlung nach JEDEM Attribut / jeder Spalte (z. B. ID / Farbe / Alter / usw.), ohne darauf beschränkt zu sein?

Natürlich kann ich das tun:

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

... aber das ist nur eklig.

Und natürlich kann ich das tun:

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

... aber das ist noch schlimmer , weil es eine zusätzliche unnötige Abfrage ausführt, wenn ich bereits das gewünschte Objekt in der $foodsSammlung habe.

Vielen Dank im Voraus für jede Anleitung.

BEARBEITEN:

Um klar zu sein, können Sie eine Illuminate Collection aufrufen, ohne eine weitere Abfrage zu erzeugen->find() , aber es wird nur eine primäre ID akzeptiert. Zum Beispiel:

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

Es gibt jedoch immer noch keine saubere (nicht schleifenförmige, nicht abfragende) Möglichkeit, ein oder mehrere Elemente anhand eines Attributs aus einer Sammlung wie folgt zu erfassen:

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

Antworten:

122

Sie können filterwie folgt verwenden:

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

filterwird auch eine Rückkehr Collection, aber da Sie es wissen , nur eine sein wird, können Sie rufen Sie firstauf , dass Collection.

Sie brauchen den Filter nicht mehr (oder vielleicht weiß ich nie, dass er fast 4 Jahre alt ist). Sie können einfach verwenden first:

$desired_object = $food->first(function($item) {
    return $item->id == 24;
});
Kalley
quelle
7
Hey danke! Ich denke ich kann damit leben. Meiner Meinung nach immer noch ungewöhnlich ausführlich für das, was normalerweise so ein "eloquentes" Framework ist, haha. Aber es ist immer noch viel sauberer als die bisherigen Alternativen, also nehme ich es.
Leng
Wie @squaretastic in der anderen Antwort hervorhebt, machen Sie in Ihrem Abschluss eine Zuordnung und keinen Vergleich (dh Sie sollten == und nicht =)
ElementalStorm
24
Eigentlich ist es nicht einmal nötig anzurufen, filter()->first()man kann einfach anrufenfirst(function(...))
lukasgeiter
aus der Dokumentation der Laravel Collection. laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
Shiro
2
Sie können dasselbe mit der where-Funktion tun. $desired_object = $food->where('id', 24)->first();
Bhavin Thummar
109

Laravel bietet eine Methode namens keyBy, mit der Schlüssel anhand eines bestimmten Schlüssels im Modell festgelegt werden können.

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

gibt die Sammlung zurück, wobei Schlüssel die Werte des idAttributs eines beliebigen Modells sind.

Dann können Sie sagen:

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

und es wird das richtige Element erfassen, ohne dass eine Filterfunktion verwendet werden muss.

Maksym Cierzniak
quelle
2
Sehr nützlich, insbesondere für die Leistung. -> first () kann bei mehrmaligem Aufruf langsam sein (foreach in foreach ...), sodass Sie Ihre Sammlung wie folgt "indizieren" können: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;und ->get($category->id . ' ' . $manufacturer->id)danach verwenden können!
François Breton
Wird dieser Schlüssel weiterhin verwendet, wenn der Sammlung neue Elemente hinzugefügt werden? Oder muss ich keyBy () jedes Mal verwenden, wenn ein neues Objekt oder Array in die Sammlung verschoben wird?
Jason
Höchstwahrscheinlich müssen Sie es erneut aufrufen, da eine keyByneue Sammlung von dem zurückgegeben wird, woran ich mich erinnere. Sie sind sich jedoch nicht sicher, ob Sie dies überprüfen können Illuminate/Support/Collection. (Ich arbeite seit einiger Zeit nicht mehr in Laravel, damit mich jemand korrigieren kann.)
Maksym Cierzniak
Dies hat bei mir nicht funktioniert, es wurde ein anderes Element zurückgegeben, das nächste Element. Wenn ich get (1) eingebe, wird das Element mit der Nummer 2 als ID zurückgegeben.
Jaqueline Passos
Batch Laden eines Tisches und es dauerte einen Tag. Verwendete diese Lösung und es dauerte Minuten.
Jed Lynch
22

Ab Laravel 5.5 können Sie firstWhere () verwenden.

In Ihrem Fall:

$green_foods = $foods->firstWhere('color', 'green');
Victor Timoftii
quelle
3
Dies sollte die akzeptierte Antwort nach Laravel 5.5
Beerwin
7

Da ich nicht die gesamte Sammlung durchlaufen muss, ist es meiner Meinung nach besser, eine solche Hilfsfunktion zu haben

/**
 * 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
quelle
7

Verwenden Sie die integrierten Auflistungsmethoden enthalten und suchen , die nach primären IDs (anstelle von Array-Schlüsseln) suchen. Beispiel:

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

enthält () ruft eigentlich nur find () auf und sucht nach null, so dass Sie es auf Folgendes verkürzen können:

if ($myModel = $model->collection->find($primaryId)) {
    var_dump($myModel);
}
Ziad Hilal
quelle
Wir verstehen, dass find () eine primäre ID akzeptiert. Was wir wollen, ist eine Methode, die alle Attribute wie "Farbe" oder "Alter" akzeptiert . Bisher ist die Kalley-Methode die einzige, die für ein Attribut funktioniert.
Leng
5

Ich weiß, dass diese Frage ursprünglich gestellt wurde, bevor Laravel 5.0 veröffentlicht wurde, aber ab Laravel 5.0 unterstützen Sammlungen die where()Methode für diesen Zweck.

Für Laravel 5.0, 5.1 und 5.2 führt die where()Methode auf dem Collectionnur einen Vergleich durch. Außerdem ===wird standardmäßig ein strikter Vergleich ( ) durchgeführt. Um einen losen Vergleich ( ==) durchzuführen, können Sie entweder falseals dritten Parameter übergeben oder die whereLoose()Methode verwenden.

Ab Laravel 5.3 wurde die where()Methode so erweitert, dass sie eher der where()Methode für den Abfrage-Generator ähnelt, der einen Operator als zweiten Parameter akzeptiert. Ebenso wie der Abfrage-Generator verwendet der Operator standardmäßig einen Gleichheitsvergleich, wenn keiner angegeben wird. Der Standardvergleich wurde ebenfalls standardmäßig von streng auf lose umgestellt. Wenn Sie also einen strengen Vergleich wünschen, können Sie whereStrict()oder nur ===als Operator für verwenden where().

Ab Laravel 5.0 funktioniert das letzte Codebeispiel in der Frage daher genau wie beabsichtigt:

$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
quelle
3

Ich muss darauf hinweisen, dass Kalleys Antwort einen kleinen, aber absolut kritischen Fehler enthält. Ich hatte mehrere Stunden damit zu kämpfen, bevor mir klar wurde:

Innerhalb der Funktion geben Sie einen Vergleich zurück, und daher wäre so etwas korrekter:

$desired_object = $food->filter(function($item) {
    return ($item->id **==** 24);
})->first();
squaretastic
quelle
1
Ja, danke, dass Sie darauf hingewiesen haben. Es ist auch wichtig zu beachten, dass sich die Filterfunktion in Bezug auf die foreach()Leistung nicht von meinem Beispiel unterscheidet , da sie nur die gleiche Art von Schleife foreach()ausführt. Tatsächlich ist mein Beispiel leistungsfähiger, da es beim Finden des richtigen Modells unterbrochen wird. Auch ... {Collection}->find(24)wird nach Primärschlüssel greifen, was es hier zur besten Option macht. Der vorgeschlagene Filter Kalley ist tatsächlich identisch mit $desired_object = $foods->find(24);.
Leng
1
Ich habe den **==**Bediener noch nie gesehen . Was macht er?
Kiradotee
@ kiradotee Ich denke, das OP hat nur versucht, den doppelt gleichen Vergleichsoperator ( == ) hervorzuheben . In der ursprünglichen Antwort wurde nur ein Gleichheitszeichen verwendet, sodass anstelle eines Vergleichs eine Zuordnung vorgenommen wurde. OP versuchte zu betonen, dass es zwei Gleichheitszeichen geben sollte.
Patrickus
1

Als obige Frage, wenn Sie die where-Klausel verwenden, müssen Sie auch die Methode get Or first verwenden, um das Ergebnis zu erhalten.

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

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