Laravel: Dapatkan Objek Dari Koleksi Berdasarkan Atribut

91

Di Laravel, jika saya melakukan kueri:

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

... kemudian $foodsadalah Koleksi Illuminate dari Foodobyek model. (Pada dasarnya serangkaian model.)

Namun, kunci dari array ini hanyalah:

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

... jadi jika saya ingin mengubah, katakanlah, Foodobjek dengan id24, saya tidak bisa melakukan ini:

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

... karena ini hanya akan mengubah elemen ke-25 dalam larik, bukan elemen dengan id24.

Bagaimana cara mendapatkan satu (atau beberapa) elemen dari koleksi berdasarkan atribut / kolom APA SAJA (seperti, tetapi tidak terbatas pada, id / color / age / etc.)?

Tentu saja, saya bisa melakukan ini:

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

... tapi, itu menjijikkan.

Dan, tentu saja, saya bisa melakukan ini:

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

... tapi itu bahkan lebih menjijikkan , karena melakukan kueri tambahan yang tidak perlu ketika saya sudah memiliki objek yang diinginkan dalam $foodskoleksi.

Terima kasih sebelumnya atas panduan apa pun.

EDIT:

Untuk memperjelas, Anda dapat memanggil ->find()Koleksi Iluminasi tanpa memunculkan kueri lain, tetapi hanya menerima ID utama. Contohnya:

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

Namun, masih belum ada cara yang bersih (non-perulangan, non-kueri) untuk mengambil elemen dengan atribut dari Koleksi, seperti ini:

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

Jawaban:

126

Anda dapat menggunakan filter, seperti ini:

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

filterjuga akan kembali Collection, tapi karena Anda tahu akan ada hanya satu, Anda dapat menghubungi firstpada itu Collection.

Anda tidak perlu filter lagi (atau mungkin pernah, saya tidak tahu ini hampir 4 tahun). Anda bisa menggunakan first:

$desired_object = $food->first(function($item) {
    return $item->id == 24;
});
kalley
sumber
7
Hei, terima kasih! Saya pikir saya bisa hidup dengan itu. Masih sangat bertele-tele menurut saya untuk apa yang biasanya merupakan kerangka kerja 'Eloquent' haha. Tapi sejauh ini masih jauh lebih bersih daripada alternatifnya, jadi saya akan mengambilnya.
Leng
Seperti yang ditunjukkan @squaretastic di jawaban lain, di dalam penutupan Anda, Anda membuat tugas dan bukan perbandingan (yaitu Anda harus == dan bukan =)
ElementalStorm
24
Sebenarnya bahkan tidak perlu menelepon filter()->first()Anda bisa meneleponfirst(function(...))
lukasgeiter
dari dokumentasi Laravel Collection. laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
Shiro
2
Anda dapat melakukan hal yang sama dengan fungsi where. $desired_object = $food->where('id', 24)->first();
Bhavin Thummar
111

Laravel menyediakan metode yang disebut keyByyang memungkinkan untuk mengatur kunci dengan kunci yang diberikan dalam model.

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

akan mengembalikan koleksi tetapi dengan kunci menjadi nilai idatribut dari model apa pun.

Kemudian Anda bisa mengatakan:

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

dan itu akan mengambil item yang benar tanpa kekacauan menggunakan fungsi filter.

Maksym Cierzniak
sumber
2
Sangat berguna, terutama untuk kinerja, -> first () bisa lambat ketika dipanggil beberapa kali (foreach in foreach ...) sehingga Anda dapat "mengindeks" koleksi Anda seperti: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;dan digunakan ->get($category->id . ' ' . $manufacturer->id)setelahnya!
François Breton
Apakah kunci ini terus digunakan saat item baru ditambahkan ke koleksi? Atau apakah saya perlu menggunakan keyBy () setiap kali objek atau array baru didorong ke koleksi?
Jason
Kemungkinan besar Anda harus memanggilnya lagi karena keyBymengembalikan koleksi baru dari yang saya ingat, meskipun tidak yakin, Anda dapat memeriksanya Illuminate/Support/Collectionuntuk menemukannya. (Tidak bekerja di Laravel untuk beberapa waktu sehingga seseorang dapat mengoreksi saya).
Maksym Cierzniak
Ini tidak berhasil untuk saya, ini mengembalikan item lain, item berikutnya, jika saya mengetik get (1) itu akan mengembalikan item yang memiliki nomor 2 sebagai id.
Jaqueline Passos
Batch memuat tabel dan butuh satu hari. Menggunakan solusi ini dan butuh beberapa menit.
Jed Lynch
23

Mulai dari Laravel 5.5 Anda dapat menggunakan firstWhere ()

Dalam kasus Anda:

$green_foods = $foods->firstWhere('color', 'green');
Victor Timoftii
sumber
3
Ini harus menjadi jawaban yang diterima setelah Laravel 5,5
beerwin
7

Karena saya tidak perlu mengulang seluruh koleksi, saya pikir lebih baik memiliki fungsi pembantu seperti ini

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

Gunakan metode koleksi bawaan berisi dan temukan , yang akan mencari berdasarkan id utama (bukan kunci array). Contoh:

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

contains () sebenarnya hanya memanggil find () dan memeriksa null, sehingga Anda dapat mempersingkatnya menjadi:

if ($myModel = $model->collection->find($primaryId)) {
    var_dump($myModel);
}
Ziad Hilal
sumber
Kami memahami bahwa find () menerima ID utama. Yang kami inginkan adalah metode yang menerima atribut apa pun , seperti "warna" atau "usia". Sejauh ini, metode kalley adalah satu-satunya metode yang berfungsi untuk atribut apa pun.
Leng
5

Saya tahu pertanyaan ini awalnya ditanyakan sebelum Laravel 5.0 dirilis, tetapi pada Laravel 5.0, Koleksi mendukung where()metode untuk tujuan ini.

Untuk Laravel 5.0, 5.1, dan 5.2, where()metode di Collectionhanya akan melakukan perbandingan yang sama. Selain itu, ia melakukan perbandingan yang sama ketat ( ===) secara default. Untuk melakukan perbandingan longgar ( ==), Anda dapat meneruskan falsesebagai parameter ketiga atau menggunakan whereLoose()metode.

Pada Laravel 5.3, where()metode ini diperluas agar berfungsi lebih seperti where()metode untuk pembuat kueri, yang menerima operator sebagai parameter kedua. Juga seperti pembuat kueri, operator akan secara default menggunakan perbandingan yang sama jika tidak ada yang disediakan. Perbandingan default juga diubah dari ketat secara default menjadi longgar secara default. Jadi, jika Anda ingin perbandingan yang ketat, Anda dapat menggunakan whereStrict(), atau hanya menggunakan ===sebagai operator where().

Oleh karena itu, pada Laravel 5.0, contoh kode terakhir dalam pertanyaan akan bekerja persis seperti yang diinginkan:

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

Saya harus menunjukkan bahwa ada kesalahan kecil tapi sangat KRITIS dalam jawaban kalley. Saya berjuang dengan ini selama beberapa jam sebelum menyadari:

Di dalam fungsi, apa yang Anda kembalikan adalah perbandingan, dan dengan demikian sesuatu seperti ini akan lebih tepat:

$desired_object = $food->filter(function($item) {
    return ($item->id **==** 24);
})->first();
squaretastic
sumber
1
Ya, terima kasih telah menunjukkan hal ini. Penting juga untuk dicatat bahwa fungsi filter tidak berbeda dari foreach()contoh saya dari segi kinerja, karena hanya melakukan jenis perulangan yang sama ... pada kenyataannya, foreach()contoh saya berkinerja lebih baik karena rusak saat menemukan model yang benar. Juga ... {Collection}->find(24)akan mengambil kunci utama, yang menjadikannya pilihan terbaik di sini. Filter yang diusulkan Kalley sebenarnya identik dengan $desired_object = $foods->find(24);.
Leng
1
Belum pernah melihat **==**operator, apa fungsinya?
Kiradotee
@kiradotee Saya pikir OP hanya mencoba untuk menekankan operator perbandingan ganda yang sama ( == ). Jawaban asli hanya menggunakan satu tanda sama dengan, jadi ia mengerjakan tugas, bukan perbandingan. OP berusaha menekankan harus ada dua tanda yang sama.
patricus
0

Seperti pertanyaan di atas saat Anda menggunakan klausa where Anda juga perlu menggunakan metode get Or first untuk mendapatkan hasilnya.

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

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