diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index b6fa288d7a1b..7f40c863b465 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1130,6 +1130,31 @@ public function sole($key = null, $operator = null, $value = null) return $items->first(); } + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Illuminate\Collections\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + $items = $this->when($filter)->filter($filter); + + if ($items->isEmpty()) { + throw new ItemNotFoundException; + } + + return $items->first(); + } + /** * Chunk the collection into chunks of the given size. * diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index a5da57e9bd3d..1a634c1866c2 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1074,6 +1074,30 @@ public function sole($key = null, $operator = null, $value = null) ->sole(); } + /** + * Get the first item in the collection but throw an exception if no matching items exist. + * + * @param mixed $key + * @param mixed $operator + * @param mixed $value + * @return mixed + * + * @throws \Illuminate\Collections\ItemNotFoundException + */ + public function firstOrFail($key = null, $operator = null, $value = null) + { + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->when($filter) + ->filter($filter) + ->take(1) + ->collect() + ->firstOrFail(); + } + /** * Chunk the collection into chunks of the given size. * diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index aa689d054310..3f7dbd8b34d0 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -86,7 +86,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) /** * @dataProvider collectionClassProvider */ - public function testSoleThrowsExceptionIfNoItemsExists($collection) + public function testSoleThrowsExceptionIfNoItemsExist($collection) { $this->expectException(ItemNotFoundException::class); @@ -129,7 +129,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback( /** * @dataProvider collectionClassProvider */ - public function testSoleThrowsExceptionIfNoItemsExistsWithCallback($collection) + public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection) { $this->expectException(ItemNotFoundException::class); @@ -154,6 +154,91 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($coll }); } + /** + * @dataProvider collectionClassProvider + */ + public function testFirstOrFailReturnsFirstItemInCollection($collection) + { + $collection = new $collection([ + ['name' => 'foo'], + ['name' => 'bar'], + ]); + + $this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->firstOrFail()); + $this->assertSame(['name' => 'foo'], $collection->firstOrFail('name', '=', 'foo')); + $this->assertSame(['name' => 'foo'], $collection->firstOrFail('name', 'foo')); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection) + { + $this->expectException(ItemNotFoundException::class); + + $collection = new $collection([ + ['name' => 'foo'], + ['name' => 'bar'], + ]); + + $collection->where('name', 'INVALID')->firstOrFail(); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExists($collection) + { + $collection = new $collection([ + ['name' => 'foo'], + ['name' => 'foo'], + ['name' => 'bar'], + ]); + + $this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->firstOrFail()); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection) + { + $data = new $collection(['foo', 'bar', 'baz']); + $result = $data->firstOrFail(function ($value) { + return $value === 'bar'; + }); + $this->assertSame('bar', $result); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collection) + { + $this->expectException(ItemNotFoundException::class); + + $data = new $collection(['foo', 'bar', 'baz']); + + $data->firstOrFail(function ($value) { + return $value === 'invalid'; + }); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCallback($collection) + { + $data = new $collection(['foo', 'bar', 'bar']); + + $this->assertSame( + 'bar', + $data->firstOrFail(function ($value) { + return $value === 'bar'; + }) + ); + } + /** * @dataProvider collectionClassProvider */ diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php index e88f95618bd6..2d21e54bc26c 100644 --- a/tests/Support/SupportLazyCollectionIsLazyTest.php +++ b/tests/Support/SupportLazyCollectionIsLazyTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Support; +use Illuminate\Support\ItemNotFoundException; use Illuminate\Support\LazyCollection; use Illuminate\Support\MultipleItemsFoundException; use PHPUnit\Framework\TestCase; @@ -986,6 +987,35 @@ public function testSliceIsLazy() }); } + public function testFindFirstOrFailIsLazy() + { + $this->assertEnumerates(1, function ($collection) { + $collection->firstOrFail(); + }); + + $this->assertEnumerates(1, function ($collection) { + $collection->firstOrFail(function ($item) { + return $item === 1; + }); + }); + + $this->assertEnumerates(100, function ($collection) { + try { + $collection->firstOrFail(function ($item) { + return $item === 101; + }); + } catch (ItemNotFoundException $e) { + // + } + }); + + $this->assertEnumerates(2, function ($collection) { + $collection->firstOrFail(function ($item) { + return $item % 2 === 0; + }); + }); + } + public function testSomeIsLazy() { $this->assertEnumerates(5, function ($collection) {