Skip to content

Commit 0633b9e

Browse files
powellblythToby Powell-BlythJosephSilbertaylorotwell
authored
[8.x] Adds firstOrFail to Illuminate\Support\Collections and Illuminate\Support\LazyCollections (#38420)
* Add firstOrFail method to Collections and LazyCollections * Restore test that got moved, fixed some grammaticals in test names * Style fixes * Make take(2) as take(1) instead * use unless instead of when Per Joseph Silber's suggestion Co-authored-by: Joseph Silber <[email protected]> * Use Unless instead of When Per Joseph Silber's suggestion Co-authored-by: Joseph Silber <[email protected]> * Revert L9 changes, add test requested by @JosephSilber * Remove unneeded try catch * Remove unneeded include * Test that when an enumeration does not contain an item, that firstOrFail enumerates the full collection * Style fix * Update Collection.php * Update LazyCollection.php Co-authored-by: Toby Powell-Blyth <[email protected]> Co-authored-by: Joseph Silber <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent b1d9365 commit 0633b9e

File tree

4 files changed

+166
-2
lines changed

4 files changed

+166
-2
lines changed

src/Illuminate/Collections/Collection.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,31 @@ public function sole($key = null, $operator = null, $value = null)
11301130
return $items->first();
11311131
}
11321132

1133+
/**
1134+
* Get the first item in the collection but throw an exception if no matching items exist.
1135+
*
1136+
* @param mixed $key
1137+
* @param mixed $operator
1138+
* @param mixed $value
1139+
* @return mixed
1140+
*
1141+
* @throws \Illuminate\Collections\ItemNotFoundException
1142+
*/
1143+
public function firstOrFail($key = null, $operator = null, $value = null)
1144+
{
1145+
$filter = func_num_args() > 1
1146+
? $this->operatorForWhere(...func_get_args())
1147+
: $key;
1148+
1149+
$items = $this->when($filter)->filter($filter);
1150+
1151+
if ($items->isEmpty()) {
1152+
throw new ItemNotFoundException;
1153+
}
1154+
1155+
return $items->first();
1156+
}
1157+
11331158
/**
11341159
* Chunk the collection into chunks of the given size.
11351160
*

src/Illuminate/Collections/LazyCollection.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,30 @@ public function sole($key = null, $operator = null, $value = null)
10741074
->sole();
10751075
}
10761076

1077+
/**
1078+
* Get the first item in the collection but throw an exception if no matching items exist.
1079+
*
1080+
* @param mixed $key
1081+
* @param mixed $operator
1082+
* @param mixed $value
1083+
* @return mixed
1084+
*
1085+
* @throws \Illuminate\Collections\ItemNotFoundException
1086+
*/
1087+
public function firstOrFail($key = null, $operator = null, $value = null)
1088+
{
1089+
$filter = func_num_args() > 1
1090+
? $this->operatorForWhere(...func_get_args())
1091+
: $key;
1092+
1093+
return $this
1094+
->when($filter)
1095+
->filter($filter)
1096+
->take(1)
1097+
->collect()
1098+
->firstOrFail();
1099+
}
1100+
10771101
/**
10781102
* Chunk the collection into chunks of the given size.
10791103
*

tests/Support/SupportCollectionTest.php

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExists($collection)
8686
/**
8787
* @dataProvider collectionClassProvider
8888
*/
89-
public function testSoleThrowsExceptionIfNoItemsExists($collection)
89+
public function testSoleThrowsExceptionIfNoItemsExist($collection)
9090
{
9191
$this->expectException(ItemNotFoundException::class);
9292

@@ -129,7 +129,7 @@ public function testSoleReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback(
129129
/**
130130
* @dataProvider collectionClassProvider
131131
*/
132-
public function testSoleThrowsExceptionIfNoItemsExistsWithCallback($collection)
132+
public function testSoleThrowsExceptionIfNoItemsExistWithCallback($collection)
133133
{
134134
$this->expectException(ItemNotFoundException::class);
135135

@@ -154,6 +154,91 @@ public function testSoleThrowsExceptionIfMoreThanOneItemExistsWithCallback($coll
154154
});
155155
}
156156

157+
/**
158+
* @dataProvider collectionClassProvider
159+
*/
160+
public function testFirstOrFailReturnsFirstItemInCollection($collection)
161+
{
162+
$collection = new $collection([
163+
['name' => 'foo'],
164+
['name' => 'bar'],
165+
]);
166+
167+
$this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->firstOrFail());
168+
$this->assertSame(['name' => 'foo'], $collection->firstOrFail('name', '=', 'foo'));
169+
$this->assertSame(['name' => 'foo'], $collection->firstOrFail('name', 'foo'));
170+
}
171+
172+
/**
173+
* @dataProvider collectionClassProvider
174+
*/
175+
public function testFirstOrFailThrowsExceptionIfNoItemsExist($collection)
176+
{
177+
$this->expectException(ItemNotFoundException::class);
178+
179+
$collection = new $collection([
180+
['name' => 'foo'],
181+
['name' => 'bar'],
182+
]);
183+
184+
$collection->where('name', 'INVALID')->firstOrFail();
185+
}
186+
187+
/**
188+
* @dataProvider collectionClassProvider
189+
*/
190+
public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExists($collection)
191+
{
192+
$collection = new $collection([
193+
['name' => 'foo'],
194+
['name' => 'foo'],
195+
['name' => 'bar'],
196+
]);
197+
198+
$this->assertSame(['name' => 'foo'], $collection->where('name', 'foo')->firstOrFail());
199+
}
200+
201+
/**
202+
* @dataProvider collectionClassProvider
203+
*/
204+
public function testFirstOrFailReturnsFirstItemInCollectionIfOnlyOneExistsWithCallback($collection)
205+
{
206+
$data = new $collection(['foo', 'bar', 'baz']);
207+
$result = $data->firstOrFail(function ($value) {
208+
return $value === 'bar';
209+
});
210+
$this->assertSame('bar', $result);
211+
}
212+
213+
/**
214+
* @dataProvider collectionClassProvider
215+
*/
216+
public function testFirstOrFailThrowsExceptionIfNoItemsExistWithCallback($collection)
217+
{
218+
$this->expectException(ItemNotFoundException::class);
219+
220+
$data = new $collection(['foo', 'bar', 'baz']);
221+
222+
$data->firstOrFail(function ($value) {
223+
return $value === 'invalid';
224+
});
225+
}
226+
227+
/**
228+
* @dataProvider collectionClassProvider
229+
*/
230+
public function testFirstOrFailDoesntThrowExceptionIfMoreThanOneItemExistsWithCallback($collection)
231+
{
232+
$data = new $collection(['foo', 'bar', 'bar']);
233+
234+
$this->assertSame(
235+
'bar',
236+
$data->firstOrFail(function ($value) {
237+
return $value === 'bar';
238+
})
239+
);
240+
}
241+
157242
/**
158243
* @dataProvider collectionClassProvider
159244
*/

tests/Support/SupportLazyCollectionIsLazyTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Tests\Support;
44

5+
use Illuminate\Support\ItemNotFoundException;
56
use Illuminate\Support\LazyCollection;
67
use Illuminate\Support\MultipleItemsFoundException;
78
use PHPUnit\Framework\TestCase;
@@ -986,6 +987,35 @@ public function testSliceIsLazy()
986987
});
987988
}
988989

990+
public function testFindFirstOrFailIsLazy()
991+
{
992+
$this->assertEnumerates(1, function ($collection) {
993+
$collection->firstOrFail();
994+
});
995+
996+
$this->assertEnumerates(1, function ($collection) {
997+
$collection->firstOrFail(function ($item) {
998+
return $item === 1;
999+
});
1000+
});
1001+
1002+
$this->assertEnumerates(100, function ($collection) {
1003+
try {
1004+
$collection->firstOrFail(function ($item) {
1005+
return $item === 101;
1006+
});
1007+
} catch (ItemNotFoundException $e) {
1008+
//
1009+
}
1010+
});
1011+
1012+
$this->assertEnumerates(2, function ($collection) {
1013+
$collection->firstOrFail(function ($item) {
1014+
return $item % 2 === 0;
1015+
});
1016+
});
1017+
}
1018+
9891019
public function testSomeIsLazy()
9901020
{
9911021
$this->assertEnumerates(5, function ($collection) {

0 commit comments

Comments
 (0)