From 2999a0111f73163623dd3d29b1476a612364e22e Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Tue, 17 Aug 2021 20:46:43 +0100 Subject: [PATCH 01/13] Add firstOrFail method to Collections and LazyCollections --- src/Illuminate/Collections/Collection.php | 25 ++++ src/Illuminate/Collections/LazyCollection.php | 24 ++++ tests/Support/SupportCollectionTest.php | 114 +++++++++++++++--- 3 files changed, 148 insertions(+), 15 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index a58bd7518f9f..f1b2fc1d7316 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1132,6 +1132,31 @@ public function sole($key = null, $operator = null, $value = null) return $items->first(); } + /** + * Get the first item in the collection, but if no items exist throw an exception. + * + * @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 3ae549c154ac..bd2a761ef365 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 if no items exist throw an exception. + * + * @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(2) + ->collect() + ->firstOrFail(); + } + /** * Chunk the collection into chunks of the given size. * diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 02bb6cf8099c..5db079bdc571 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -68,21 +68,6 @@ public function testFirstWithDefaultAndWithoutCallback($collection) $this->assertSame('default', $result); } - /** - * @dataProvider collectionClassProvider - */ - public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) - { - $collection = new $collection([ - ['name' => 'foo'], - ['name' => 'bar'], - ]); - - $this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->sole()); - $this->assertSame(['name' => 'foo'], $collection->sole('name', '=', 'foo')); - $this->assertSame(['name' => 'foo'], $collection->sole('name', 'foo')); - } - /** * @dataProvider collectionClassProvider */ @@ -154,6 +139,105 @@ 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 testFirstOrFailThrowsExceptionIfNoItemsExists($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 testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) + { + $collection = new $collection([ + ['name' => 'foo'], + ['name' => 'bar'], + ]); + + $this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->sole()); + $this->assertSame(['name' => 'foo'], $collection->sole('name', '=', 'foo')); + $this->assertSame(['name' => 'foo'], $collection->sole('name', 'foo')); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testFirstOrFailThrowsExceptionIfNoItemsExistsWithCallback($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 */ From ff9db099cbc35731d16cbac7f1ca074762715eaf Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Tue, 17 Aug 2021 20:49:57 +0100 Subject: [PATCH 02/13] Restore test that got moved, fixed some grammaticals in test names --- tests/Support/SupportCollectionTest.php | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 5db079bdc571..cf1b7282422e 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -71,7 +71,22 @@ public function testFirstWithDefaultAndWithoutCallback($collection) /** * @dataProvider collectionClassProvider */ - public function testSoleThrowsExceptionIfNoItemsExists($collection) + public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) + { + $collection = new $collection([ + ['name' => 'foo'], + ['name' => 'bar'], + ]); + + $this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->sole()); + $this->assertSame(['name' => 'foo'], $collection->sole('name', '=', 'foo')); + $this->assertSame(['name' => 'foo'], $collection->sole('name', 'foo')); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testSoleThrowsExceptionIfNoItemsExist($collection) { $this->expectException(ItemNotFoundException::class); @@ -114,7 +129,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback( /** * @dataProvider collectionClassProvider */ - public function testSoleThrowsExceptionIfNoItemsExistsWithCallback($collection) + public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection) { $this->expectException(ItemNotFoundException::class); @@ -157,7 +172,7 @@ public function testFirstOrFailReturnsFirstItemInCollection($collection) /** * @dataProvider collectionClassProvider */ - public function testFirstOrFailThrowsExceptionIfNoItemsExists($collection) + public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection) { $this->expectException(ItemNotFoundException::class); @@ -198,22 +213,7 @@ public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCa /** * @dataProvider collectionClassProvider */ - public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) - { - $collection = new $collection([ - ['name' => 'foo'], - ['name' => 'bar'], - ]); - - $this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->sole()); - $this->assertSame(['name' => 'foo'], $collection->sole('name', '=', 'foo')); - $this->assertSame(['name' => 'foo'], $collection->sole('name', 'foo')); - } - - /** - * @dataProvider collectionClassProvider - */ - public function testFirstOrFailThrowsExceptionIfNoItemsExistsWithCallback($collection) + public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collection) { $this->expectException(ItemNotFoundException::class); From a8ebd116b2b8670299c8351bd5a415303159564a Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Tue, 17 Aug 2021 21:05:11 +0100 Subject: [PATCH 03/13] Style fixes --- tests/Support/SupportCollectionTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index cf1b7282422e..98becf77194a 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -238,6 +238,7 @@ public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCa }) ); } + /** * @dataProvider collectionClassProvider */ From 03fe61460e14b3cd8093c52f1bc96a92c776bf53 Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Wed, 18 Aug 2021 08:13:48 +0100 Subject: [PATCH 04/13] Make take(2) as take(1) instead --- src/Illuminate/Collections/LazyCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index bd2a761ef365..f4a0a2ba3668 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1093,7 +1093,7 @@ public function firstOrFail($key = null, $operator = null, $value = null) return $this ->when($filter) ->filter($filter) - ->take(2) + ->take(1) ->collect() ->firstOrFail(); } From 5d9bdacb38b15c53d0fd6b5f32da7f99b3603eb9 Mon Sep 17 00:00:00 2001 From: powellblyth Date: Wed, 18 Aug 2021 15:36:02 +0100 Subject: [PATCH 05/13] use unless instead of when Per Joseph Silber's suggestion Co-authored-by: Joseph Silber --- src/Illuminate/Collections/LazyCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index f4a0a2ba3668..da880772b694 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1091,7 +1091,7 @@ public function firstOrFail($key = null, $operator = null, $value = null) : $key; return $this - ->when($filter) + ->unless($filter == null) ->filter($filter) ->take(1) ->collect() From 55271b00d1616a39f6d1c7f00c4ca212176a1cce Mon Sep 17 00:00:00 2001 From: powellblyth Date: Wed, 18 Aug 2021 15:36:23 +0100 Subject: [PATCH 06/13] Use Unless instead of When Per Joseph Silber's suggestion Co-authored-by: Joseph Silber --- src/Illuminate/Collections/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index f1b2fc1d7316..038e418321c5 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1148,7 +1148,7 @@ public function firstOrFail($key = null, $operator = null, $value = null) ? $this->operatorForWhere(...func_get_args()) : $key; - $items = $this->when($filter)->filter($filter); + $items = $this->unless($filter == null)->filter($filter); if ($items->isEmpty()) { throw new ItemNotFoundException; From df2226dfd68d2f9da44cafefd571244554185f33 Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Wed, 18 Aug 2021 22:22:36 +0100 Subject: [PATCH 07/13] Revert L9 changes, add test requested by @JosephSilber --- src/Illuminate/Collections/Collection.php | 2 +- src/Illuminate/Collections/LazyCollection.php | 2 +- .../SupportLazyCollectionIsLazyTest.php | 28 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 038e418321c5..f1b2fc1d7316 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1148,7 +1148,7 @@ public function firstOrFail($key = null, $operator = null, $value = null) ? $this->operatorForWhere(...func_get_args()) : $key; - $items = $this->unless($filter == null)->filter($filter); + $items = $this->when($filter)->filter($filter); if ($items->isEmpty()) { throw new ItemNotFoundException; diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index da880772b694..f4a0a2ba3668 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1091,7 +1091,7 @@ public function firstOrFail($key = null, $operator = null, $value = null) : $key; return $this - ->unless($filter == null) + ->when($filter) ->filter($filter) ->take(1) ->collect() diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php index 54d8ea60cae7..a132a8da3713 100644 --- a/tests/Support/SupportLazyCollectionIsLazyTest.php +++ b/tests/Support/SupportLazyCollectionIsLazyTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Support; +use Illuminate\Collections\ItemNotFoundException; use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Support\LazyCollection; use PHPUnit\Framework\TestCase; @@ -986,6 +987,33 @@ public function testSliceIsLazy() }); } + public function testFindFirstOrFailIsLazy() + { + $this->assertEnumerates(1, function ($collection) { + try { + $collection->firstOrFail(); + } catch (ItemNotFoundException $e) { + // + } + }); + + $this->assertEnumerates(1, function ($collection) { + $collection->firstOrFail(function ($item) { + return $item === 1; + }); + }); + + $this->assertEnumerates(2, function ($collection) { + try { + $collection->firstOrFail(function ($item) { + return $item % 2 === 0; + }); + } catch (ItemNotFoundException $e) { + // + } + }); + } + public function testSomeIsLazy() { $this->assertEnumerates(5, function ($collection) { From 6efef00758b35413072bf6c2f86a83e53765235c Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Thu, 19 Aug 2021 08:58:34 +0100 Subject: [PATCH 08/13] Remove unneeded try catch --- .../Support/SupportLazyCollectionIsLazyTest.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php index a132a8da3713..defb92893749 100644 --- a/tests/Support/SupportLazyCollectionIsLazyTest.php +++ b/tests/Support/SupportLazyCollectionIsLazyTest.php @@ -990,11 +990,7 @@ public function testSliceIsLazy() public function testFindFirstOrFailIsLazy() { $this->assertEnumerates(1, function ($collection) { - try { - $collection->firstOrFail(); - } catch (ItemNotFoundException $e) { - // - } + $collection->firstOrFail(); }); $this->assertEnumerates(1, function ($collection) { @@ -1004,13 +1000,9 @@ public function testFindFirstOrFailIsLazy() }); $this->assertEnumerates(2, function ($collection) { - try { - $collection->firstOrFail(function ($item) { - return $item % 2 === 0; - }); - } catch (ItemNotFoundException $e) { - // - } + $collection->firstOrFail(function ($item) { + return $item % 2 === 0; + }); }); } From 32c64172c1077598327553f06d7827b851cf200c Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Thu, 19 Aug 2021 09:00:01 +0100 Subject: [PATCH 09/13] Remove unneeded include --- tests/Support/SupportLazyCollectionIsLazyTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php index defb92893749..f241755aa03d 100644 --- a/tests/Support/SupportLazyCollectionIsLazyTest.php +++ b/tests/Support/SupportLazyCollectionIsLazyTest.php @@ -2,7 +2,6 @@ namespace Illuminate\Tests\Support; -use Illuminate\Collections\ItemNotFoundException; use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Support\LazyCollection; use PHPUnit\Framework\TestCase; From d3994b6342e9535c38be4ed471869b38a2afba57 Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Thu, 19 Aug 2021 09:12:08 +0100 Subject: [PATCH 10/13] Test that when an enumeration does not contain an item, that firstOrFail enumerates the full collection --- tests/Support/SupportLazyCollectionIsLazyTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php index f241755aa03d..dfe4c6ed1778 100644 --- a/tests/Support/SupportLazyCollectionIsLazyTest.php +++ b/tests/Support/SupportLazyCollectionIsLazyTest.php @@ -2,7 +2,9 @@ namespace Illuminate\Tests\Support; +use Illuminate\Collections\ItemNotFoundException; use Illuminate\Collections\MultipleItemsFoundException; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\LazyCollection; use PHPUnit\Framework\TestCase; use stdClass; @@ -998,6 +1000,16 @@ public function testFindFirstOrFailIsLazy() }); }); + $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; From 178218c5c14f7dde7ecafd26d3b57b819959ffe5 Mon Sep 17 00:00:00 2001 From: Toby Powell-Blyth Date: Thu, 19 Aug 2021 09:13:49 +0100 Subject: [PATCH 11/13] Style fix --- tests/Support/SupportLazyCollectionIsLazyTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php index dfe4c6ed1778..e226814d0b29 100644 --- a/tests/Support/SupportLazyCollectionIsLazyTest.php +++ b/tests/Support/SupportLazyCollectionIsLazyTest.php @@ -4,7 +4,6 @@ use Illuminate\Collections\ItemNotFoundException; use Illuminate\Collections\MultipleItemsFoundException; -use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\LazyCollection; use PHPUnit\Framework\TestCase; use stdClass; From 15254867c6f1e7c55044ae54f8954d982a803359 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 19 Aug 2021 09:04:47 -0500 Subject: [PATCH 12/13] Update Collection.php --- src/Illuminate/Collections/Collection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index a9c9a17d2fe6..7f40c863b465 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1131,7 +1131,7 @@ public function sole($key = null, $operator = null, $value = null) } /** - * Get the first item in the collection, but if no items exist throw an exception. + * Get the first item in the collection but throw an exception if no matching items exist. * * @param mixed $key * @param mixed $operator From f87195afec3a2f23512af6759f7c2ba29bf2630b Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 19 Aug 2021 09:05:09 -0500 Subject: [PATCH 13/13] Update LazyCollection.php --- src/Illuminate/Collections/LazyCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index d808352db232..1a634c1866c2 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1075,7 +1075,7 @@ public function sole($key = null, $operator = null, $value = null) } /** - * Get the first item in the collection, but if no items exist throw an exception. + * Get the first item in the collection but throw an exception if no matching items exist. * * @param mixed $key * @param mixed $operator