Laravel: pobierz obiekt z kolekcji według atrybutu

91

W Laravel, jeśli wykonam zapytanie:

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

... wtedy $foodsjest Illuminate Collection of Foodmodelowych obiektów. (Zasadniczo szereg modeli.)

Jednak klucze tej tablicy to po prostu:

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

... więc jeśli chcę zmienić, powiedzmy, Foodobiekt z idwartością 24, nie mogę tego zrobić:

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

... ponieważ to tylko zmieni 25-ty element tablicy, a nie element z idwartością 24.

Jak uzyskać pojedynczy (lub wiele) element (y) z kolekcji według DOWOLNEGO atrybutu / kolumny (takiego jak, ale nie wyłącznie, identyfikator / kolor / wiek / itp.)?

Oczywiście mogę to zrobić:

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

... ale to po prostu obrzydliwe.

I oczywiście mogę to zrobić:

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

... ale to jeszcze bardziej obrzydliwe , ponieważ wykonuje dodatkowe niepotrzebne zapytanie, gdy mam już żądany obiekt w $foodskolekcji.

Z góry dziękuję za wszelkie wskazówki.

EDYTOWAĆ:

Żeby było jasne, to może zadzwonić ->find()na Kolekcji Illuminate bez tarła kolejne zapytanie, ale tylko akceptuje podstawowy identyfikator. Na przykład:

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

Jednak nadal nie ma czystego (nie zapętlającego się i nie wykonującego zapytań) sposobu na pobranie elementu (ów) przez atrybut z kolekcji, na przykład:

$foods = Food::all();
$green_foods = $foods->where('color', 'green'); // This won't work.  :(
Leng
źródło

Odpowiedzi:

126

Możesz użyć na filterprzykład:

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

filterbędzie również zwróci Collection, ale skoro wiesz, że będzie tylko jeden, można wywołać firstna tym Collection.

Nie potrzebujesz już filtra (a może nigdy, nie wiem, że ma prawie 4 lata). Możesz po prostu użyć first:

$desired_object = $food->first(function($item) {
    return $item->id == 24;
});
Kalley
źródło
7
Hej dzięki! Myślę, że mogę z tym żyć. Wciąż niezwykle rozwlekłe moim zdaniem jak na to, co zwykle jest takim „elokwentnym” frameworkiem haha. Ale nadal jest znacznie czystszy niż dotychczasowe alternatywy, więc wezmę to.
Leng
Jak @squaretastic wskazuje w drugiej odpowiedzi, w zamknięciu wykonujesz przypisanie, a nie porównanie (tj. Powinieneś == a nie =)
ElementalStorm
24
Właściwie nie trzeba nawet dzwonić filter()->first(), możesz po prostu zadzwonićfirst(function(...))
lukasgeiter
z dokumentacji Laravel Collection. laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
Shiro
2
Możesz zrobić to samo z funkcją Where. $desired_object = $food->where('id', 24)->first();
Bhavin Thummar
111

Laravel udostępnia metodę o nazwie, keyByktóra pozwala ustawić klucze według danego klucza w modelu.

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

zwróci kolekcję, ale z kluczami będącymi wartościami idatrybutu z dowolnego modelu.

Następnie możesz powiedzieć:

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

i złapie właściwy przedmiot bez bałaganu związanego z używaniem funkcji filtra.

Maksym Cierzniak
źródło
2
Naprawdę przydatne, szczególnie ze względu na wydajność, -> first () może być powolne, gdy zostanie wywołane wiele razy (foreach in foreach ...), więc możesz "indeksować" swoją kolekcję na przykład: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;i używać ->get($category->id . ' ' . $manufacturer->id)after!
François Breton,
Czy ten klucz będzie nadal używany po dodaniu nowych elementów do kolekcji? Czy też muszę używać keyBy () za każdym razem, gdy nowy obiekt lub tablica jest wypychany do kolekcji?
Jason,
Najprawdopodobniej będziesz musiał zadzwonić ponownie, ponieważ keyByzwraca nową kolekcję z tego, co pamiętam, chociaż nie jestem pewien, możesz sprawdzić, Illuminate/Support/Collectionaby się dowiedzieć. (Nie pracuję w Laravel od dłuższego czasu, więc ktoś może mnie poprawić).
Maksym Cierzniak
To nie zadziałało, zwróciło kolejną pozycję, następną pozycję, jeśli wpiszę get (1), zwróci element, który ma numer 2 jako id.
Jaqueline Passos
Ładowanie partii do stołu i zajęło to dzień. Użyłem tego rozwiązania i zajęło to kilka minut.
Jed Lynch
23

Od Laravel 5.5 możesz użyć firstWhere ()

W twoim przypadku:

$green_foods = $foods->firstWhere('color', 'green');
Victor Timoftii
źródło
3
To powinna być akceptowana odpowiedź po Laravel 5.5
beerwin
7

Ponieważ nie muszę zapętlać całej kolekcji, myślę, że lepiej jest mieć taką funkcję pomocniczą

/**
 * 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
źródło
7

Użyj wbudowanych metod kolekcji zawierających i znajduj , które będą wyszukiwać według podstawowych identyfikatorów (zamiast kluczy tablicowych). Przykład:

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

zawiera () w rzeczywistości po prostu wywołuje find () i sprawdza wartość null, więc możesz skrócić to do:

if ($myModel = $model->collection->find($primaryId)) {
    var_dump($myModel);
}
Ziad Hilal
źródło
Rozumiemy, że funkcja find () akceptuje podstawowy identyfikator. Chcemy metody, która akceptuje dowolny atrybut, taki jak „kolor” lub „wiek”. Jak dotąd metoda Kalleya jest jedyną, która działa dla każdego atrybutu.
Leng
5

Wiem, że to pytanie zostało pierwotnie zadane przed wydaniem Laravel 5.0, ale od Laravel 5.0 Kolekcje obsługują where()metodę do tego celu.

W przypadku Laravel 5.0, 5.1 i 5.2 where()metoda na Collectionwoli wykonuje tylko porównanie równości. Ponadto ===domyślnie wykonuje ścisłe porównanie równości ( ). Aby wykonać luźne porównanie ( ==), możesz albo przekazać falsejako trzeci parametr, albo użyć whereLoose()metody.

Od wersji Laravel 5.3 where()metoda została rozszerzona, aby bardziej przypominała where()metodę konstruktora zapytań, która przyjmuje operator jako drugi parametr. Podobnie jak w konstruktorze zapytań, operator domyślnie wybierze porównanie równości, jeśli żadne nie zostanie podane. Domyślne porównanie zostało również zmienione ze ścisłego domyślnie na luźne. Jeśli więc chcesz uzyskać ścisłe porównanie, możesz użyć whereStrict()lub po prostu użyć ===jako operatora dla where().

Dlatego od wersji Laravel 5.0 ostatni przykład kodu w pytaniu będzie działał dokładnie tak, jak powinien:

$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
źródło
3

Muszę zaznaczyć, że w odpowiedzi Kalleya jest mały, ale absolutnie KRYTYCZNY błąd. Zmagałem się z tym przez kilka godzin, zanim zdałem sobie sprawę:

Wewnątrz funkcji zwracasz porównanie, więc coś takiego byłoby bardziej poprawne:

$desired_object = $food->filter(function($item) {
    return ($item->id **==** 24);
})->first();
squaretastic
źródło
1
Tak, dziękuję za zwrócenie uwagi. Należy również zauważyć, że funkcja filtrująca nie różni się od mojego foreach()przykładu pod względem wydajności, ponieważ po prostu wykonuje ten sam rodzaj pętli ... w rzeczywistości mój foreach()przykład działa lepiej, ponieważ psuje się po znalezieniu prawidłowego modelu. Ponadto ... {Collection}->find(24)będzie przechwytywał klucz główny, co czyni go najlepszą opcją tutaj. Filtr zaproponowany przez Kalleya jest w rzeczywistości identyczny z $desired_object = $foods->find(24);.
Leng
1
Nigdy nie widziałem **==**operatora, co robi?
kiradotee
@kiradotee Myślę, że OP właśnie próbował podkreślić podwójny równy operator porównania ( == ). Oryginalna odpowiedź zawierała tylko jeden znak równości, więc zamiast porównania wykonywała zadanie. OP próbował podkreślić, że powinny istnieć dwa znaki równości.
patricus
0

Jak w powyższym pytaniu, gdy używasz klauzuli where, musisz również użyć metody get Or first, aby uzyskać wynik.

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

$green_foods = Food::where('color', 'green')->get();
Marco
źródło