Подтвердить что ты не робот

Laravel: Получить объект из коллекции по атрибуту

В Laravel, если я выполняю запрос:

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

... then $foods представляет собой Illuminate Collection объектов модели Food. (По сути, это массив моделей.)

Однако ключи этого массива просто:

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

... поэтому, если я хочу изменить, скажем, объект Food с id из 24, я не могу этого сделать:

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

... потому что это просто изменит 25-й элемент в массиве, а не элемент с id из 24.

Как получить один (или несколько) элемент из коллекции по ANY атрибуту/столбцу (например, id/color/age/etc.)?

Конечно, я могу это сделать:

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.

Заранее благодарим за любые рекомендации.

EDIT:

Чтобы быть понятным, вы можете вызвать ->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.  :(
4b9b3361

Ответ 1

Вы можете использовать filter, например:

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

filter также вернет Collection, но поскольку вы знаете, что будет только один, вы можете вызвать first на этом Collection.

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

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

Ответ 2

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

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

вернет коллекцию, но с ключами будут значения атрибута id от любой модели.

Затем вы можете сказать:

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

и он будет захватывать правильный элемент без беспорядка использования функции фильтра.

Ответ 3

Так как мне не нужно цитировать всю коллекцию, я думаю, что лучше иметь вспомогательную функцию, подобную этой

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

Ответ 4

Используйте встроенные методы коллекции содержат и find, которые будут искать по основным идентификаторам (вместо ключей массива). Пример:

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

содержит() на самом деле просто вызывает find() и проверяет значение null, поэтому вы можете сократить его до:

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

Ответ 5

Я знаю, что этот вопрос был первоначально задан до того, как был выпущен Laravel 5.0, но с Laravel 5.0 Collections поддерживают метод 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');

Ответ 6

Я должен указать, что в ответе Калли есть небольшая, но абсолютно КРИТИЧЕСКАЯ ошибка. Я боролся с этим несколько часов, прежде чем осознать:

Внутри функции то, что вы возвращаете, является сравнением, и, следовательно, что-то вроде этого было бы более правильным:

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

Ответ 8

Как вопрос выше, когда вы используете предложение where, вам также нужно использовать метод get или first для получения результата.

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

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