Sitemap

Laravel: How the AsCollection::of() saves tons of lines

A simple PR will save dozens of unnecessary keystrokes

3 min readMay 1, 2025

One great thing about Eloquent Mutators included with Laravel is the AsCollection class. By adding it as a cast to a Model attribute, we can quickly retrieve a list of items as a Collection instead of a plain array.

The problem with that are the items themselves. When having a list of simple scalar items like booleans, strings or even other arrays, there is not much to do. If your items should be object instances of a determined class, that’s when the problem arises.

What we had to do until now

Let’s imagine we have a list of Cars. Each item in the list is another list with the Car brand, model and color. This list is persisted into each “Driver” model.

use App\Models\Driver;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Casts\AsCollection;
use Illuminate\Database\Eloquent\Model;

class Driver extends Model
{
public function casts()
{
return ['cars' => AsCollection::class];
}
}

$driver = Driver::find(1)

$driver->cars = Collection::make([
['brand' => 'Porsche', 'model' => 'Cayman', 'color' => 'red'],
['brand' => 'Hyundai', 'model' => 'Elantra', 'color' => 'gray'],
['brand' => 'Nissan', 'model' => 'Z', 'color' => 'yellow'],
]);

The problem is that we need each item to be mapped into a determined Car class, back and forth the model. Trying make this work will require two alternatives: creating our own cast method, or use our own Collection instance.

// Using a Attribute Cast Method

use App\ValueObjects\Car;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Collection;

class Driver extends Model
{
protected function cars(): Attribute
{
return Attribute::make(
get: fn($value) => Collection::make($value)->mapInto(Car::class),
set: fn($value) => json_encode($value->toJson),
);
}
}
use App\ValueObjects\Car;
use Illuminate\Database\Eloquent\Casts\AsCollection;
use Illuminate\Support\Collection;

class CarCollection extends Collection
{
public function __construct($items = [])
{
parent::__construct(
array_map(fn($value) => new Car($value), $items)
);
}
}

class Driver extends Model
{
public function casts()
{
return ['cars' => AsCollection::using(CarCollection::class)];
}
}

Both of these solutions are overkill just to map each item into an object instance. That’s why I pushed a simple PR to Laravel to do just that with the AsCollection cast.

AsCollection with object instances

The AsCollection class got a new static method called of(). It accepts two types of arguments that differ on how each item would be created.

  • Issuing a class name will use the mapInto() method of the Collection to create new instances based on the raw item value.
  • Issuing a callable as an array will use the map() method of the Collection, returning what the callable does as an Collection item.

The first is easy to understand. For example, our Car class will receive the decoded array and create an instance from it.

class Car
{
public string $brand;
public string $model;
public string $color;

public function __construct(array $data)
{
$this->model = $data['brand'];
$this->model = $data['model'];
$this->model = $data['color'];
}
}

class Driver extends Model
{
public function casts()
{
return ['cars' => AsCollection::of(Car::class)];
}
}

This may suffice for simple classes, but if you require special treatment on how the Item should be constructed, then the callback is a great way to do it. In this case, we can create a static method on the Car class and call it for each item.

For example, let’s say our Car class is not constructed with an array, but using each attribute separately, since it’s also done in that way across the application. Instancing a class won’t work since mapInto will push only the array and the numeric key as argument to instance the class. In that case, we can use a method called fromArray() that takes care of it.

class Car
{
public function __construct(
public string $brand;
public string $model;
public string $color;
)
{
//
}

public static function fromArray(array $data, int $key): static
{
return new static($data['brand'], $data['model'], $data['color']);
}
}

class Driver extends Model
{
public function casts()
{
return ['cars' => AsCollection::of([Car::class, 'fromArray'])];
}
}

Both approaches are fine and will depend on the application needs.

Personally, I like more adding a fromArray() static method to the classes I need to instance from an Item array, but that’s my preference and may sound too complicated for simple objects with a few values, especially if your items are based on Illuminate\Support\Fluent instances.

--

--

Italo Baeza Cabrera
Italo Baeza Cabrera

Written by Italo Baeza Cabrera

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com

No responses yet