php - Extract subset of fields from within a Laravel collection

I have a Laravel collection like this (approximating using array syntax; the actual data is a Collection of objects obtained from an API response, not a local DB):

$rows = [
  [
    'id': 1,
    'name': 'Sue',
    'age': 23,
  ],
  [
    'id': 2,
    'name': 'Joe',
    'age': 25,
  ],
]

I want to extract a subset of the fields:

$subset = [];
foreach ($rows as $row) {
  $subset[] = ['name' => $row['name'], 'age' => $row['age']];
}

So that I end up with:

$subset = [
  [
    'name': 'Sue',
    'age': 23,
  ],
  [
    'name': 'Joe',
    'age': 25,
  ],
]

What should I use to achieve that instead of the for loop?

I found this suggestion, using a higher-order message, which made some kind of sense:

$subset = $rows->map->only(['name', 'age']);

but that just gives me a Collection of null values. Expanding it into a conventional map call produced the same effect. I feel like I want some kind of multipluck, but I'm not sure what that corresponds to!

Update

It turns out that I was doing this correctly with the higher-order map->only approach. However, while the items in my collection were a kind of Model, they were not a subclass or compatible implementation of the Laravel Model class, and lacked an implementation of the only method. The author added the method, and now it works as expected.

Answer

Solution:

You were close, but you don't chain map and only, and only doesn't seem to work on a Collection of nested arrays/objects.

So, for your case, use map() with a Callback:

$rows = collect([
  (object)[
    'id' => 1,
    'name' => 'Sue',
    'age' => 23,
  ],
  (object)[
    'id' => 2,
    'name' => 'Joe',
    'age' => 25,
  ]
]);

$mapped = $rows->map(function ($row) {
  return ['age' => $row->age, 'name' => $row->name];
});

dd($mapped->toArray());

Output of that would be:

array:2 [?�?
  0 => array:2 [?�?
    "age" => 23
    "name" => "Sue"
  ]
  1 => array:2 [?�?
    "age" => 25
    "name" => "Joe"
  ]
]

Note: If these are arrays and not objects, then you'd do $row['age'] and $row['name'] instead of $row->age and $row->name. In Laravel, Models are both, and allow either syntax.

References:

https://laravel.com/docs/9.x/collections#method-map

https://laravel.com/docs/9.x/collections#method-only

Edit:

Some alternatives. If you have a Collection of Models, then you can natively do:

$mapped = $rows->map(function ($model) {
  return $model->only(['age', 'name']);
});

If you have a Collection of Collections, then you can do:

$mapped = $rows->map(function ($collection) {
  return $collection->only(['age', 'name']);
});

And lastly, if you arrays or objects, you can collect() and call ->only():

$mapped = $rows->map(function ($row) {
  return collect($row)->only(['age', 'name']);
});

Source