From 91625d84663c5e10d508485889d31306d960c7b5 Mon Sep 17 00:00:00 2001 From: Deeka Wong Date: Thu, 13 Jun 2024 14:23:42 +0800 Subject: [PATCH] Added `before` and `after` methods to `Collection` (#6869) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 李铭昕 <715557344@qq.com> --- CHANGELOG-3.1.md | 4 + src/collection/src/Collection.php | 46 +++++++++ src/collection/src/Enumerable.php | 16 ++++ src/collection/src/LazyCollection.php | 58 ++++++++++++ src/collection/tests/CollectionTest.php | 121 ++++++++++++++++++++++++ 5 files changed, 245 insertions(+) diff --git a/CHANGELOG-3.1.md b/CHANGELOG-3.1.md index f67601670..649780791 100644 --- a/CHANGELOG-3.1.md +++ b/CHANGELOG-3.1.md @@ -1,5 +1,9 @@ # v3.1.27 - TBD +## Added + +- [#6869](https://github.com/hyperf/hyperf/pull/6869) Added `before` and `after` methods to `Collection`. + # v3.1.26 - 2024-06-13 ## Fixed diff --git a/src/collection/src/Collection.php b/src/collection/src/Collection.php index 251f4b809..7bd40bea3 100644 --- a/src/collection/src/Collection.php +++ b/src/collection/src/Collection.php @@ -924,6 +924,52 @@ class Collection implements Enumerable, ArrayAccess return false; } + /** + * Get the item before the given item. + * + * @param (callable(TValue,TKey): bool)|TValue $value + * @return null|TValue + */ + public function before(mixed $value, bool $strict = false): mixed + { + $key = $this->search($value, $strict); + + if ($key === false) { + return null; + } + + $position = $this->keys()->search($key); + + if ($position === 0) { + return null; + } + + return $this->get($this->keys()->get($position - 1)); + } + + /** + * Get the item after the given item. + * + * @param (callable(TValue,TKey): bool)|TValue $value + * @return null|TValue + */ + public function after(mixed $value, bool $strict = false): mixed + { + $key = $this->search($value, $strict); + + if ($key === false) { + return null; + } + + $position = $this->keys()->search($key); + + if ($position === $this->keys()->count() - 1) { + return null; + } + + return $this->get($this->keys()->get($position + 1)); + } + /** * Get and remove the first item from the collection. * diff --git a/src/collection/src/Enumerable.php b/src/collection/src/Enumerable.php index bb990bda4..cf8fa373f 100644 --- a/src/collection/src/Enumerable.php +++ b/src/collection/src/Enumerable.php @@ -845,6 +845,22 @@ interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, */ public function search($value, bool $strict = false); + /** + * Get the item before the given item. + * + * @param (callable(TValue,TKey): bool)|TValue $value + * @return null|TValue + */ + public function before(mixed $value, bool $strict = false): mixed; + + /** + * Get the item after the given item. + * + * @param (callable(TValue,TKey): bool)|TValue $value + * @return null|TValue + */ + public function after(mixed $value, bool $strict = false): mixed; + /** * Shuffle the items in the collection. * diff --git a/src/collection/src/LazyCollection.php b/src/collection/src/LazyCollection.php index 150bbe65b..73cc1c7ac 100644 --- a/src/collection/src/LazyCollection.php +++ b/src/collection/src/LazyCollection.php @@ -1069,6 +1069,64 @@ class LazyCollection implements Enumerable return false; } + /** + * Get the item before the given item. + * + * @param (callable(TValue,TKey): bool)|TValue $value + * @return null|TValue + */ + public function before(mixed $value, bool $strict = false): mixed + { + $previous = null; + + /** @var (callable(TValue,TKey): bool) $predicate */ + $predicate = $this->useAsCallable($value) + ? $value + : function ($item) use ($value, $strict) { + return $strict ? $item === $value : $item == $value; + }; + + foreach ($this as $key => $item) { + if ($predicate($item, $key)) { + return $previous; + } + + $previous = $item; + } + + return null; + } + + /** + * Get the item after the given item. + * + * @param (callable(TValue,TKey): bool)|TValue $value + * @return null|TValue + */ + public function after(mixed $value, bool $strict = false): mixed + { + $found = false; + + /** @var (callable(TValue,TKey): bool) $predicate */ + $predicate = $this->useAsCallable($value) + ? $value + : function ($item) use ($value, $strict) { + return $strict ? $item === $value : $item == $value; + }; + + foreach ($this as $key => $item) { + if ($found) { + return $item; + } + + if ($predicate($item, $key)) { + $found = true; + } + } + + return null; + } + /** * Shuffle the items in the collection. * diff --git a/src/collection/tests/CollectionTest.php b/src/collection/tests/CollectionTest.php index 5dfd98eba..51773792e 100644 --- a/src/collection/tests/CollectionTest.php +++ b/src/collection/tests/CollectionTest.php @@ -979,4 +979,125 @@ class CollectionTest extends TestCase $this->assertSame('foo', $data->get('email')); $this->assertSame('male', $data->get('gender')); } + + #[DataProvider('collectionClassProvider')] + public function testBeforeReturnsItemBeforeTheGivenItem($collection) + { + $c = new $collection([1, 2, 3, 4, 5, 2, 5, 'name' => 'taylor', 'framework' => 'laravel']); + + $this->assertEquals(1, $c->before(2)); + $this->assertEquals(1, $c->before('2')); + $this->assertEquals(5, $c->before('taylor')); + $this->assertSame('taylor', $c->before('laravel')); + $this->assertEquals(4, $c->before(function ($value) { + return $value > 4; + })); + $this->assertEquals(5, $c->before(function ($value) { + return ! is_numeric($value); + })); + } + + #[DataProvider('collectionClassProvider')] + public function testBeforeInStrictMode($collection) + { + $c = new $collection([false, 0, 1, [], '']); + $this->assertNull($c->before('false', true)); + $this->assertNull($c->before('1', true)); + $this->assertNull($c->before(false, true)); + $this->assertEquals(false, $c->before(0, true)); + $this->assertEquals(0, $c->before(1, true)); + $this->assertEquals(1, $c->before([], true)); + $this->assertEquals([], $c->before('', true)); + } + + #[DataProvider('collectionClassProvider')] + public function testBeforeReturnsNullWhenItemIsNotFound($collection) + { + $c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']); + + $this->assertNull($c->before(6)); + $this->assertNull($c->before('foo')); + $this->assertNull($c->before(function ($value) { + return $value < 1 && is_numeric($value); + })); + $this->assertNull($c->before(function ($value) { + return $value === 'nope'; + })); + } + + #[DataProvider('collectionClassProvider')] + public function testBeforeReturnsNullWhenItemOnTheFirstitem($collection) + { + $c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']); + + $this->assertNull($c->before(1)); + $this->assertNull($c->before(function ($value) { + return $value < 2 && is_numeric($value); + })); + + $c = new $collection(['foo' => 'bar', 1, 2, 3, 4, 5]); + $this->assertNull($c->before('bar')); + } + + #[DataProvider('collectionClassProvider')] + public function testAfterReturnsItemAfterTheGivenItem($collection) + { + $c = new $collection([1, 2, 3, 4, 2, 5, 'name' => 'taylor', 'framework' => 'laravel']); + + $this->assertEquals(2, $c->after(1)); + $this->assertEquals(3, $c->after(2)); + $this->assertEquals(4, $c->after(3)); + $this->assertEquals(2, $c->after(4)); + $this->assertEquals('taylor', $c->after(5)); + $this->assertEquals('laravel', $c->after('taylor')); + + $this->assertEquals(4, $c->after(function ($value) { + return $value > 2; + })); + $this->assertEquals('laravel', $c->after(function ($value) { + return ! is_numeric($value); + })); + } + + #[DataProvider('collectionClassProvider')] + public function testAfterInStrictMode($collection) + { + $c = new $collection([false, 0, 1, [], '']); + + $this->assertNull($c->after('false', true)); + $this->assertNull($c->after('1', true)); + $this->assertNull($c->after('', true)); + $this->assertEquals(0, $c->after(false, true)); + $this->assertEquals([], $c->after(1, true)); + $this->assertEquals('', $c->after([], true)); + } + + #[DataProvider('collectionClassProvider')] + public function testAfterReturnsNullWhenItemIsNotFound($collection) + { + $c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']); + + $this->assertNull($c->after(6)); + $this->assertNull($c->after('foo')); + $this->assertNull($c->after(function ($value) { + return $value < 1 && is_numeric($value); + })); + $this->assertNull($c->after(function ($value) { + return $value === 'nope'; + })); + } + + #[DataProvider('collectionClassProvider')] + public function testAfterReturnsNullWhenItemOnTheLastItem($collection) + { + $c = new $collection([1, 2, 3, 4, 5, 'foo' => 'bar']); + + $this->assertNull($c->after('bar')); + $this->assertNull($c->after(function ($value) { + return $value > 4 && ! is_numeric($value); + })); + + $c = new $collection(['foo' => 'bar', 1, 2, 3, 4, 5]); + $this->assertNull($c->after(5)); + } }