From 1aef4296931b70cc618b785a8e5da3cb2c908fd6 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Sun, 18 Apr 2021 02:16:24 +0100 Subject: [PATCH 01/15] Added the 'sole' method for Collections. --- src/Illuminate/Collections/Enumerable.php | 13 ++++++ .../Collections/ItemNotFoundException.php | 10 ++++ .../MultipleItemsFoundException.php | 10 ++++ .../Collections/Traits/EnumeratesValues.php | 24 ++++++++++ tests/Support/SupportCollectionTest.php | 46 +++++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 src/Illuminate/Collections/ItemNotFoundException.php create mode 100644 src/Illuminate/Collections/MultipleItemsFoundException.php diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 4bda35476905..451e23d6b8f6 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -3,6 +3,8 @@ namespace Illuminate\Support; use Countable; +use Illuminate\Collections\ItemNotFoundException; +use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; use IteratorAggregate; @@ -441,6 +443,17 @@ public function first(callable $callback = null, $default = null); */ public function firstWhere($key, $operator = null, $value = null); + /** + * Get the first item in the collection, but only if exactly + * item exists. Otherwise, throw an exception. + * + * @return mixed + * + * @throws ItemNotFoundException + * @throws MultipleItemsFoundException + */ + public function sole(); + /** * Get a flattened array of the items in the collection. * diff --git a/src/Illuminate/Collections/ItemNotFoundException.php b/src/Illuminate/Collections/ItemNotFoundException.php new file mode 100644 index 000000000000..b41b52b06669 --- /dev/null +++ b/src/Illuminate/Collections/ItemNotFoundException.php @@ -0,0 +1,10 @@ +first($this->operatorForWhere(...func_get_args())); } + /** + * Get the first item in the collection, but only if exactly + * item exists. Otherwise, throw an exception. + * + * @return mixed + * + * @throws ItemNotFoundException + * @throws MultipleItemsFoundException + */ + public function sole() + { + if ($this->isEmpty()) { + throw new ItemNotFoundException; + } + + if ($this->count() > 1) { + throw new MultipleItemsFoundException(); + } + + return $this->first(); + } + /** * Determine if the collection is not empty. * diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 3c09a0018def..8a190122afcc 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -7,6 +7,8 @@ use ArrayObject; use CachingIterator; use Exception; +use Illuminate\Collections\ItemNotFoundException; +use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; use Illuminate\Support\Collection; @@ -32,6 +34,50 @@ public function testFirstReturnsFirstItemInCollection($collection) $this->assertSame('foo', $c->first()); } + /** + * @dataProvider collectionClassProvider + */ + public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) + { + $collection = new $collection([ + ['name' => 'foo'], + ['name' => 'bar'], + ]); + + $this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->sole()); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testSoleThrowsExceptionIfNoItemsExists($collection) + { + $this->expectException(ItemNotFoundException::class); + + $collection = new $collection([ + ['name' => 'foo'], + ['name' => 'bar'], + ]); + + $collection->where('name', 'INVALID')->sole(); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection) + { + $this->expectException(MultipleItemsFoundException::class); + + $collection = new $collection([ + ['name' => 'foo'], + ['name' => 'foo'], + ['name' => 'bar'], + ]); + + $collection->where('name', 'foo')->sole(); + } + /** * @dataProvider collectionClassProvider */ From af6270dcef3d09b6a418aebc8e3e0467a7f7478c Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Sun, 18 Apr 2021 02:30:17 +0100 Subject: [PATCH 02/15] Style updates. --- src/Illuminate/Collections/Traits/EnumeratesValues.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 462ec3032fcf..45f82170e20d 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -310,7 +310,7 @@ public function sole() } if ($this->count() > 1) { - throw new MultipleItemsFoundException(); + throw new MultipleItemsFoundException; } return $this->first(); From d9c21db12bd7dec9af9a172b70ebc95181d6c0be Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Sun, 18 Apr 2021 02:31:04 +0100 Subject: [PATCH 03/15] Moved tests. --- tests/Support/SupportCollectionTest.php | 68 ++++++++++++------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 8a190122afcc..5277f3baf775 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -34,6 +34,40 @@ public function testFirstReturnsFirstItemInCollection($collection) $this->assertSame('foo', $c->first()); } + /** + * @dataProvider collectionClassProvider + */ + public function testFirstWithCallback($collection) + { + $data = new $collection(['foo', 'bar', 'baz']); + $result = $data->first(function ($value) { + return $value === 'bar'; + }); + $this->assertSame('bar', $result); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testFirstWithCallbackAndDefault($collection) + { + $data = new $collection(['foo', 'bar']); + $result = $data->first(function ($value) { + return $value === 'baz'; + }, 'default'); + $this->assertSame('default', $result); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testFirstWithDefaultAndWithoutCallback($collection) + { + $data = new $collection; + $result = $data->first(null, 'default'); + $this->assertSame('default', $result); + } + /** * @dataProvider collectionClassProvider */ @@ -78,40 +112,6 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection) $collection->where('name', 'foo')->sole(); } - /** - * @dataProvider collectionClassProvider - */ - public function testFirstWithCallback($collection) - { - $data = new $collection(['foo', 'bar', 'baz']); - $result = $data->first(function ($value) { - return $value === 'bar'; - }); - $this->assertSame('bar', $result); - } - - /** - * @dataProvider collectionClassProvider - */ - public function testFirstWithCallbackAndDefault($collection) - { - $data = new $collection(['foo', 'bar']); - $result = $data->first(function ($value) { - return $value === 'baz'; - }, 'default'); - $this->assertSame('default', $result); - } - - /** - * @dataProvider collectionClassProvider - */ - public function testFirstWithDefaultAndWithoutCallback($collection) - { - $data = new $collection; - $result = $data->first(null, 'default'); - $this->assertSame('default', $result); - } - /** * @dataProvider collectionClassProvider */ From 034bad1c1f374d52900c6df8dcafac53303d9f54 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Sun, 18 Apr 2021 19:37:24 +0100 Subject: [PATCH 04/15] Updated sole() to work with callbacks. --- src/Illuminate/Collections/Collection.php | 27 +++++++++++ src/Illuminate/Collections/Enumerable.php | 23 ++++----- src/Illuminate/Collections/LazyCollection.php | 48 +++++++++++++++++++ .../Collections/Traits/EnumeratesValues.php | 22 --------- 4 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 68fb3cecc100..ce73cfb7a3ae 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -4,6 +4,8 @@ use ArrayAccess; use ArrayIterator; +use Illuminate\Collections\ItemNotFoundException; +use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Support\Traits\EnumeratesValues; use Illuminate\Support\Traits\Macroable; use stdClass; @@ -1050,6 +1052,31 @@ public function splitIn($numberOfGroups) return $this->chunk(ceil($this->count() / $numberOfGroups)); } + /** + * Get the first item in the collection, but only if exactly + * item exists. Otherwise, throw an exception. + * + * @param callable|null $callback + * @return mixed + * + * @throws ItemNotFoundException + * @throws MultipleItemsFoundException + */ + public function sole(callable $callback = null) + { + $items = $this->filter($callback); + + if ($items->isEmpty()) { + throw new ItemNotFoundException; + } + + if ($items->count() > 1) { + throw new MultipleItemsFoundException; + } + + return $items->first(); + } + /** * Chunk the collection into chunks of the given size. * diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 451e23d6b8f6..9f146debdaa0 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -443,17 +443,6 @@ public function first(callable $callback = null, $default = null); */ public function firstWhere($key, $operator = null, $value = null); - /** - * Get the first item in the collection, but only if exactly - * item exists. Otherwise, throw an exception. - * - * @return mixed - * - * @throws ItemNotFoundException - * @throws MultipleItemsFoundException - */ - public function sole(); - /** * Get a flattened array of the items in the collection. * @@ -821,6 +810,18 @@ public function slice($offset, $length = null); */ public function split($numberOfGroups); + /** + * Get the first item in the collection, but only if exactly + * item exists. Otherwise, throw an exception. + * + * @param callable|null $callback + * @return mixed + * + * @throws ItemNotFoundException + * @throws MultipleItemsFoundException + */ + public function sole(callable $callback = null); + /** * Chunk the collection into chunks of the given size. * diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index fbf2dbe9c84c..596bbc2c0348 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -5,6 +5,8 @@ use ArrayIterator; use Closure; use DateTimeInterface; +use Illuminate\Collections\ItemNotFoundException; +use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Support\Traits\EnumeratesValues; use Illuminate\Support\Traits\Macroable; use IteratorAggregate; @@ -1010,6 +1012,52 @@ public function split($numberOfGroups) return $this->passthru('split', func_get_args()); } + /** + * Get the first item in the collection, but only if exactly + * item exists. Otherwise, throw an exception. + * + * @param callable|null $callback + * @return mixed + * + * @throws ItemNotFoundException + * @throws MultipleItemsFoundException + */ + public function sole(callable $callback = null) + { + $iterator = $this->getIterator(); + + if (is_null($callback)) { + if (! $iterator->valid()) { + throw new ItemNotFoundException; + } + + if ($this->take(2)->count() > 1) { + throw new MultipleItemsFoundException; + } + + return $iterator->current(); + } + + $items = []; + + foreach ($iterator as $key => $value) { + if ($callback($value, $key)) { + $items[] = $value; + } + } + + if (! count($items)) { + throw new ItemNotFoundException; + } + + if (count($items) > 1) { + throw new MultipleItemsFoundException; + } + + return $items[0]; + } + + /** * Chunk the collection into chunks of the given size. * diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 45f82170e20d..cb40a56556ea 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -294,28 +294,6 @@ public function firstWhere($key, $operator = null, $value = null) return $this->first($this->operatorForWhere(...func_get_args())); } - /** - * Get the first item in the collection, but only if exactly - * item exists. Otherwise, throw an exception. - * - * @return mixed - * - * @throws ItemNotFoundException - * @throws MultipleItemsFoundException - */ - public function sole() - { - if ($this->isEmpty()) { - throw new ItemNotFoundException; - } - - if ($this->count() > 1) { - throw new MultipleItemsFoundException; - } - - return $this->first(); - } - /** * Determine if the collection is not empty. * From 5465ded6abd2aae62c2c876df7e22db86264a291 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Sun, 18 Apr 2021 19:41:08 +0100 Subject: [PATCH 05/15] Added tests for the sole() Collection method. --- tests/Support/SupportCollectionTest.php | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 5277f3baf775..2f3544900403 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -112,6 +112,46 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExists($collection) $collection->where('name', 'foo')->sole(); } + /** + * @dataProvider collectionClassProvider + */ + public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection) + { + $data = new $collection(['foo', 'bar', 'baz']); + $result = $data->sole(function ($value) { + return $value === 'bar'; + }); + $this->assertSame('bar', $result); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testSoleThrowsExceptionIfNoItemsExistsWithCallback($collection) + { + $this->expectException(ItemNotFoundException::class); + + $data = new $collection(['foo', 'bar', 'baz']); + + $data->sole(function ($value) { + return $value === 'invalid'; + }); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($collection) + { + $this->expectException(MultipleItemsFoundException::class); + + $data = new $collection(['foo', 'bar', 'bar']); + + $data->sole(function ($value) { + return $value === 'bar'; + }); + } + /** * @dataProvider collectionClassProvider */ From 04a1df175a9bd2de03aec05e9fab0639f0fc7ea7 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Sun, 18 Apr 2021 19:45:44 +0100 Subject: [PATCH 06/15] Added soleWhere() method and added tests. --- src/Illuminate/Collections/Enumerable.php | 12 +++++ .../Collections/Traits/EnumeratesValues.php | 15 +++++++ tests/Support/SupportCollectionTest.php | 44 +++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 9f146debdaa0..c356f0ed0024 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -822,6 +822,18 @@ public function split($numberOfGroups); */ public function sole(callable $callback = null); + /** + * Get the first item by the given key value pair, but only if + * exactly one item matches the criteria. Otherwise, throw + * an exception. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return mixed + */ + public function soleWhere($key, $operator = null, $value = null); + /** * Chunk the collection into chunks of the given size. * diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index cb40a56556ea..9631fc7bb4db 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -455,6 +455,21 @@ public function sum($callback = null) }, 0); } + /** + * Get the first item by the given key value pair, but only if + * exactly one item matches the criteria. Otherwise, throw + * an exception. + * + * @param string $key + * @param mixed $operator + * @param mixed $value + * @return mixed + */ + public function soleWhere($key, $operator = null, $value = null) + { + return $this->sole($this->operatorForWhere(...func_get_args())); + } + /** * Apply the callback if the value is truthy. * diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 2f3544900403..18316cfcccf0 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -168,6 +168,50 @@ public function testFirstWhere($collection) $this->assertNull($data->firstWhere('nonexistent', 'key')); } + /** + * @dataProvider collectionClassProvider + */ + public function testSoleWhere($collection) + { + $data = new $collection([ + ['material' => 'paper', 'type' => 'book'], + ['material' => 'rubber', 'type' => 'gasket'], + ]); + + $this->assertSame('book', $data->soleWhere('material', 'paper')['type']); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testSoleWhereThrowsExceptionIfNoItemExists($collection) + { + $this->expectException(ItemNotFoundException::class); + + $data = new $collection([ + ['material' => 'paper', 'type' => 'book'], + ['material' => 'rubber', 'type' => 'gasket'], + ]); + + $data->soleWhere('material', 'invalid'); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testSoleWhereThrowsExceptionIfMultipleItemsExists($collection) + { + $this->expectException(MultipleItemsFoundException::class); + + $data = new $collection([ + ['material' => 'paper', 'type' => 'book'], + ['material' => 'paper', 'type' => 'letter'], + ['material' => 'rubber', 'type' => 'gasket'], + ]); + + $data->soleWhere('material', 'paper'); + } + /** * @dataProvider collectionClassProvider */ From d6a023e9b246a08cea604afe46396d458af9ce07 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Sun, 18 Apr 2021 20:02:37 +0100 Subject: [PATCH 07/15] Style updates. --- src/Illuminate/Collections/ItemNotFoundException.php | 1 - src/Illuminate/Collections/LazyCollection.php | 1 - src/Illuminate/Collections/MultipleItemsFoundException.php | 1 - src/Illuminate/Collections/Traits/EnumeratesValues.php | 2 -- 4 files changed, 5 deletions(-) diff --git a/src/Illuminate/Collections/ItemNotFoundException.php b/src/Illuminate/Collections/ItemNotFoundException.php index b41b52b06669..8f9c17f0eb74 100644 --- a/src/Illuminate/Collections/ItemNotFoundException.php +++ b/src/Illuminate/Collections/ItemNotFoundException.php @@ -6,5 +6,4 @@ class ItemNotFoundException extends RuntimeException { - } diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index 596bbc2c0348..45602e330cb6 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1057,7 +1057,6 @@ public function sole(callable $callback = null) return $items[0]; } - /** * Chunk the collection into chunks of the given size. * diff --git a/src/Illuminate/Collections/MultipleItemsFoundException.php b/src/Illuminate/Collections/MultipleItemsFoundException.php index b0d3d36092df..e1ead76393ab 100644 --- a/src/Illuminate/Collections/MultipleItemsFoundException.php +++ b/src/Illuminate/Collections/MultipleItemsFoundException.php @@ -6,5 +6,4 @@ class MultipleItemsFoundException extends RuntimeException { - } diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index 9631fc7bb4db..ce64f1994c77 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -5,8 +5,6 @@ use CachingIterator; use Closure; use Exception; -use Illuminate\Collections\ItemNotFoundException; -use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; use Illuminate\Support\Arr; From 534bb57a02021acfc3f72f2b5377134554a54271 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Mon, 19 Apr 2021 15:22:55 +0100 Subject: [PATCH 08/15] Removed sole() and soleWhere() from the Enumerable contract. --- src/Illuminate/Collections/Enumerable.php | 24 ----------------------- 1 file changed, 24 deletions(-) diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index c356f0ed0024..497da7ecc3a8 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -810,30 +810,6 @@ public function slice($offset, $length = null); */ public function split($numberOfGroups); - /** - * Get the first item in the collection, but only if exactly - * item exists. Otherwise, throw an exception. - * - * @param callable|null $callback - * @return mixed - * - * @throws ItemNotFoundException - * @throws MultipleItemsFoundException - */ - public function sole(callable $callback = null); - - /** - * Get the first item by the given key value pair, but only if - * exactly one item matches the criteria. Otherwise, throw - * an exception. - * - * @param string $key - * @param mixed $operator - * @param mixed $value - * @return mixed - */ - public function soleWhere($key, $operator = null, $value = null); - /** * Chunk the collection into chunks of the given size. * From d8cd628a42367f47946507f27fe3399bfa3d41ce Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Mon, 19 Apr 2021 15:28:54 +0100 Subject: [PATCH 09/15] Removed soleWhere() and simplified sole() method for LazyCollections. --- src/Illuminate/Collections/Collection.php | 4 +- src/Illuminate/Collections/LazyCollection.php | 41 ++++------------- .../Collections/Traits/EnumeratesValues.php | 15 ------- tests/Support/SupportCollectionTest.php | 44 ------------------- 4 files changed, 10 insertions(+), 94 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index ce73cfb7a3ae..b2c4ce214fd8 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1059,8 +1059,8 @@ public function splitIn($numberOfGroups) * @param callable|null $callback * @return mixed * - * @throws ItemNotFoundException - * @throws MultipleItemsFoundException + * @throws \Illuminate\Collections\ItemNotFoundException + * @throws \Illuminate\Collections\MultipleItemsFoundException */ public function sole(callable $callback = null) { diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index 45602e330cb6..f4b59ee9dd9f 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1019,42 +1019,17 @@ public function split($numberOfGroups) * @param callable|null $callback * @return mixed * - * @throws ItemNotFoundException - * @throws MultipleItemsFoundException + * @throws \Illuminate\Collections\ItemNotFoundException + * @throws \Illuminate\Collections\MultipleItemsFoundException */ public function sole(callable $callback = null) { - $iterator = $this->getIterator(); - - if (is_null($callback)) { - if (! $iterator->valid()) { - throw new ItemNotFoundException; - } - - if ($this->take(2)->count() > 1) { - throw new MultipleItemsFoundException; - } - - return $iterator->current(); - } - - $items = []; - - foreach ($iterator as $key => $value) { - if ($callback($value, $key)) { - $items[] = $value; - } - } - - if (! count($items)) { - throw new ItemNotFoundException; - } - - if (count($items) > 1) { - throw new MultipleItemsFoundException; - } - - return $items[0]; + return $this + ->when($callback) + ->filter($callback) + ->take(2) + ->collect() + ->sole(); } /** diff --git a/src/Illuminate/Collections/Traits/EnumeratesValues.php b/src/Illuminate/Collections/Traits/EnumeratesValues.php index ce64f1994c77..bdeb7fde8a48 100644 --- a/src/Illuminate/Collections/Traits/EnumeratesValues.php +++ b/src/Illuminate/Collections/Traits/EnumeratesValues.php @@ -453,21 +453,6 @@ public function sum($callback = null) }, 0); } - /** - * Get the first item by the given key value pair, but only if - * exactly one item matches the criteria. Otherwise, throw - * an exception. - * - * @param string $key - * @param mixed $operator - * @param mixed $value - * @return mixed - */ - public function soleWhere($key, $operator = null, $value = null) - { - return $this->sole($this->operatorForWhere(...func_get_args())); - } - /** * Apply the callback if the value is truthy. * diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 18316cfcccf0..2f3544900403 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -168,50 +168,6 @@ public function testFirstWhere($collection) $this->assertNull($data->firstWhere('nonexistent', 'key')); } - /** - * @dataProvider collectionClassProvider - */ - public function testSoleWhere($collection) - { - $data = new $collection([ - ['material' => 'paper', 'type' => 'book'], - ['material' => 'rubber', 'type' => 'gasket'], - ]); - - $this->assertSame('book', $data->soleWhere('material', 'paper')['type']); - } - - /** - * @dataProvider collectionClassProvider - */ - public function testSoleWhereThrowsExceptionIfNoItemExists($collection) - { - $this->expectException(ItemNotFoundException::class); - - $data = new $collection([ - ['material' => 'paper', 'type' => 'book'], - ['material' => 'rubber', 'type' => 'gasket'], - ]); - - $data->soleWhere('material', 'invalid'); - } - - /** - * @dataProvider collectionClassProvider - */ - public function testSoleWhereThrowsExceptionIfMultipleItemsExists($collection) - { - $this->expectException(MultipleItemsFoundException::class); - - $data = new $collection([ - ['material' => 'paper', 'type' => 'book'], - ['material' => 'paper', 'type' => 'letter'], - ['material' => 'rubber', 'type' => 'gasket'], - ]); - - $data->soleWhere('material', 'paper'); - } - /** * @dataProvider collectionClassProvider */ From 483e43b09b14902d2853d6f795ff656a1b118a0d Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Mon, 19 Apr 2021 15:33:35 +0100 Subject: [PATCH 10/15] Added when() to sole(). --- 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 b2c4ce214fd8..65b9ec5f48b9 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1064,7 +1064,7 @@ public function splitIn($numberOfGroups) */ public function sole(callable $callback = null) { - $items = $this->filter($callback); + $items = $this->when($callback)->filter($callback); if ($items->isEmpty()) { throw new ItemNotFoundException; From 4e76b9158f98f7f0c23c710b95ccc8eb06337f05 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Mon, 19 Apr 2021 15:43:38 +0100 Subject: [PATCH 11/15] Added test for sole(). --- tests/Support/SupportLazyCollectionIsLazyTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php index c523038c450c..82730fe587af 100644 --- a/tests/Support/SupportLazyCollectionIsLazyTest.php +++ b/tests/Support/SupportLazyCollectionIsLazyTest.php @@ -977,6 +977,17 @@ public function testSomeIsLazy() }); } + public function testSoleIsLazy() + { + $data = $this->make([['a' => 1], ['a' => 2], ['a' => 3]]); + + $this->assertEnumeratesCollection($data, 3, function ($collection) { + $collection->sole(function ($item) { + return $item['a'] === 2; + }); + }); + } + public function testSortIsLazy() { $this->assertDoesNotEnumerate(function ($collection) { From c0a82d8e42ec973bc49b0e5bba93e4dbc36bd908 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Mon, 19 Apr 2021 15:45:07 +0100 Subject: [PATCH 12/15] Style updates. --- src/Illuminate/Collections/Enumerable.php | 2 -- src/Illuminate/Collections/LazyCollection.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 497da7ecc3a8..4bda35476905 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -3,8 +3,6 @@ namespace Illuminate\Support; use Countable; -use Illuminate\Collections\ItemNotFoundException; -use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; use IteratorAggregate; diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index f4b59ee9dd9f..be5a7d09602c 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -5,8 +5,6 @@ use ArrayIterator; use Closure; use DateTimeInterface; -use Illuminate\Collections\ItemNotFoundException; -use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Support\Traits\EnumeratesValues; use Illuminate\Support\Traits\Macroable; use IteratorAggregate; From 3c4f386a450d3eb2c66dc0afa72c4e81bf1f2b13 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Mon, 19 Apr 2021 18:04:30 +0100 Subject: [PATCH 13/15] Updated test. --- .../SupportLazyCollectionIsLazyTest.php | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tests/Support/SupportLazyCollectionIsLazyTest.php b/tests/Support/SupportLazyCollectionIsLazyTest.php index 82730fe587af..dca52b769e07 100644 --- a/tests/Support/SupportLazyCollectionIsLazyTest.php +++ b/tests/Support/SupportLazyCollectionIsLazyTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Support; +use Illuminate\Collections\MultipleItemsFoundException; use Illuminate\Support\LazyCollection; use PHPUnit\Framework\TestCase; use stdClass; @@ -979,13 +980,29 @@ public function testSomeIsLazy() public function testSoleIsLazy() { - $data = $this->make([['a' => 1], ['a' => 2], ['a' => 3]]); + $this->assertEnumerates(2, function ($collection) { + try { + $collection->sole(); + } catch (MultipleItemsFoundException $e) { + // + } + }); - $this->assertEnumeratesCollection($data, 3, function ($collection) { + $this->assertEnumeratesOnce(function ($collection) { $collection->sole(function ($item) { - return $item['a'] === 2; + return $item === 1; }); }); + + $this->assertEnumerates(4, function ($collection) { + try { + $collection->sole(function ($item) { + return $item % 2 === 0; + }); + } catch (MultipleItemsFoundException $e) { + // + } + }); } public function testSortIsLazy() From 2d3b63c3f5008741ad7aa8ae1c0bdd522c1887e9 Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Mon, 19 Apr 2021 18:41:39 +0100 Subject: [PATCH 14/15] Added operator suport for the sole() method. --- src/Illuminate/Collections/Collection.php | 24 ++++++++++++------- src/Illuminate/Collections/LazyCollection.php | 22 ++++++++++------- tests/Support/SupportCollectionTest.php | 2 ++ 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 65b9ec5f48b9..0e18e2c2a1d8 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1056,25 +1056,31 @@ public function splitIn($numberOfGroups) * Get the first item in the collection, but only if exactly * item exists. Otherwise, throw an exception. * - * @param callable|null $callback + * @param mixed $key + * @param mixed $operator + * @param mixed $value * @return mixed * * @throws \Illuminate\Collections\ItemNotFoundException * @throws \Illuminate\Collections\MultipleItemsFoundException */ - public function sole(callable $callback = null) + public function sole($key = null, $operator = null, $value = null) { - $items = $this->when($callback)->filter($callback); + if (func_num_args() <= 1) { + $items = $this->when($key)->filter($key); - if ($items->isEmpty()) { - throw new ItemNotFoundException; - } + if ($items->isEmpty()) { + throw new ItemNotFoundException; + } + + if ($items->count() > 1) { + throw new MultipleItemsFoundException; + } - if ($items->count() > 1) { - throw new MultipleItemsFoundException; + return $items->first(); } - return $items->first(); + return $this->sole($this->operatorForWhere(...func_get_args())); } /** diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index be5a7d09602c..8046f3aa421b 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1014,20 +1014,26 @@ public function split($numberOfGroups) * Get the first item in the collection, but only if exactly * item exists. Otherwise, throw an exception. * - * @param callable|null $callback + * @param mixed $key + * @param mixed $operator + * @param mixed $value * @return mixed * * @throws \Illuminate\Collections\ItemNotFoundException * @throws \Illuminate\Collections\MultipleItemsFoundException */ - public function sole(callable $callback = null) + public function sole($key = null, $operator = null, $value = null) { - return $this - ->when($callback) - ->filter($callback) - ->take(2) - ->collect() - ->sole(); + if (func_num_args() <= 1) { + return $this + ->when($key) + ->filter($key) + ->take(2) + ->collect() + ->sole(); + } + + return $this->sole($this->operatorForWhere(...func_get_args())); } /** diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 2f3544900403..f80f69a2236c 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -79,6 +79,8 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection) ]); $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')); } /** From 9c42ab18e301bfeb6b8455b824d62835945d21fc Mon Sep 17 00:00:00 2001 From: Ashley Allen Date: Mon, 19 Apr 2021 19:12:07 +0100 Subject: [PATCH 15/15] Replace 'if' statement with ternary. --- src/Illuminate/Collections/Collection.php | 20 +++++++++---------- src/Illuminate/Collections/LazyCollection.php | 20 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 0e18e2c2a1d8..23059c37eab1 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1066,21 +1066,21 @@ public function splitIn($numberOfGroups) */ public function sole($key = null, $operator = null, $value = null) { - if (func_num_args() <= 1) { - $items = $this->when($key)->filter($key); + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; - if ($items->isEmpty()) { - throw new ItemNotFoundException; - } + $items = $this->when($filter)->filter($filter); - if ($items->count() > 1) { - throw new MultipleItemsFoundException; - } + if ($items->isEmpty()) { + throw new ItemNotFoundException; + } - return $items->first(); + if ($items->count() > 1) { + throw new MultipleItemsFoundException; } - return $this->sole($this->operatorForWhere(...func_get_args())); + return $items->first(); } /** diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index 8046f3aa421b..76f970fef6c4 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1024,16 +1024,16 @@ public function split($numberOfGroups) */ public function sole($key = null, $operator = null, $value = null) { - if (func_num_args() <= 1) { - return $this - ->when($key) - ->filter($key) - ->take(2) - ->collect() - ->sole(); - } - - return $this->sole($this->operatorForWhere(...func_get_args())); + $filter = func_num_args() > 1 + ? $this->operatorForWhere(...func_get_args()) + : $key; + + return $this + ->when($filter) + ->filter($filter) + ->take(2) + ->collect() + ->sole(); } /**