Support the ongoing development of Laravel.io โ†’

Laravel Collections: The Artisan's Guide

16 Jan, 2023 9 min read 392 views

Introduction

Laravel Collections are really powerful for working with arrays of data. They provide a fluent, convenient interface for performing common array operations, such as filtering, formatting and transforming data. In this blog post, we'll explore some of the key features of Laravel Collections and how they can be used to simplify and streamline your code.

Creating Collections

The simplest way to create a Collection is to pass an array to the collect() method:

$collection = collect(1, 2, 3, 4, 5);

When working with Eloquent Models and the Query Builder, it's also going to return a Collection by default.

$users = User::query()
    ->where('is_active', true)
    ->get();

Filtering Data

The Laravel Collections have a lot of different methods that allows us to filter the data in our Collections. I'll show you some of the methods that can be used for this goal.

filter()

This is the most common method for filtering data in a Collection. It will remove all the items that returns false for the given callback.

$users = collect([
    ['name' => 'John Doe', 'is_active' => true],
    ['name' => 'Mary Doe', 'is_active' => true],
    ['name' => 'Peter Doe', 'is_active' => false],
]);

$filtered = $users->filter(fn ($user) => $user['is_active']);
// [
//     ['name' => 'John Doe', 'is_active' => true],
//     ['name' => 'Mary Doe', 'is_active' => true],
// ]

You can also call the filter() method without any callback. In this case, it will remove all items that are false/empty like null, false, '', 0, [].

It's important to note that this method doesn't apply any changes to the original Collection, it creates a whole new Collection with the filtered items.

where()

Like the where() method used for Eloquent queries, the where() method can be used to filter data based on a key/value.

$users = collect([
    ['name' => 'John Doe', 'age' => 15],
    ['name' => 'Mary Doe', 'age' => 20],
    ['name' => 'Peter Doe', 'age' => 30],
    ['name' => 'George Doe', 'age' => 20],
]);

$filtered = $users->where('age', 20);
// [
//     ['name' => 'Mary Doe', 'age' => 20],
//     ['name' => 'George Doe', 'age' => 20],
// ]

You can also pass a comparison operator as the second parameter, just like the Eloquent where() method.

$users = collect([
    ['name' => 'John Doe', 'age' => 15],
    ['name' => 'Mary Doe', 'age' => 20],
    ['name' => 'Peter Doe', 'age' => 30],
    ['name' => 'George Doe', 'age' => 20],
]);

$filtered = $users->where('age', '>=', 20);
// [
//     ['name' => 'Mary Doe', 'age' => 20],
//     ['name' => 'Peter Doe', 'age' => 30],
//     ['name' => 'George Doe', 'age' => 20],
// ]

Be aware that the where() method just checks if the value is the same, not the type, so 20 and '20' will be the same. If you want to check the value and the type, you can use the whereStrict() method instead.

You can also filter for multiple values using the whereIn() method and by a range of values using the whereBetween() method

$users = collect([
    ['name' => 'John Doe', 'age' => 15],
    ['name' => 'Mary Doe', 'age' => 20],
    ['name' => 'Peter Doe', 'age' => 30],
    ['name' => 'George Doe', 'age' => 20],
]);

$filtered = $users->whereIn('age', [20, 30]);
// [
//     ['name' => 'Mary Doe', 'age' => 20],
//     ['name' => 'Peter Doe', 'age' => 30],
//     ['name' => 'George Doe', 'age' => 20],
// ]
$users = collect([
    ['name' => 'John Doe', 'age' => 15],
    ['name' => 'Mary Doe', 'age' => 20],
    ['name' => 'Peter Doe', 'age' => 30],
    ['name' => 'George Doe', 'age' => 20],
]);

$filtered = $users->whereBetween('age', [15, 20]);
// [
//     ['name' => 'John Doe', 'age' => 15],
//     ['name' => 'Mary Doe', 'age' => 20],
//     ['name' => 'George Doe', 'age' => 20],
// ]

It's important to note that these methods don't apply any changes to the original Collection, they create a whole new Collection with the filtered items.

first()

This method returns the first item of the Collection that returns true for the given callback.

$users = collect([
    ['name' => 'John Doe', 'age' => 15],
    ['name' => 'Mary Doe', 'age' => 20],
    ['name' => 'Peter Doe', 'age' => 30],
]);

$filtered = $users->first(fn ($user) => $user['age'] > 18);
// ['name' => 'Mary Doe', 'age' => 20]

You can also call the first() method without any callback. In this case it will return the first item of the Collection.

$users = collect([
    ['name' => 'John Doe', 'age' => 15],
    ['name' => 'Mary Doe', 'age' => 20],
    ['name' => 'Peter Doe', 'age' => 30],
]);

$filtered = $users->first(fn ($user) => $user['age'] > 18);
// ['name' => 'John Doe', 'age' => 15]

If you want to throw an exception if no result is found, you can use the firstOrFail() method instead. In this case if no item is found it will throw an Illuminate\Support\ItemNotFoundException exception.

last()

This method returns the last item of the Collection that returns true for the given callback.

$users = collect([
    ['name' => 'John Doe', 'age' => 15],
    ['name' => 'Mary Doe', 'age' => 20],
    ['name' => 'Peter Doe', 'age' => 30],
]);

$filtered = $users->last(fn ($user) => $user['age'] > 18);
// ['name' => 'Peter Doe', 'age' => 30]

You can also call the last() method without any callback. In this case it will return the last item of the Collection.

$users = collect([
    ['name' => 'John Doe', 'age' => 15],
    ['name' => 'Mary Doe', 'age' => 20],
    ['name' => 'Peter Doe', 'age' => 30],
]);

$filtered = $users->first(fn ($user) => $user['age'] > 18);
// ['name' => 'Peter Doe', 'age' => 30]

only()

This method can be used to filter out the needed items from the Collection based on the keys.

$user = collect([
    'id' => 1,
    'name' => 'John Doe',
    'email' => '[email protected]',
    'username' => 'john_doe',
]);

$filtered = $user->only(['name', 'email']);
// ['name' => 'John Doe', 'email' => '[email protected]']

It's important to note that this method doesn't apply any changes to the original Collection, it creates a whole new Collection with the filtered items.

except()

This method can be used to filter out unwanted items from the Collection based on the keys.

$user = collect([
    'id' => 1,
    'name' => 'John Doe',
    'email' => '[email protected]',
    'username' => 'john_doe',
]);

$filtered = $user->except(['name', 'email']);
// ['id' => 1, 'username' => 'john_doe']

It's important to note that this method doesn't apply any changes to the original Collection, it creates a whole new Collection with the filtered items.

Formatting and Transforming Data

It's pretty common for applications to get some data, loop over it to transform the value and push the formatted value into a temporary variable. Instead of doing this, we can use the map() method that will perform the given callback in each item of the Collection and return a new Collection with the transformed/formatted values.

$users = collect([
    ['name' => 'John Doe', 'email' => '[email protected]', 'is_active' => true],
    ['name' => 'Mary Doe', 'email' => '[email protected]', 'is_active' => true],
    ['name' => 'Peter Doe', 'email' => '[email protected]', 'is_active' => true],
]);

$userModels = $users->map(fn ($user) => new User($user));

Debugging Collections

Sometimes we need to debug the values of our Collections and the Laravel Collections have two methods out-of-the-box for helping us with that.

dump()

This method dumps the Collection items and continues executing the script.

dd()

This method dumps the Collection items and ends the execution of the script.

Hidden Gems

Besides the methods already presented in the sections above. There are some methods that are not used that much or neither very well-known, but that can be a game changer depending on what we're working on. Below I'll list some of the methods that I think you should know.

undot()

This method can be used to transform dot-noted strings into arrays.

$data = collect([
    'user.first_name' => 'John',
    'user.last_name' => 'Doe',
    'user.email' => '[email protected]',
    'user.social.twitter' => '@john_doe',
    'user.social.github' => 'JohnDoe',
]);

$user = $data->undot();
// [
//     "user" => [
//         "first_name" => "John",
//         "last_name" => "Doe",
//         "email" => "[email protected]",
//         "social" => [
//             "twitter" => '@john_doe',
//             "github" => 'JohnDoe',
//         ],
//     ],
// ]

partition()

This method separates the Collection items in two different ones based on the given callback. The items that return true for the given callback will be pushed into the first Collection and the ones returning false will be pushed into the second one.

$users = collect([
    ['name' => 'John Doe', 'is_active' => true],
    ['name' => 'Mary Doe', 'is_active' => false],
    ['name' => 'Peter Doe', 'is_active' => true],
]);

[$activeUsers, $inactiveUsers] = $users->partition(fn ($user) => $user['is_active']);

$activeUsers->all();
// [
//     ['name' => 'John Doe', 'is_active' => true],
//     ['name' => 'Peter Doe', 'is_active' => true],
// ]

$inactiveUsers->all();
// [
//     ['name' => 'Mary Doe', 'is_active' => false],
// ]

reject()

This is the opposite of the filter() method, but it's not that well-known. It will remove all the items that returns true for the given callback.

$users = collect([
    ['name' => 'John Doe', 'is_active' => true],
    ['name' => 'Mary Doe', 'is_active' => true],
    ['name' => 'Peter Doe', 'is_active' => false],
]);

$filtered = $users->reject(fn ($user) => $user['is_active']);
// [
//     ['name' => 'Peter Doe', 'is_active' => false],
// ]

random()

This method returns a random item from the Collection.

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

$data->random();
// 3 - randomly selected

If you want more than one item retrieved you can pass an integer as argument.

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

$data->random(2);
// [2, 3] - randomly selected

isEmpty()

This method returns true if the Collection has no items or false if it has at least one item.

collect([])->isEmpty(); // true
collect([1])->isEmpty(); // false

isNotEmpty()

This method is the opposite of the isEmpty() method. It will return true if the Collection has at least one item and false if it doesn't have any items on it.

collect([])->isNotEmpty(); // false
collect([1])->isNotEmpty(); // true

High Order Messages

High Order Messages are like shortcuts that we can use to apply common actions in our Collections. The methods that support this feature are: average(), avg(), contains(), each(), every(), filter(), first(), flatMap(), groupBy(), keyBy(), map(), max(), min(), partition(), reject(), skipUntil(), skipWhile(), some(), sortBy(), sortByDesc(), sum(), takeUntil(), takeWhile(), and unique().

Imagine that you need to send a Newsletter to all your subscribed users. With the use of this feature you can do something like this.

User::query()
    ->where('receive_newsletter', true)
    ->each
    ->sendNewsletter();

Lazy Collections

Collections are really powerful and LazyCollections extend the power of them by using generators, so we can work with large datasets but keeping the memory usage low.

Imagine that we have millions of products in our database, and we need to perform some actions on them, we can use the cursor() method from Eloquent/Query Builder to return a LazyCollection instead of a normal Collection.

$payments = Payment::query()
    ->where('is_accepted', true)
    ->cursor()
    ->map(fn ($payment) => $this->formatPaymentData($payment));

foreach ($payments as $payment) {
    // LOGIC HERE
}

In the example above a single query will be run against the database, but it will also keep only a single item in memory at a time. Also, the map() callback won't be executed right away, but only when we iterate over each single item in the foreach statement.

Creating Your Own Collection Methods

Besides all the amazing methods that the Collections already have out-of-the-box, we can create our own methods since they are "macroable". This means that we can use the macro() method to extend them with our own methods. To do that we need to add our new custom methods in the boot() method of the AppServiceProvider class.

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

Conclusion

These are just a few examples of the many powerful methods available on Laravel Collections. By using these methods, you can easily and efficiently manipulate your data, making your code more readable and maintainable.

I hope that you liked this article and if you do, donโ€™t forget to share this article with your friends!!! See ya! :wink:

Last updated 3 weeks ago.
3
Like this article? Let the author know and give them a clap!
wendell_adriel (Wendell Adriel) Web Artisan specialized in PHP/Laravel ๐Ÿ˜Ž Open Source Enthusiast ๐Ÿ”ฅ I help you to level up your skills ๐Ÿ’ช 13+ yrs of XP in Web-Dev ๐Ÿค˜ Mentored dozens of Devs ๐ŸŽ“

Other articles you might like

January 16th 2023

API Versioning in Laravel

Introduction A lot of companies, especially SaaS ones use this technique in their applications and t...

Read article
January 13th 2023

User Levels, Enums and Policies, oh my!

Users are not equal. You heard me. Some users are above others. They are allowed to do more than oth...

Read article
January 23rd 2023

Laravel AaaS - Actions as a Service

Introduction Laravel is an amazing framework. We can build products really quick with all the featur...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2023 Laravel.io - All rights reserved.