How to Use the sole() Method on Laravel Collections

How to Use the sole() Method on Laravel Collections

In this post we are using original post by Ashley Allen from ashallendesign website and we have pointed some misleading information by original writer and tried to make mistakes clear.

Introduction

In the Laravel 8.23 release back in January 2021, a sole() method was added to the query builder. This method basically returns the first (and only) record that matches the criteria. If more than one record were found that matched the criteria, a MultipleRecordsFoundException would be thrown. And if no records matched the criteria, a RecordsNotFoundException would be thrown.

I personally find this method really useful because it helps to give me extra piece of mind over the data in my database. Sometimes you might want to make a call to a database table using the first() method and make the assumption that it's returned the only row that matched your criteria. But, without using some kind of check (such as count()) before you use first(), you won't be sure if it's fetched the first row (of many rows) or the only row.

Please note that for finding only one row in database regardless of how many rows you have in your table using where() and first() works just fine, here first is pointed to your where() condition and not to table rows so there will be no mistake by query, also you have always option of findOrFail() if you concern about using first() method. Therefore there is nothing to worry about using first() method. Here is some samples for you:

$post = Post::where('id', $id)->first();

// or

$post = Post::findOrFail($id);

// even you can use

$post = Post::whereId($id);

All code above return the same (correct) results regardless of how many rows you have in your table (no count() method needed).

Therefore, this is where the sole() method can be used to make the assertion for you that only one row actually matched your criteria.

Using sole() on Collections

I'm a fan of using the sole() method in the query builder, so I thought that this functionality would be useful to use inside Collections as well. I noticed that this method hadn't already been added, so I made a PR to Laravel with the addition. After some help from Joseph Silber (massive thank you!), the PR got merged in and added to the 8.39 release.

Under the hood, the sole() method for Collections works in a similar way to the how it was implemented for the query builder.

To give a bit of context, let's take this example:

$collection = collect([
    ['name' => 'foo'],
    ['name' => 'bar'],
    ['name' => 'bar'],
]);

// $result will be equal to: ['name' => 'foo']
$result = $collection->where('name', 'foo')->sole();

// This will throw an ItemNotFoundException
$collection->where('name', 'INVALID')->sole();

// This will throw a MultipleItemsFoundException
$collection->where('name', 'bar')->sole();

As you can see, if only record in the collection matches the criteria, it will be returned. If no records match the criteria, an Illuminate\Collections\ItemNotFoundException exception will be thrown. If multiple items match the criteria, an Illuminate\Collections\MultipleItemsFoundException will be thrown.

This method also has operator support like in the example below:

$collection = collect([
    ['name' => 'foo'],
    ['name' => 'bar'],
    ['name' => 'bar'],
]);

// $result will be equal to: ['name' => 'foo']
$result = $collection->sole('name', 'foo');

// $result will be equal to: ['name' => 'foo']
$result = $collection->sole('name', '!=', 'bar');

It also supports passing closures if you need more complex filtering:

$collection = collect([
    ['name' => 'foo'],
    ['name' => 'bar'],
    ['name' => 'bar'],
]);

// $result will be equal to: ['name' => 'foo']
$result = $collection->sole(function ($value) {
    return $value['name'] === 'foo';
});

// $result will be equal to: ['name' => 'foo']
$result = $collection->sole(fn($value) => $value['name'] === 'foo');

In my personal opinion, I think that this method will be pretty useful. I think that it will come in handy in places where collections are being built up in a complex way (such as processing many smaller collections and then merging them together). If you want to read a little bit more about Collections or the the new method, check out the documentation here.

At the end I would like to thank Ashley Allen for his interesting article and mention that all "I" letters in tutorial are on behalf of Ashley and not me.
Hope you enjoyed this article and learnt new things.

- Last updated 4 months ago

Be the first to leave a comment.

You must login to leave a comment