Skip to content

Commit

Permalink
[11.x] Add before and after methods to Collection (#51752)
Browse files Browse the repository at this point in the history
* add before method to Collection

* add tests for before method

* add before method to Enumerable interface

* add before method to LazyCollection

* update tests

* add "after" method to collection

* add tests for after method

* remove unnecessary condition

* fix coding style

* Update Collection.php

---------

Co-authored-by: Taylor Otwell <[email protected]>
  • Loading branch information
avosalmon and taylorotwell authored Jun 11, 2024
1 parent da50eea commit 7aef671
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/Illuminate/Collections/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
18 changes: 18 additions & 0 deletions src/Illuminate/Collections/Enumerable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
60 changes: 60 additions & 0 deletions src/Illuminate/Collections/LazyCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
121 changes: 121 additions & 0 deletions tests/Support/SupportCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down

0 comments on commit 7aef671

Please sign in to comment.