In this article
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.
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:
1$collection = collect([ 2 ['name' => 'foo'], 3 ['name' => 'bar'], 4 ['name' => 'bar'], 5]); 6 7// $result will be equal to: ['name' => 'foo'] 8$result = $collection->where('name', 'foo')->sole(); 9 10// This will throw an ItemNotFoundException11$collection->where('name', 'INVALID')->sole();12 13// This will throw a MultipleItemsFoundException14$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:
1$collection = collect([ 2 ['name' => 'foo'], 3 ['name' => 'bar'], 4 ['name' => 'bar'], 5]); 6 7// $result will be equal to: ['name' => 'foo'] 8$result = $collection->sole('name', 'foo'); 9 10// $result will be equal to: ['name' => 'foo']11$result = $collection->sole('name', '!=', 'bar');
It also supports passing closures if you need more complex filtering:
1$collection = collect([ 2 ['name' => 'foo'], 3 ['name' => 'bar'], 4 ['name' => 'bar'], 5]); 6 7// $result will be equal to: ['name' => 'foo'] 8$result = $collection->sole(function ($value) { 9 return $value['name'] === 'foo';10});11 12// $result will be equal to: ['name' => 'foo']13$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.
I'd love to hear what you think about it though in the comments. Is it a method that you think you'll use yourself? 🚀