通过Laravel Collection仅获取特定属性


82

我一直在查看Laravel Collections的文档和API,但似乎找不到我想要的东西:

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

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

Laravel中似乎没有这个的帮助者?-如何最简单地做到这一点?


其他观众可能对此感兴趣Collection::implode()。它可以采用属性并将其从集合中的所有对象中提取。这并不能完全回答这个问题,但对像我一样从Google来到这里的其他人可能有用。laravel.com/docs/5.7/collections#method-implode
musicin3d

Answers:


173

您可以结合使用现有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传递给传入的回调。从回调的每次调用返回的值将构建Collectionmap()方法生成的新值。

collect($user->toArray())只是CollectionUsers属性之外建立一个新的临时属性。

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

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

将所有内容放在一起,您将获得“对于用户集合中的每个用户,仅返回ID,名称和电子邮件属性的数组。”


Laravel 5.5更新

Laravel 5.5only在Model上添加了一个方法,该方法与基本上具有相同的作用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']);

谢谢!我将其标记为可接受的答案,因为它可以与现有Laravel功能结合使用。-现在,可以通过自定义集合类将其提供给集合,如我在替代解决方案中所述。
preyz

而已。但是,我希望有一种不太冗长的方法来模仿QueryBuilder的::get([ 'fields', 'array' ])行为。基本上,这对于“转储”会很方便。
彼拉特

可以做的阵列$newArray = Arr::only($initialArray, [ 'id', 'name' /* allowed keys list */ ]);
Hop hop

这很棒,但我想知道如果我内部有嵌套数组
怎么办?

1
在Laravel的最新版本中,这可以写得更加简洁:$users->map->only(['column', ...])
okdewit,

17

use User::get(['id', 'name', 'email']),它将返回具有指定列的集合,如果要使其成为数组,只需toArray()在如下get()方法之后使用:

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

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


1
该集合已由先前的查询创建,需要更多属性。因此,我需要生成一个仅具有特定属性的数据数组。
preyz

您可以通过传递一个要返回的列数组来使查询动态化,也可以执行另一个->get(['id', 'email', 'name'])不会重新查询数据库的查询,但是它将返回仅包含选定值的新集合。查询构建器类和集合类中的方法如pluck / lists,get,first等都具有相同的名称。
皱纹

皱纹,在Laravel 5.3中不起作用。-查询构建器的“ get”方法实际上会占用列,但它还会执行另一个查询。集合的“ get”方法可以通过集合中的键提取特定的模型,这不是我想要的。
preyz

get()总是返回一个集合,它是DB :: table()-> get()还是Model :: get()都没关系,因此,如果将查询保存在变量中并在该变量上运行get方法$ query变量,您不应该再向数据库查询。
荷叶边

1
当我将查询存储在$ query = Model :: where(...)这样的变量中时,每当我执行$ query-> get()时,它将对数据库运行查询。-另外,您的建议恕我直言不是解决问题的理想方法。我已经通过我通过自定义集合(我自己的答案)实现的新数组函数解决了该问题。
preyz

3

下面的方法也可以。

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

1

我现在提出了自己的解决方案:

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->extract()方法可用。


查阅有关扩展集合的Laravel文档:laravel.com/docs/5.5/collections#extending-collections
Andreas Hacker


0

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

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

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

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

0

这是上述父级[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();


-1

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

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

输出:

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

-3
$users = Users::get();

$subset = $users->map(function ($user) {
    return array_only(user, ['id', 'name', 'email']);
});
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.