Benjamin Crozat "What if you cut code review time & bugs in half, instantly?" Start free for 14 days→

15 Laravel Collections tips to refactor your codebase

6 minutes read

15 Laravel Collections tips to refactor your codebase

Introduction to Laravel Collections

Laravel Collections are a powerful tool for working with arrays. They wrap native PHP functions, add helpful helpers, and give you a fluent API.

In general, collections behave immutably: most methods return a new collection instead of changing the current one. Some methods do mutate the instance (for example, transform, push, pop, shift, put, prepend). When you want a non-mutating alternative, reach for map. See the docs for transform and map.

Laravel Collection methods I always use

Create a collection from an array

To create a collection from an array, use the collect() helper.

$collection = collect(); // Empty collection.

$collection = collect([1, 2, 3]); // Collection from an array.

You can also use the static constructor Collection::make(...) from Illuminate\Support\Collection:

use Illuminate\Support\Collection;

$collection = Collection::make([1, 2, 3]); // Static constructor.

Transform a collection back into an array

To turn a collection into an array, call toArray().

$collection = collect([1, 2, 3]);

$array = $collection->toArray();

This isn’t necessary most of the time, but it can come in handy.

Use collections in foreach loops

Collections work in foreach just like arrays.

foreach ($collection as $item) {
    // ...
}

You can also access the key:

foreach ($collection as $key => $value) {
    // ...
}

They are iterable because Collection implements PHP’s IteratorAggregate.

Prefer each() over foreach when you want fluency

The most basic collection use is replacing a foreach with each().

$collection = collect(['Foo', 'Bar', 'Baz']);

$collection->each(function ($value, $key) {
    // ...
});

Returning false from the callback stops the iteration early. See each.

Merge collections correctly

Merging two collections is simple with merge().

$numbers = collect([10, 20])->merge(collect([30]));
// [10, 20, 30]

$settings = collect(['theme' => 'light'])->merge(collect(['theme' => 'dark']));
// ['theme' => 'dark'] (string keys overwrite)

Behavior note: merge appends numeric-keyed values and overwrites on duplicate string keys. See merge.

Use filter() to clean up

It’s common to build a temporary array inside a loop. Collections let you skip that with filter():

return $bar->filter(function ($baz) {
    return $baz->something();
});

filter() preserves the original keys. If order or JSON output matters, chain ->values() to reset the indices:

return $bar->filter(fn ($baz) => $baz->something())
           ->values();

Use sum(), avg(), min(), and max() for numbers

Collections include handy math helpers:

$numbers = collect([1, 2, 3, 4, 5]);

$numbers->sum(); // 15
$numbers->avg(); // 3  (average() is an alias)
$numbers->min(); // 1
$numbers->max(); // 5

See avg, sum, min, and max.

Use higher order messages for concise iteration

Higher order messages (HOM) make chains shorter by calling a method on each item.

foreach (User::where('foo', 'bar')->get() as $user) {
    $user->notify(new SomeNotification);
}

Refactor with each():

User::where('foo', 'bar')
    ->get()
    ->each(function (User $user) {
        $user->notify(new SomeNotification);
    });

Then use HOM to drop the callback:

User::where('foo', 'bar')
    ->get()
    ->each
    ->notify(new SomeNotification);

Keep or drop keys with only() and except()

Need specific keys?

$original = [
    'foo' => 'foo',
    'bar' => 'bar',
    'baz' => 'baz',
];

$new = collect($original)->only(['foo', 'bar']);

Need to remove some?

$new = collect($original)->except(['foo', 'bar']);

See only and except.

Use dump() and dd() on collections

You can call these right on the collection:

// Before:
dd($collection->where('foo', 'bar'));

// After:
$collection->where('foo', 'bar')->dd();

dd() dumps and halts; dump() dumps and lets code continue. See dd and dump.

Pick random items with random()

$one = collect(['Foo', 'Bar', 'Baz'])->random();
// Returns a single item.

$two = collect(['a', 'b', 'c'])->random(2);
// Returns a Collection of 2 items.

Requesting more items than exist throws InvalidArgumentException. See random.

Replace temporary arrays with map()

This is a common pattern:

namespace App\Twitter;

use App\Twitter\Tweet;
use Illuminate\Support\Facades\Http;

class Client
{
    public function tweets()
    {
        $tweets = Http::get('https://api.twitter.com/2/users/me')
            ->json('tweets');

        $tmp = [];

        foreach ($tweets as $tweet) {
            $tmp[] = new Tweet(...$tweet);
        }

        return $tmp;
    }
}

With collections, you can skip the temporary variable:

namespace App\Twitter;

use App\Twitter\Tweet;
use Illuminate\Support\Facades\Http;

class Client
{
    public function tweets()
    {
        return Http::get('https://api.twitter.com/2/users/me')
            ->collect('tweets') // Response::collect exists on HTTP client responses
            ->map(fn (array $value) => new Tweet(...$value))
            ->all(); // Keep returning an array
    }
}

When to use: prefer map() when you want a new collection; transform() mutates the existing one.

See the HTTP client’s collect on responses.

Use isEmpty() and isNotEmpty() for readability

if ($collection->isEmpty()) {
    // Do something if the collection is empty.
}
if ($collection->isNotEmpty()) {
    // Do something if the collection is not empty.
}

Use when() and unless() to conditionally transform a collection

Collections use the Illuminate\Support\Traits\Conditionable trait, which adds when() and unless() for fluent conditional logic.

$models = Model::query()->when(
    $something,
    fn ($query) => $query->where('something', true),
    fn ($query) => $query->where('something_else', true),
)->get();

This trait also powers other parts of the framework (for example, Eloquent factories, Logger, HTTP PendingRequest, and Carbon), so you’ll see the same pattern elsewhere. See when / unless on collections.

Extend collections with your own methods

Along with other Laravel classes, collections are “macroable,” so you can extend them at runtime. Register macros in a service provider’s boot method.

app/Providers/AppServiceProvider.php:

use Illuminate\Support\Str;
use Illuminate\Support\Collection;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Collection::macro('pluralize', function () {
            return $this->map(function ($value) {
                return Str::plural($value);
            });
        });
    }
}

Now your custom method is available anywhere:

$collection = collect(['apple', 'banana', 'strawberry']);

$collection->pluralize();
// ['apples', 'bananas', 'strawberries']

Conclusion

  • Prefer non-mutating methods like map(); know when transform() changes the instance.
  • each() can replace foreach and stops early when the callback returns false.
  • only()/except(), isEmpty()/isNotEmpty(), and when()/unless() make intent clear.
  • Use higher order methods (->each->notify(...)) for clean, readable chains.
  • Remember gotchas: filter() preserves keys; merge() overwrites string keys and appends numeric keys.

Next steps: explore Eloquent collections and lazy collections to work efficiently with large datasets.


Did you like this article? Then, keep learning:

Help me reach more people by sharing this article on social media!

0 comments

Guest

Markdown is supported.

Hey, you need to sign in with your GitHub account to comment. Get started →

Great tools for developers

Search for posts and links

Try to type something…