diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 995ea2ac2429..ee48d37daff0 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1113,6 +1113,54 @@ public function search($value, $strict = false) return false; } + /** + * Get the item before the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function before($value, $strict = false) + { + $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 TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function after($value, $strict = false) + { + $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 N items from the collection. * diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index f52b67e7e9d7..eaef19990dad 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -889,6 +889,24 @@ public function reverse(); */ public function search($value, $strict = false); + /** + * Get the item before the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function before($value, $strict = false); + + /** + * Get the item after the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function after($value, $strict = false); + /** * Shuffle the items in the collection. * diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index 8aea9e1ee29e..958daf8a7508 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1082,6 +1082,66 @@ public function search($value, $strict = false) return false; } + /** + * Get the item before the given item. + * + * @param TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function before($value, $strict = false) + { + $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 TValue|(callable(TValue,TKey): bool) $value + * @param bool $strict + * @return TValue|null + */ + public function after($value, $strict = false) + { + $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/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 01de6e32904d..0b1a0f6fcb67 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -3702,6 +3702,127 @@ public function testSearchReturnsFalseWhenItemIsNotFound($collection) })); } + #[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)); + } + #[DataProvider('collectionClassProvider')] public function testKeys($collection) {