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个元素的元素。

如何通过ANY属性/列(例如但不限于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 Collection,而不会产生其他查询,但是它接受主ID。例如:

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

但是,仍然没有干净的(非循环,非查询)方式来通过Collection中的属性来获取元素,如下所示:

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

Answers:


125

您可以这样使用filter

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

filter也将返回一个Collection,但因为你知道会有只有一个,你可以叫firstCollection

您不再需要过滤器(或者也许曾经,我不知道它已经快4年了)。您可以使用first

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

7
嘿,谢谢!我想我可以接受。在我看来,通常如此“口才”的框架仍然非常冗长。但是,到目前为止,它仍然比替代方案干净得多,因此我接受它。
Leng 2014年

正如@squaretastic在另一个答案中指出的那样,在闭包内部,您是在分配而不是进行比较(即,您应该==而不是=)
ElementalStorm

24
实际上,甚至没有必要致电filter()->first()您就可以致电first(function(...))
lukasgeiter 2015年

来自Laravel Collection文档。laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
Shiro

2
您可以使用where函数执行相同的操作。$desired_object = $food->where('id', 24)->first();
Bhavin Thummar

111

Laravel提供了一种称为的方法keyBy,该方法允许通过模型中的给定键来设置键。

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

将返回集合,但键是id任何模型的attribute的值。

然后您可以说:

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

它将抓住正确的项目,而不会使用过滤器功能。


2
确实很有用,尤其是对性能而言,-> first()多次调用时可能会很慢(在foreach中为foreach ...),因此您可以像这样“索引”您的集合,$exceptions->keyBy(function ($exception) { return $exception->category_id . ' ' . $exception->manufacturer_id;->get($category->id . ' ' . $manufacturer->id)在after之后使用!
弗朗索瓦·布雷顿

将新项目添加到集合时,此键是否继续使用?还是每次将新对象或数组推到集合上时都需要使用keyBy()?
杰森

很可能您必须再次调用它,因为keyBy从我记得的内容中返回了新的收藏,但是不确定,您可以检查一下Illuminate/Support/Collection以找到它。(长时间不在Laravel工作,所以有人可以纠正我)。
Maksym Cierzniak '16

这对我不起作用,它返回了另一个项目,下一个项目,如果我键入get(1),它将返回ID为2的项目。
Jaqueline Passos

批量加载表,花了一天的时间。使用了此解决方案,花了几分钟。
杰德·林奇


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

使用内置的collection方法containsfind,它们将通过主ID(而不是数组键)进行搜索。例:

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

contains()实际上只是调用find()并检查null,因此您可以将其缩短为:

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

我们了解find()接受一个主ID。我们想要的是一种接受任何属性(例如“颜色”或“年龄”)的方法。到目前为止,kalley的方法是唯一适用于任何属性的方法。

5

我知道这个问题最初是在Laravel 5.0发布之前提出的,但是从Laravel 5.0开始,Collectionswhere()为此目的支持该方法。

对于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

我必须指出,kalley的答案中存在一个很小但绝对为CRITICAL的错误。在意识到之前,我为此奋斗了几个小时:

在函数内部,您返回的是一个比较,因此类似以下的内容会更正确:

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

1
是的,谢谢你指出这一点。同样重要的是要注意,过滤器函数与我的foreach()示例在性能方面没有什么不同,因为它只执行相同类型的循环...实际上,我的foreach()示例性能更好,因为它会在找到正确的模型时中断。另外...{Collection}->find(24)将通过主键进行抓取,这使其成为此处的最佳选择。Kalley提出的滤波器实际上与相同$desired_object = $foods->find(24);
Leng

1
从未见过**==**操作员,它是做什么的?
kiradotee

@kiradotee我认为OP只是试图强调双重平等比较运算符(==)。原始答案仅使用一个等号,因此它正在执行分配而不是比较。OP试图强调应该有两个平等的迹象。
patricus'6


0

作为上面的问题,当您使用where子句时,您还需要使用get或first方法来获取结果。

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

$green_foods = Food::where('color', 'green')->get();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.