ChatGPT解决这个技术问题 Extra ChatGPT

Laravel:按属性从集合中获取对象

在 Laravel 中,如果我执行查询:

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

...那么 $foodsFood 个模型对象的 Illuminate Collection 个。 (本质上是一系列模型。)

但是,这个数组的键很简单:

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

...因此,如果我想更改 id 为 24 的 Food 对象,我不能这样做:

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

...因为这只会改变数组中的第 25 个元素,而不是 id 为 24 的元素。

如何通过任何属性/列(例如但不限于 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 集合中拥有所需的对象时,它会执行额外的不必要的查询。

提前感谢您的任何指导。

编辑:

需要明确的是,您可以在 Illuminate Collection 上调用 ->find() 而不会产生另一个查询,但它接受主 ID。例如:

$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.  :(

k
kalley

您可以使用 filter,如下所示:

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

filter 也将返回一个 Collection,但由于您知道只有一个,您可以在该 Collection 上调用 first

您不再需要过滤器(或者可能永远,我不知道这已经快 4 岁了)。您可以只使用 first

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

嘿,谢谢!我想我可以忍受。在我看来,对于通常是这样一个“雄辩”的框架来说,仍然异常冗长,哈哈。但它仍然比迄今为止的替代品更清洁,所以我会接受它。
正如@squaretastic 在另一个答案中指出的那样,在您的闭包中,您正在进行分配而不是比较(即您应该 == 而不是 = )
实际上,甚至不需要调用 filter()->first(),您只需调用 first(function(...))
来自 Laravel 集合文档。 laravel.com/docs/5.5/collections#method-first collect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
你可以用 where 函数做同样的事情。 $desired_object = $food->where('id', 24)->first();
r
ryanhightower

Laravel 提供了一个名为 keyBy 的方法,它允许通过模型中的给定键设置键。

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

将返回集合,但键是来自任何模型的 id 属性的值。

然后你可以说:

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

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


真的很有用,尤其是在性能方面, ->first() 在多次调用时可能会很慢(foreach 中的 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 的项目作为 id。
批量加载一张桌子,花了一天时间。使用此解决方案并花费了几分钟。
V
Victor Timoftii

从 Laravel 5.5 开始,您可以使用 firstWhere()

在你的情况下:

$green_foods = $foods->firstWhere('color', 'green');

这应该是 Laravel 5.5 之后公认的答案
Z
Ziad Hilal

使用内置的集合方法 contains 和 find,它将按主 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。我们想要的是一个接受任何属性的方法,例如“颜色”或“年龄”。到目前为止,kaley 的方法是唯一适用于任何属性的方法。
R
Rohith Raveendran

由于我不需要循环整个集合,我认为最好有这样的辅助函数

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

p
patricus

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

对于 Laravel 5.0、5.1 和 5.2,Collection 上的 where() 方法只会进行相等比较。此外,默认情况下,它会进行严格的等于比较 (===)。要进行松散比较 (==),您可以将 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');

s
squaretastic

我必须指出,卡利的回答中有一个小但绝对严重的错误。我为此挣扎了几个小时才意识到:

在函数内部,您返回的是一个比较,因此这样的事情会更正确:

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

是的,感谢您指出这一点。还需要注意的是,过滤器函数在性能方面与我的 foreach() 示例没有什么不同,因为它只是执行相同类型的循环......事实上,我的 foreach() 示例性能更好,因为它在找到时中断正确的模型。另外... {Collection}->find(24) 将按主键抓取,这使其成为此处的最佳选择。 Kalley 提出的过滤器实际上与 $desired_object = $foods->find(24); 相同。
从未见过 **==** 运算符,它有什么作用?
@kiradotee我认为OP只是试图强调双重相等比较运算符(==)。最初的答案只使用了一个等号,所以它是在做分配而不是比较。 OP试图强调应该有两个等号。
请分享更多详细信息 - 什么是“严重错误”,为什么如此严重,以及您是如何解决的?
s
softfrog

可以调整用于查找值 (http://betamode.de/2013/10/17/laravel-4-eloquent-check-if-there-is-a-model-with-certain-key-value-pair-in-a-collection/) 的优雅解决方案:

$desired_object_key = $food->array_search(24, $food->lists('id'));
if ($desired_object_key !== false) {
   $desired_object = $food[$desired_object_key];
}

M
Marco

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

/**
*Get all food
*
*/

$foods = Food::all();

/**
*Get green food 
*
*/

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

L
LucianDex

如果你在 Laravel 中是一对多的关系,你可以简单的写如下。 (例如,您有汽车制造商和汽车型号)

            /** Initialize array */
            $data = [];

            /** Extract collection*/
            foreach (Model::all() as $model) {
                /** Initialize relation model array */
                $relationObjects = [];

                /** Iterate and make associative array */
                foreach ($model->relationObjects as $relObject) {
                    $relationObjects[] = $relObject->name; // name or whatever property you want
                }

                /** Push 'relationObjects' to coresponding 'modelName' key */
                $data[$model->name][] = $relationObjects;
            } 

$data 的格式为:

 [   
    "Porsche": [
        [
            "Cayenne",
            "911 GT3"
        ]
    ],
    "Ford": [
        [
            "Mustang"
        ]
    ],
]