96
votes

I've been reviewing the documentation and API for Laravel Collections, but don't seem to find what I am looking for:

I would like to retrieve an array with model data from a collection, but only get specified attributes.

I.e. something like Users::toArray('id','name','email'), where the collection in fact holds all attributes for the users, because they are used elsewhere, but in this specific place I need an array with userdata, and only the specified attributes.

There does not seem to be a helper for this in Laravel? - How can I do this the easiest way?

11
Other viewers might be interested in Collection::implode(). It can take a property and extract it from all the objects in the collection. This doesn't answer this question exactly, but it might be useful for others who have come here from Google, as I did. laravel.com/docs/5.7/collections#method-implodemusicin3d
If you're looking to do the inverse and get all properties except certain properties, use except Users::get()->except(['password', 'secret_answer']);Cameron Wilby

11 Answers

205
votes

You can do this using a combination of existing Collection methods. It may be a little hard to follow at first, but it should be easy enough to break down.

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

Explanation

First, the map() method basically just iterates through the Collection, and passes each item in the Collection to the passed in callback. The value returned from each call of the callback builds the new Collection generated by the map() method.

collect($user->toArray()) is just building a new, temporary Collection out of the Users attributes.

->only(['id', 'name', 'email']) reduces the temporary Collection down to only those attributes specified.

->all() turns the temporary Collection back into a plain array.

Put it all together and you get "For each user in the users collection, return an array of just the id, name, and email attributes."


Laravel 5.5 update

Laravel 5.5 added an only method on the Model, which basically does the same thing as the collect($user->toArray())->only([...])->all(), so this can be slightly simplified in 5.5+ to:

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

If you combine this with the "higher order messaging" for collections introduced in Laravel 5.4, it can be simplified even further:

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

use User::get(['id', 'name', 'email']), it will return you a collection with the specified columns and if you want to make it an array, just use toArray() after the get() method like so:

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

Most of the times, you won't need to convert the collection to an array because collections are actually arrays on steroids and you have easy-to-use methods to manipulate the collection.

6
votes

Method below also works.

$users = User::all()->map(function ($user) {
  return collect($user)->only(['id', 'name', 'email']);
});
1
votes
  1. You need to define $hidden and $visible attributes. They'll be set global (that means always return all attributes from $visible array).

  2. Using method makeVisible($attribute) and makeHidden($attribute) you can dynamically change hidden and visible attributes. More: Eloquent: Serialization -> Temporarily Modifying Property Visibility

1
votes

I have now come up with an own solution to this:

1. Created a general function to extract specific attributes from arrays

The function below extract only specific attributes from an associative array, or an array of associative arrays (the last is what you get when doing $collection->toArray() in Laravel).

It can be used like this:

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

I am using the following functions:

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

This solution does not focus on performance implications on looping through the collections in large datasets.

2. Implement the above via a custom collection i Laravel

Since I would like to be able to simply do $collection->extract('id','url'); on any collection object, I have implemented a custom collection class.

First I created a general Model, which extends the Eloquent model, but uses a different collection class. All you models need to extend this custom model, and not the Eloquent Model then.

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

Secondly I created the following custom collection class:

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

Lastly, all models should then extend your custom model instead, like such:

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

Now the functions from no. 1 above are neatly used by the collection to make the $collection->extract() method available.

1
votes

I had a similar issue where I needed to select values from a large array, but I wanted the resulting collection to only contain values of a single value.

pluck() could be used for this purpose (if only 1 key item is required)

you could also use reduce(). Something like this with reduce:

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

this is a follow up on the patricus [answer][1] above but for nested arrays:

$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();
0
votes

This avoid to load unised attributes, directly from db:

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

References: https://laravel.com/docs/5.8/queries#retrieving-results (search for pluck)

Maybe next you can apply toArray() if you need such format

0
votes

Context

For instance, let's say you want to show a form for creating a new user in a system where the User has one Role and that Role can be used for many Users.

The User model would look like this

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

and the Role model

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

How to get only the specific attributes?

In that specific form we want to create, you'll need data from Role but you don't really need a description.

/**
 * 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.

-1
votes

this seems to work, but not sure if it's optimized for performance or not.

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

output:

array:2 [
  0 => 4
  1 => 1
]
-3
votes
$users = Users::get();

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