只从 LaravelCollection 获取特定属性

我一直在回顾 Laravel Collection 的文档和 API,但似乎没有找到我想要的:

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

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

在 Laravel 似乎没有帮手?- 我怎么才能做到这一点,最简单的方法?

216406 次浏览
  1. 您需要定义 $hidden$visible属性。它们将被设置为全局的(这意味着始终从 $visible数组返回所有属性)。

  2. 使用方法 makeVisible($attribute)makeHidden($attribute),您可以动态地更改隐藏的和可见的属性

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

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

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

我现在想出了一个自己的解决办法:

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

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

It can be used like this:

$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. 通过自定义集合 iLaravel 实现上述功能

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

首先,我创建了一个通用模型,它扩展了 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->extract()方法可用。

您可以使用现有 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()) is just building a new, temporary Collection out of the Users attributes.

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

->all()将临时 Collection返回到一个普通数组。

将它们放在一起,您会得到“对于用户集合中的每个用户,返回一个只包含 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中引入的 用于集合的“高级订单消息传递”结合起来,它可以被进一步简化:

// 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']);

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

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

output:

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

我也遇到过类似的问题,需要从大型数组中选择值,但是我希望结果集合只包含单个值的值。

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

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

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

这是对上面的 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();
$users = Users::get();


$subset = $users->map(function ($user) {
return array_only(user, ['id', 'name', 'email']);
});

This avoid to load unised attributes, directly from db:

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

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

也许下一步你可以应用 toArray(),如果你需要这样的格式

下面的方法也可以工作。

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

背景

例如,假设您想要显示一个表单,用于在一个系统中创建一个新用户,在这个系统中 User 只有一个 Role,而且这个 Role 可以用于许多 User。

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);
}
}

如何只获得特定的属性?

在我们想要创建的特定表单中,您将需要“角色”中的数据,但实际上并不需要描述。

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

Notice that we're using $role->get(['id', 'name']) to specify we don't want the description.

这是对上面@patricus answer的一个跟进:

使用以下命令返回对象的集合而不是数组,这样您就可以使用与 model 对象相同的对象,即 $subset[0]->name

$subset = $users->map(function ($user) {
return (object) $user->only(['id', 'name', 'email']);
});