【问题标题】:Get only specific attributes with from Laravel Collection仅从 Laravel 集合中获取特定属性
【发布时间】:2016-11-19 14:34:32
【问题描述】:

我一直在查看 Laravel 集合的文档和 API,但似乎没有找到我想要的:

我想从集合中检索包含模型数据的数组,但只获取指定的属性。

即像Users::toArray('id','name','email') 这样的集合实际上包含用户的所有属性,因为它们在其他地方使用,但在这个特定的地方我需要一个包含用户数据的数组,并且只有指定的属性。

在 Laravel 中似乎没有这个助手? - 我怎样才能以最简单的方式做到这一点?

【问题讨论】:

  • 其他观众可能对Collection::implode() 感兴趣。它可以获取一个属性并从集合中的所有对象中提取它。这并不能完全回答这个问题,但它可能对像我一样从谷歌来到这里的其他人有用。 laravel.com/docs/5.7/collections#method-implode
  • 如果您想要逆向获取除某些属性之外的所有属性,请使用 except Users::get()->except(['password', 'secret_answer']);

标签: php laravel


【解决方案1】:

您可以使用现有Collection 方法的组合来执行此操作。一开始可能有点难以理解,但应该很容易理解。

// get your main collection with all the attributes...
$users = Users::get();

// build your second collection with a subset of attributes. this new
// collection will be a collection of plain arrays, not Users models.
$subset = $users->map(function ($user) {
    return collect($user->toArray())
        ->only(['id', 'name', 'email'])
        ->all();
});

说明

首先,map() 方法基本上只是遍历Collection,并将Collection 中的每个项目传递给传入的回调。每次调用回调返回的值构建由map() 方法生成的新Collection

collect($user->toArray()) 只是从Users 属性中构建一个新的临时Collection

->only(['id', 'name', 'email']) 将临时 Collection 减少到仅指定的那些属性。

->all() 将临时的Collection 转换回普通数组。

把它们放在一起,你会得到“对于 users 集合中的每个用户,返回一个仅包含 id、name 和 email 属性的数组。”


Laravel 5.5 更新

Laravel 5.5 在 Model 上添加了一个only 方法,其作用与collect($user->toArray())->only([...])->all() 基本相同,因此在 5.5+ 中可以稍微简化为:

// get your main collection with all the attributes...
$users = Users::get();

// build your second collection with a subset of attributes. this new
// collection will be a collection of plain arrays, not Users models.
$subset = $users->map(function ($user) {
    return $user->only(['id', 'name', 'email']);
});

如果你将它与 Laravel 5.4 中引入的 "higher order messaging" for collections 结合起来,它可以进一步简化:

// get your main collection with all the attributes...
$users = Users::get();

// build your second collection with a subset of attributes. this new
// collection will be a collection of plain arrays, not Users models.
$subset = $users->map->only(['id', 'name', 'email']);

【讨论】:

  • 谢谢!我已将此标记为已接受的答案,因为它就像现有 Laravel 功能的魅力一样。 - 现在,可以通过自定义集合类将其提供给集合,就像我在替代解决方案中描述的那样。
  • 就是这样。但我希望有一种不那么冗长的方式来模拟 QueryBuilder 的 ::get([ 'fields', 'array' ]) 行为。基本上,它对于“转储”会很方便。
  • 对于数组你可以做$newArray = Arr::only($initialArray, [ 'id', 'name' /* allowed keys list */ ]);
  • 这很好,但我想知道如果我在里面有一个嵌套数组,它会如何工作
  • 在最近的 Laravel 版本中,这可以写得更简洁:$users->map->only(['column', ...])
【解决方案2】:

使用User::get(['id', 'name', 'email']),它会返回一个包含指定列的集合,如果你想让它成为一个数组,只需在get()方法之后使用toArray(),如下所示:

User::get(['id', 'name', 'email'])->toArray()

大多数时候,您不需要将集合转换为数组,因为集合实际上是类固醇上的数组,并且您有易于使用的方法来操作集合。

【讨论】:

  • 集合已由先前的查询创建,需要更多属性。因此我需要生成一个只有特定属性的数据数组。
  • 您可以通过传递要返回的列数组来使查询动态化,或者您可以执行另一个->get(['id', 'email', 'name']),它不会重新查询数据库,但它将返回一个仅包含选定值的新集合. pluck / lists、get、first 等方法在查询构建器类和集合类中具有相同的名称。
  • 褶边,在 Laravel 5.3 中不起作用。 - 查询构建器“get”方法实际上采用列,但它也执行另一个查询。集合的“get”方法可以通过key从集合中提取特定模型,这里不是我要的。
  • get() 总是返回一个集合,不管是 DB::table()->get() 还是 Model::get(),所以如果你把查询保存在一个变量并在该 $query 变量上运行 get 方法,您不应该对数据库进行另一个查询。
  • 当我将查询存储在像 $query = Model::where(...) 这样的变量中时,每当我执行 $query->get() 时,它都会运行对数据库的查询。 - 另外,您的建议恕我直言不是该问题的理想解决方案。我已经用我通过自定义集合实现的一些新数组函数解决了这个问题(我自己的答案)。
【解决方案3】:

下面的方法也可以。

$users = User::all()->map(function ($user) {
  return collect($user)->only(['id', 'name', 'email']);
});

【讨论】:

    【解决方案4】:
    1. 您需要定义$hidden$visible 属性。它们将被设置为全局(这意味着总是从$visible 数组返回所有属性)。

    2. 使用makeVisible($attribute)makeHidden($attribute) 方法可以动态改变隐藏和可见属性。更多:Eloquent: Serialization -> Temporarily Modifying Property Visibility

    【讨论】:

    • 我不希望这是全局的,而是选择要从集合中动态提取的属性。
    【解决方案5】:

    我现在想出了一个自己的解决方案:

    1.创建了一个通用函数来从数组中提取特定属性

    下面的函数仅从关联数组或关联数组数组中提取特定属性(最后一个是你在 Laravel 中执行 $collection->toArray() 时得到的)。

    可以这样使用:

    $data = array_extract( $collection->toArray(), ['id','url'] );
    

    我正在使用以下功能:

    function array_is_assoc( $array )
    {
            return is_array( $array ) && array_diff_key( $array, array_keys(array_keys($array)) );
    }
    
    
    
    function array_extract( $array, $attributes )
    {
        $data = [];
    
        if ( array_is_assoc( $array ) )
        {
            foreach ( $attributes as $attribute )
            {
                $data[ $attribute ] = $array[ $attribute ];
            }
        }
        else
        {
            foreach ( $array as $key => $values )
            {
                $data[ $key ] = [];
    
                foreach ( $attributes as $attribute )
                {
                    $data[ $key ][ $attribute ] = $values[ $attribute ];
                }
            }   
        }
    
        return $data;   
    }
    

    此解决方案不关注循环遍历大型数据集中的集合的性能影响。

    2。通过 Laravel 中的自定义集合实现上述操作

    由于我希望能够简单地对任何集合对象执行$collection->extract('id','url');,因此我实现了一个自定义集合类。

    首先我创建了一个通用模型,它扩展了 Eloquent 模型,但使用了不同的集合类。你所有的模型都需要扩展这个自定义模型,而不是 Eloquent 模型。

    <?php
    namespace App\Models;
    use Illuminate\Database\Eloquent\Model as EloquentModel;
    use Lib\Collection;
    class Model extends EloquentModel
    {
        public function newCollection(array $models = [])
        {
            return new Collection( $models );
        }    
    }
    ?>
    

    其次,我创建了以下自定义集合类:

    <?php
    namespace Lib;
    use Illuminate\Support\Collection as EloquentCollection;
    class Collection extends EloquentCollection
    {
        public function extract()
        {
            $attributes = func_get_args();
            return array_extract( $this->toArray(), $attributes );
        }
    }   
    ?>
    

    最后,所有模型都应该扩展您的自定义模型,如下所示:

    <?php
    namespace App\Models;
    class Article extends Model
    {
    ...
    

    现在从没有的功能。集合巧妙地使用了上面的 1 以使 $collection-&gt;extract() 方法可用。

    【讨论】:

    【解决方案6】:

    我有一个类似的问题,我需要从一个大数组中选择值,但我希望结果集合只包含单个值的值。

    pluck() 可用于此目的(如果只需要 1 个关键项)

    您也可以使用reduce()。像这样的减少:

    $result = $items->reduce(function($carry, $item) {
        return $carry->push($item->getCode());
    }, collect());
    

    【讨论】:

      【解决方案7】:

      这是对上述 patricus [answer][1] 的跟进,但适用于 嵌套 数组:

      $topLevelFields =  ['id','status'];
      $userFields = ['first_name','last_name','email','phone_number','op_city_id'];
      
      return $onlineShoppers->map(function ($user) { 
          return collect($user)->only($topLevelFields)
                                   ->merge(collect($user['user'])->only($userFields))->all();  
          })->all();
      

      【讨论】:

        【解决方案8】:

        这避免了直接从 db 加载统一的属性:

        DB::table('roles')->pluck('title', 'name');
        

        参考:https://laravel.com/docs/5.8/queries#retrieving-results(搜索 pluck)

        如果你需要这种格式,也许接下来你可以申请toArray()

        【讨论】:

          【解决方案9】:

          上下文

          例如,假设您想在系统中显示一个用于创建新用户的表单,其中用户有一个角色,并且该角色可用于多个用户。

          User 模型如下所示

          class User extends Authenticatable
          {   
              /**
               * User attributes.
               *
               * @var array
               */
              protected $fillable = [
                  'name', 'email', 'password', 'role_id'
              ];
          
          
              /**
               * Role of the user
               *
               * @return \App\Role
               */
              public function role()
              {
                  return $this->belongsTo(Role::class);
              }
          ...
          

          和角色模型

          class Role extends Model
          {
              /**
               * Role attributes.
               *
               * @var array
               */
              protected $fillable = [
                  'name', 'description'
              ];
              
              /**
               * Users of the role
               *
               * @return void
               */
              public function users()
              {
                  return $this->hasMany(User::class);
              }
          }
          

          如何只获取特定属性?

          在我们要创建的特定表单中,您需要来自 Role 的数据,但实际上并不需要描述。

          /**
           * Create user
           *
           * @param  \App\Role  $model
           * @return \Illuminate\View\View
           */
          public function create(Role $role)
          {
              return view('users.create', ['roles' => $role->get(['id', 'name'])]);
          }
          

          请注意,我们使用 $role-&gt;get(['id', 'name']) 来指定我们不需要该描述。

          【讨论】:

            【解决方案10】:

            这似乎可行,但不确定它是否针对性能进行了优化。

            $request-&gt;user()-&gt;get(['id'])-&gt;groupBy('id')-&gt;keys()-&gt;all();

            输出:

            array:2 [
              0 => 4
              1 => 1
            ]
            

            【讨论】:

              【解决方案11】:
              $users = Users::get();
              
              $subset = $users->map(function ($user) {
                  return array_only(user, ['id', 'name', 'email']);
              });
              

              【讨论】:

                猜你喜欢
                • 2021-05-02
                • 2018-12-25
                • 1970-01-01
                • 1970-01-01
                • 2014-01-22
                • 2013-08-08
                • 2016-03-06
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多