Skip to content

Commit

Permalink
Merge branch 'feature/collection-sole' into 8.x
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorotwell committed Apr 20, 2021
2 parents 26a7353 + 3ea827c commit ec7a532
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 0 deletions.
32 changes: 32 additions & 0 deletions src/Illuminate/Collections/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1050,6 +1052,36 @@ public function splitIn($numberOfGroups)
return $this->chunk(ceil($this->count() / $numberOfGroups));
}

/**
* Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
*
* @param mixed $key
* @param mixed $operator
* @param mixed $value
* @return mixed
*
* @throws \Illuminate\Collections\ItemNotFoundException
* @throws \Illuminate\Collections\MultipleItemsFoundException
*/
public function sole($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;
}

if ($items->count() > 1) {
throw new MultipleItemsFoundException;
}

return $items->first();
}

/**
* Chunk the collection into chunks of the given size.
*
Expand Down
9 changes: 9 additions & 0 deletions src/Illuminate/Collections/ItemNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Illuminate\Collections;

use RuntimeException;

class ItemNotFoundException extends RuntimeException
{
}
25 changes: 25 additions & 0 deletions src/Illuminate/Collections/LazyCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,31 @@ public function split($numberOfGroups)
return $this->passthru('split', func_get_args());
}

/**
* Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
*
* @param mixed $key
* @param mixed $operator
* @param mixed $value
* @return mixed
*
* @throws \Illuminate\Collections\ItemNotFoundException
* @throws \Illuminate\Collections\MultipleItemsFoundException
*/
public function sole($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()
->sole();
}

/**
* Chunk the collection into chunks of the given size.
*
Expand Down
9 changes: 9 additions & 0 deletions src/Illuminate/Collections/MultipleItemsFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Illuminate\Collections;

use RuntimeException;

class MultipleItemsFoundException extends RuntimeException
{
}
88 changes: 88 additions & 0 deletions tests/Support/SupportCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -66,6 +68,92 @@ 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
*/
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
*/
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
*/
Expand Down
28 changes: 28 additions & 0 deletions tests/Support/SupportLazyCollectionIsLazyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Illuminate\Tests\Support;

use Illuminate\Collections\MultipleItemsFoundException;
use Illuminate\Support\LazyCollection;
use PHPUnit\Framework\TestCase;
use stdClass;
Expand Down Expand Up @@ -977,6 +978,33 @@ public function testSomeIsLazy()
});
}

public function testSoleIsLazy()
{
$this->assertEnumerates(2, function ($collection) {
try {
$collection->sole();
} catch (MultipleItemsFoundException $e) {
//
}
});

$this->assertEnumeratesOnce(function ($collection) {
$collection->sole(function ($item) {
return $item === 1;
});
});

$this->assertEnumerates(4, function ($collection) {
try {
$collection->sole(function ($item) {
return $item % 2 === 0;
});
} catch (MultipleItemsFoundException $e) {
//
}
});
}

public function testSortIsLazy()
{
$this->assertDoesNotEnumerate(function ($collection) {
Expand Down

0 comments on commit ec7a532

Please sign in to comment.