【问题标题】:Laravel: Get Object From Collection By AttributeLaravel:按属性从集合中获取对象
【发布时间】:2014-01-22 17:43:30
【问题描述】:

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

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

...那么$foods 是Illuminate Collection 的Food 模型对象。 (本质上是一个模型数组。)

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

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

...所以如果我想用 24 的 id 更改 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.  :(

【问题讨论】:

    标签: php mysql laravel


    【解决方案1】:

    如果你在 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"
            ]
        ],
    ]
    

    【讨论】:

      【解决方案2】:

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

      在你的情况下:

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

      【讨论】:

      • 这应该是 Laravel 5.5 之后公认的答案
      【解决方案3】:

      你可以使用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 Collection 文档。 laravel.com/docs/5.5/collections#method-firstcollect([1, 2, 3, 4])->first(function ($value, $key) { return $value == 2; });
      • 你可以用 where 函数做同样的事情。 $desired_object = $food->where('id', 24)->first();
      【解决方案4】:

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

      /**
      *Get all food
      *
      */
      
      $foods = Food::all();
      
      /**
      *Get green food 
      *
      */
      
      $green_foods = Food::where('color', 'green')->get();
      

      【讨论】:

        【解决方案5】:

        我知道这个问题最初是在 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');
        

        【讨论】:

          【解决方案6】:

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

          $collection = $collection-&gt;keyBy('id');

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

          那么你可以说:

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

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

          【讨论】:

          • 非常有用,尤其是在性能方面, ->first() 在多次调用时可能会很慢(foreach in foreach...),因此您可以“索引”您的集合,例如:$exceptions-&gt;keyBy(function ($exception) { return $exception-&gt;category_id . ' ' . $exception-&gt;manufacturer_id; 并使用-&gt;get($category-&gt;id . ' ' . $manufacturer-&gt;id) 之后!
          • 当新项目添加到集合中时是否继续使用此密钥?还是每次将新对象或数组推送到集合时都需要使用 keyBy()?
          • 很可能您必须再次调用它,因为keyBy 从我记得的内容中返回了新集合,但不确定,您可以查看Illuminate/Support/Collection 来找出它。 (很长一段时间没有在 Laravel 工作,所以有人可以纠正我)。
          • 这对我不起作用,它返回了另一个项目,下一个项目,如果我输入 get(1),它将返回编号为 2 的项目。
          • 批量加载一张表,花了一天时间。使用了这个解决方案,花了几分钟。
          【解决方案7】:

          使用内置的收集方法 contain 和 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。我们想要的是一个接受 any 属性的方法,例如“color”或“age”。到目前为止,kaley 的方法是唯一适用于任何属性的方法。
          【解决方案8】:

          可以调整用于查找值的优雅解决方案 (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];
          }
          

          【讨论】:

            【解决方案9】:

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

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

            【讨论】:

              【解决方案10】:

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

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

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

              【讨论】:

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