Laravel: Отримати об’єкт із колекції за атрибутом


90

У Laravel, якщо я виконую запит:

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

... то $foodsце Освітіть Колекція з Foodмодельних об'єктів. (По суті масив моделей.)

Однак ключі цього масиву просто:

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

... тому, якщо я хочу змінити, скажімо, Foodоб’єкт із idзначенням 24, я не можу цього зробити:

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

... тому що це просто змінить 25-й елемент масиву, а не елемент з id24.

Як отримати один (або декілька) елементів із колекції за БУДЬ-ЯКИМ атрибутом / стовпцем (наприклад, але, не обмежуючись цим, id / color / age / тощо)?

Звичайно, я можу зробити це:

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

... але, це просто грубо.

І, звичайно, я можу це зробити:

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

... але це ще грубіше , оскільки він виконує додатковий непотрібний запит, коли у мене вже є бажаний об’єкт у $foodsколекції.

Заздалегідь дякую за будь-які вказівки.

РЕДАГУВАТИ:

Щоб були зрозумілі, ви можете зателефонувати ->find()на Illuminate колекції без нересту іншого запиту, але він тільки приймає первинний ідентифікатор. Наприклад:

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

Однак досі не існує чистого (нециклічного, незапитного) способу захопити елемент (и) за атрибутом із колекції, наприклад:

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

Відповіді:


125

Ви можете використовувати filterприблизно так:

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

filterтакож поверне a Collection, але оскільки ви знаєте, що буде лише один, ви можете зателефонувати firstза цим Collection.

Вам більше не потрібен фільтр (або, можливо, коли-небудь, я не знаю, що йому майже 4 роки). Ви можете просто використовувати first:

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

7
Гей, спасибі! Я думаю, що можу з цим жити. На мою думку, все ще надзвичайно багатослівний щодо того, що зазвичай є такою `` промовистою '' структурою ха-ха. Але це все ще набагато чистіше, ніж альтернативи, поки що, тому я візьму це.
Ленг

Як зазначає @squaretastic в іншій відповіді, всередині вашого закриття ви робите присвоєння, а не порівняння (тобто ви повинні ==, а не =)
ElementalStorm

24
Насправді навіть телефонувати не потрібно, filter()->first()ви можете просто зателефонуватиfirst(function(...))
lukasgeiter

з документації колекції Laravel. laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
Широ

2
Ви можете зробити те ж саме з функцією where. $desired_object = $food->where('id', 24)->first();
Bhavin Thummar

111

Laravel пропонує метод, keyByякий називається, що дозволяє встановлювати ключі за даним ключем у моделі.

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

поверне колекцію, але ключі є значеннями id атрибута з будь-якої моделі.

Тоді ви можете сказати:

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

і він захопить правильний предмет, не використовуючи функцію фільтра.


2
Дійсно корисно, особливо для продуктивності, -> first () може бути повільним при багаторазовому виклику (foreach in foreach ...), тому ви можете "індексувати" свою колекцію, як: $exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;і використовувати ->get($category->id . ' ' . $manufacturer->id)після!
Франсуа Бретон,

Чи продовжує цей ключ використовуватись, коли до колекції додаються нові предмети? Або мені потрібно використовувати keyBy () кожного разу, коли новий об'єкт або масив надсилається до колекції?
Джейсон

Швидше за все, вам доведеться зателефонувати йому ще раз, оскільки keyByповертає нову колекцію з того, що я пам’ятаю, хоча, не впевнений, ви можете перевірити, Illuminate/Support/Collectionщоб це дізнатись. (Давно не працюю в Laravel, щоб хтось міг мене виправити).
Максим Черзняк

Це не спрацювало у мене, він повернув інший елемент, наступний елемент, якщо я наберу get (1), він поверне товар, який має номер 2 як ідентифікатор.
Jaqueline Passos

Пакетне завантаження столу займало день. Використовував це рішення, і це зайняло хвилини.
Jed Lynch,


7

Оскільки мені не потрібно циклізувати всю колекцію, я вважаю, що краще мати таку допоміжну функцію

/**
 * 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;
}

7

Використовуйте вбудовані методи збору вмісту і пошуку , які здійснюватимуть пошук за первинними ідентифікаторами (замість ключів масиву). Приклад:

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

містить () насправді просто викликає find () і перевіряє наявність null, тому ви можете скоротити його до:

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

Ми розуміємо, що find () приймає основний ідентифікатор. Нам потрібен метод, який приймає будь-який атрибут, такий як "колір" або "вік". Поки що метод Каллі єдиний, який працює для будь-якого атрибута.
Ленг

5

Я знаю, що це питання спочатку було задано ще до випуску Laravel 5.0, але станом на Laravel 5.0 колекції підтримують where()метод для цієї мети.

Для Laravel 5.0, 5.1 та 5.2 where()метод на Collectionзаповіті робить лише рівне порівняння. Крім того, він ===за замовчуванням робить суворе порівняння ( ). Щоб зробити вільне порівняння ( ==), ви можете або пройтиfalse як третій параметр, або використовуватиwhereLoose() методом.

Починаючи з Laravel 5.3, where()метод був розширений, щоб працювати більше, як where()метод для конструктора запитів, який приймає оператор як другий параметр. Також, як і конструктор запитів, оператор за замовчуванням порівнює порівняння, якщо жодне з них не вказано. Порівняння за замовчуванням також було переключено зі строгого за замовчуванням на вільне за замовчуванням. Отже, якщо ви хочете суворого порівняння, ви можете використовувати whereStrict()або просто використовувати ===як оператор дляwhere() .

Отже, станом на Laravel 5.0, останній приклад коду у питанні буде працювати точно так, як передбачалося:

$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');

3

Я повинен зазначити, що у відповіді Келлі є невелика, але абсолютно КРИТИЧНА помилка. Я боровся з цим кілька годин, перш ніж зрозуміти:

Усередині функції те, що ви повертаєте, є порівнянням, і, отже, щось подібне було б більш правильним:

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

1
Так, дякую, що вказали на це. Важливо також зазначити, що функція фільтра нічим не відрізняється від мого foreach()прикладу за ефективністю, оскільки вона просто робить один і той же цикл ... насправді, мій foreach()приклад є кращим, оскільки він ламається після пошуку правильної моделі. Також ... {Collection}->find(24)захопить первинний ключ, що робить його найкращим варіантом тут. Запропонований фільтр Каллі насправді ідентичний $desired_object = $foods->find(24);.
Ленг,

1
Ніколи не бачив **==**оператора, що він робить?
kiradotee

@kiradotee Я думаю, що OP просто намагався підкреслити оператор порівняння з подвійною рівністю ( == ). Початкова відповідь використовувала лише один знак рівності, тому вона виконувала завдання замість порівняння. OP намагався підкреслити, що має бути два знаки рівності.
patricus


0

Як питання вище, коли ви використовуєте речення where, вам також потрібно використовувати метод get Or first для отримання результату.

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

$green_foods = Food::where('color', 'green')->get();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.