├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml └── src ├── CollectionMacroServiceProvider.php ├── Exceptions └── CollectionItemNotFound.php ├── Helpers └── CatchableCollectionProxy.php └── Macros ├── At.php ├── ChunkBy.php ├── CollectBy.php ├── ContainsAll.php ├── ContainsAny.php ├── EachCons.php ├── Eighth.php ├── Extract.php ├── Fifth.php ├── FilterMap.php ├── FirstOrFail.php ├── FirstOrPush.php ├── Fourth.php ├── FromPairs.php ├── GetCaseInsensitive.php ├── GetNth.php ├── Glob.php ├── GroupByModel.php ├── HasCaseInsensitive.php ├── Head.php ├── IfAny.php ├── IfEmpty.php ├── IfMacro.php ├── InsertAfter.php ├── InsertAfterKey.php ├── InsertAt.php ├── InsertBefore.php ├── InsertBeforeKey.php ├── Ninth.php ├── None.php ├── Paginate.php ├── ParallelMap.php ├── Path.php ├── PluckMany.php ├── PluckManyValues.php ├── PluckToArray.php ├── Prioritize.php ├── Recursive.php ├── Rotate.php ├── Second.php ├── SectionBy.php ├── Seventh.php ├── SimplePaginate.php ├── Sixth.php ├── SliceBefore.php ├── Tail.php ├── Tenth.php ├── Third.php ├── ToPairs.php ├── Transpose.php ├── TryCatch.php ├── Validate.php ├── WeightedRandom.php └── WithSize.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | ````# The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Logo for laravel-collection-macros 6 | 7 | 8 | 9 |

A set of useful Laravel collection macros

10 | 11 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-collection-macros.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-collection-macros) 12 | ![Run tests](https://github.com/spatie/laravel-collection-macros/workflows/Run%20tests/badge.svg) 13 | ![Check & fix styling](https://github.com/spatie/laravel-collection-macros/workflows/Check%20&%20fix%20styling/badge.svg) 14 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-collection-macros.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-collection-macros) 15 | 16 |
17 | 18 | This repository contains some useful collection macros. 19 | 20 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 21 | 22 | ## Support us 23 | 24 | [](https://spatie.be/github-ad-click/laravel-collection-macros) 25 | 26 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 27 | 28 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 29 | 30 | ## Installation 31 | 32 | You can pull in the package via composer: 33 | 34 | ``` bash 35 | composer require spatie/laravel-collection-macros 36 | ``` 37 | 38 | The package will automatically register itself. 39 | 40 | ## Macros 41 | 42 | - [`after`](#after) 43 | - [`at`](#at) 44 | - [`second`](#second) 45 | - [`third`](#third) 46 | - [`fourth`](#fourth) 47 | - [`fifth`](#fifth) 48 | - [`sixth`](#sixth) 49 | - [`seventh`](#seventh) 50 | - [`eighth`](#eighth) 51 | - [`ninth`](#ninth) 52 | - [`tenth`](#tenth) 53 | - [`getNth`](#getNth) 54 | - [`catch`](#catch) 55 | - [`chunkBy`](#chunkby) 56 | - [`collectBy`](#collectBy) 57 | - [`containsAny`](#containsAny) 58 | - [`containsAll`](#containsAll) 59 | - [`eachCons`](#eachcons) 60 | - [`extract`](#extract) 61 | - [`filterMap`](#filtermap) 62 | - [`firstOrFail`](#firstorfail) 63 | - [`firstOrPush`](#firstorpush) 64 | - [`fromPairs`](#frompairs) 65 | - [`getCaseInsensitive`](#getcaseinsensitive) 66 | - [`glob`](#glob) 67 | - [`groupByModel`](#groupbymodel) 68 | - [`hasCaseInsensitive`](#hascaseinsensitive) 69 | - [`head`](#head) 70 | - [`if`](#if) 71 | - [`ifAny`](#ifany) 72 | - [`ifEmpty`](#ifempty) 73 | - [`insertAfter`](#insertafter) 74 | - [`insertAfterKey`](#insertafterkey) 75 | - [`insertAt`](#insertat) 76 | - [`insertBefore`](#insertbefore) 77 | - [`insertBeforeKey`](#insertbeforekey) 78 | - [`none`](#none) 79 | - [`paginate`](#paginate) 80 | - [`path`](#path) 81 | - [`pluckMany`](#pluckmany) 82 | - [`pluckManyValues`](#pluckmanyvalues) 83 | - [`pluckToArray`](#plucktoarray) 84 | - [`prioritize`](#prioritize) 85 | - [`recursive`](#recursive) 86 | - [`rotate`](#rotate) 87 | - [`sectionBy`](#sectionby) 88 | - [`simplePaginate`](#simplepaginate) 89 | - [`sliceBefore`](#slicebefore) 90 | - [`tail`](#tail) 91 | - [`try`](#try) 92 | - [`toPairs`](#topairs) 93 | - [`transpose`](#transpose) 94 | - [`validate`](#validate) 95 | - [`weightedRandom`](#weightedRandom) 96 | - [`withSize`](#withsize) 97 | 98 | ### `after` 99 | 100 | Get the next item from the collection. 101 | 102 | ```php 103 | $collection = collect([1,2,3]); 104 | 105 | $currentItem = 2; 106 | 107 | $currentItem = $collection->after($currentItem); // return 3; 108 | $collection->after($currentItem); // return null; 109 | 110 | $currentItem = $collection->after(function($item) { 111 | return $item > 1; 112 | }); // return 3; 113 | ``` 114 | 115 | You can also pass a second parameter to be used as a fallback. 116 | 117 | ```php 118 | $collection = collect([1,2,3]); 119 | 120 | $currentItem = 3; 121 | 122 | $collection->after($currentItem, $collection->first()); // return 1; 123 | ``` 124 | 125 | ### `at` 126 | 127 | Retrieve an item at an index. 128 | 129 | ```php 130 | $data = new Collection([1, 2, 3]); 131 | 132 | $data->at(0); // 1 133 | $data->at(1); // 2 134 | $data->at(-1); // 3 135 | ``` 136 | 137 | ### `second` 138 | Retrieve item at the second index. 139 | 140 | ```php 141 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 142 | 143 | $data->second(); // 2 144 | ``` 145 | 146 | ### `third` 147 | Retrieve item at the third index. 148 | 149 | ```php 150 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 151 | 152 | $data->third(); // 3 153 | ``` 154 | 155 | ### `fourth` 156 | Retrieve item at the fourth index. 157 | 158 | ```php 159 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 160 | 161 | $data->fourth(); // 4 162 | ``` 163 | 164 | ### `fifth` 165 | Retrieve item at the fifth index. 166 | 167 | ```php 168 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 169 | 170 | $data->fifth(); // 5 171 | ``` 172 | 173 | ### `sixth` 174 | Retrieve item at the sixth index. 175 | 176 | ```php 177 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 178 | 179 | $data->sixth(); // 6 180 | ``` 181 | 182 | ### `seventh` 183 | Retrieve item at the seventh index. 184 | 185 | ```php 186 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 187 | 188 | $data->seventh(); // 7 189 | ``` 190 | 191 | ### `eighth` 192 | Retrieve item at the eighth index. 193 | 194 | ```php 195 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 196 | 197 | $data->eighth(); // 8 198 | ``` 199 | 200 | ### `ninth` 201 | Retrieve item at the ninth index. 202 | 203 | ```php 204 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 205 | 206 | $data->ninth(); // 9 207 | ``` 208 | 209 | ### `tenth` 210 | Retrieve item at the tenth index. 211 | 212 | ```php 213 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 214 | 215 | $data->tenth(); // 10 216 | ``` 217 | 218 | ### `getNth` 219 | Retrieve item at the nth item. 220 | 221 | ```php 222 | $data = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); 223 | 224 | $data->getNth(11); // 11 225 | ``` 226 | 227 | ### `before` 228 | 229 | Get the previous item from the collection. 230 | 231 | ```php 232 | $collection = collect([1,2,3]); 233 | 234 | $currentItem = 2; 235 | 236 | $currentItem = $collection->before($currentItem); // return 1; 237 | $collection->before($currentItem); // return null; 238 | 239 | $currentItem = $collection->before(function($item) { 240 | return $item > 2; 241 | }); // return 2; 242 | ``` 243 | 244 | You can also pass a second parameter to be used as a fallback. 245 | 246 | ```php 247 | $collection = collect([1,2,3]); 248 | 249 | $currentItem = 1; 250 | 251 | $collection->before($currentItem, $collection->last()); // return 3; 252 | ``` 253 | 254 | ### `catch` 255 | 256 | See [`Try`](#try) 257 | 258 | ### `chunkBy` 259 | 260 | Chunks the values from a collection into groups as long the given callback is true. If the optional parameter `$preserveKeys` as `true` is passed, it will preserve the original keys. 261 | 262 | ```php 263 | collect(['A', 'A', 'B', 'A'])->chunkBy(function($item) { 264 | return $item == 'A'; 265 | }); // return Collection([['A', 'A'],['B'], ['A']]) 266 | ``` 267 | 268 | ### `collectBy` 269 | 270 | Get an item at a given key, and collect it. 271 | 272 | ```php 273 | $collection = collect([ 274 | 'foo' => [1, 2, 3], 275 | 'bar' => [4, 5, 6], 276 | ]); 277 | 278 | $collection->collectBy('foo'); // Collection([1, 2, 3]) 279 | ``` 280 | 281 | You can also pass a second parameter to be used as a fallback. 282 | 283 | ```php 284 | $collection = collect([ 285 | 'foo' => [1, 2, 3], 286 | 'bar' => [4, 5, 6], 287 | ]); 288 | 289 | $collection->collectBy('baz', ['Nope']); // Collection(['Nope']) 290 | ``` 291 | 292 | ### `containsAny` 293 | 294 | Will return `true` if one or more of the given values exist in the collection. 295 | 296 | ```php 297 | $collection = collect(['a', 'b', 'c']); 298 | 299 | $collection->containsAny(['b', 'c', 'd']); // returns true 300 | $collection->containsAny(['c', 'd', 'e']); // returns true 301 | $collection->containsAny(['d', 'e', 'f']); // returns false 302 | $collection->containsAny([]); // returns false 303 | ``` 304 | 305 | ### `containsAll` 306 | 307 | Will return `true` if all given values exist in the collection. 308 | 309 | ```php 310 | $collection = collect(['a', 'b', 'c']); 311 | 312 | $collection->containsAll(['b', 'c',]); // returns true 313 | $collection->containsAll(['c', 'd']); // returns false 314 | $collection->containsAll(['d', 'e']); // returns false 315 | $collection->containsAll([]); // returns true 316 | ``` 317 | 318 | ### `eachCons` 319 | 320 | Get the following consecutive neighbours in a collection from a given chunk size. If the optional parameter `$preserveKeys` as `true` is passed, it will preserve the original keys. 321 | 322 | ```php 323 | collect([1, 2, 3, 4])->eachCons(2); // return collect([[1, 2], [2, 3], [3, 4]]) 324 | ``` 325 | 326 | ### `extract` 327 | 328 | Extract keys from a collection. This is very similar to `only`, with two key differences: 329 | 330 | - `extract` returns an array of values, not an associative array 331 | - If a value doesn't exist, it will fill the value with `null` instead of omitting it 332 | 333 | `extract` is useful when using PHP 7.1 short `list()` syntax. 334 | 335 | ```php 336 | [$name, $role] = collect($user)->extract('name', 'role.name'); 337 | ``` 338 | 339 | ### `filterMap` 340 | 341 | Map a collection and remove falsy values in one go. 342 | 343 | ```php 344 | $collection = collect([1, 2, 3, 4, 5, 6])->filterMap(function ($number) { 345 | $quotient = $number / 3; 346 | 347 | return is_integer($quotient) ? $quotient : null; 348 | }); 349 | 350 | $collection->toArray(); // returns [1, 2] 351 | ``` 352 | 353 | ### `firstOrFail` 354 | 355 | Get the first item. Throws `Spatie\CollectionMacros\Exceptions\CollectionItemNotFound` if the item was not found. 356 | 357 | ```php 358 | $collection = collect([1, 2, 3, 4, 5, 6])->firstOrFail(); 359 | 360 | $collection->toArray(); // returns [1] 361 | 362 | collect([])->firstOrFail(); // throws Spatie\CollectionMacros\Exceptions\CollectionItemNotFound 363 | ``` 364 | 365 | ### `firstOrPush` 366 | 367 | Retrieve the first item using the callable given as the first parameter. If no value exists, push the value of the second 368 | parameter into the collection. You can pass a callable as the second parameter. 369 | 370 | This method is really useful when dealing with cached class properties, where you want to store a value retrieved from an API or computationally expensive function in a collection to be used multiple times. 371 | 372 | ```php 373 | $collection = collect([1, 2, 3])->firstOrPush(fn($item) => $item === 4, 4); 374 | 375 | $collection->toArray(); // returns [1, 2, 3, 4] 376 | ``` 377 | 378 | Occasionally, you'll want to specify the target collection to be pushed to. You may pass this as a third parameter. 379 | 380 | ```php 381 | $collection = collect([1, 2, 3]); 382 | $collection->filter()->firstOrPush(fn($item) => $item === 4, 4, $collection); 383 | 384 | $collection->toArray(); // returns [1, 2, 3, 4] 385 | ``` 386 | 387 | ### `fromPairs` 388 | 389 | Transform a collection into an associative array form collection item. 390 | 391 | ```php 392 | $collection = collect([['a', 'b'], ['c', 'd'], ['e', 'f']])->fromPairs(); 393 | 394 | $collection->toArray(); // returns ['a' => 'b', 'c' => 'd', 'e' => 'f'] 395 | ``` 396 | 397 | ### `getCaseInsensitive` 398 | 399 | Get the value of a given key. 400 | 401 | If the key is a string, we'll search for the key using a case-insensitive comparison. 402 | 403 | ```php 404 | $collection = collect([ 405 | 'foo' => 'bar', 406 | ]); 407 | 408 | $collection->getCaseInsensitive('Foo'); // returns 'bar'; 409 | ``` 410 | 411 | ### `glob` 412 | 413 | Returns a collection of a `glob()` result. 414 | 415 | ```php 416 | Collection::glob('config/*.php'); 417 | ``` 418 | 419 | ### `groupByModel` 420 | 421 | Similar to `groupBy`, but groups the collection by an Eloquent model. Since the key is an object instead of an integer or string, the results are divided into separate arrays. 422 | 423 | ```php 424 | $posts->groupByModel('category'); 425 | 426 | // [ 427 | // [$categoryA, [/*...$posts*/]], 428 | // [$categoryB, [/*...$posts*/]], 429 | // ]; 430 | ``` 431 | 432 | Full signature: `groupByModel($callback, $preserveKeys, $modelKey, $itemsKey)` 433 | 434 | ### `hasCaseInsensitive` 435 | 436 | Determine if the collection contains a key with a given name. 437 | 438 | If $key is a string, we'll search for the key using a case-insensitive comparison. 439 | 440 | ```php 441 | $collection = collect([ 442 | 'foo' => 'bar', 443 | ]); 444 | 445 | $collection->hasCaseInsensitive('Foo'); // returns true; 446 | ``` 447 | 448 | ### `head` 449 | 450 | Retrieves first item from the collection. 451 | 452 | ```php 453 | $collection = collect([1,2,3]); 454 | 455 | $collection->head(); // return 1 456 | 457 | $collection = collect([]); 458 | 459 | $collection->head(); // return null 460 | ``` 461 | 462 | ### `if` 463 | 464 | The `if` macro can help branch collection chains. This is the signature of this macro: 465 | 466 | ```php 467 | if(mixed $if, mixed $then = null, mixed $else = null): mixed 468 | ``` 469 | 470 | `$if`, `$then` and `$else` can be any type. If a closure is passed to any of these parameters, then that closure will be executed and the macro will use its results. 471 | 472 | When `$if` returns a truthy value, then `$then` will be returned, otherwise `$else` will be returned. 473 | 474 | Here are some examples: 475 | 476 | ```php 477 | collect()->if(true, then: true, else: false); // returns true 478 | collect()->if(false, then: true, else: false); // returns false 479 | ``` 480 | 481 | When a closure is passed to `$if`, `$then` or `$else`, the entire collection will be passed as an argument to that closure. 482 | 483 | ```php 484 | // the `then` closure will be executed 485 | // the first element of the returned collection now contains "THIS IS THE VALUE" 486 | $collection = collect(['this is a value']) 487 | ->if( 488 | fn(Collection $collection) => $collection->contains('this is a value'), 489 | then: fn(Collection $collection) => $collection->map(fn(string $item) => strtoupper($item)), 490 | else: fn(Collection $collection) => $collection->map(fn(string $item) => Str::kebab($item)) 491 | ); 492 | 493 | // the `else` closure will be executed 494 | // the first element of the returned collection now contains "this-is-another-value" 495 | $collection = collect(['this is another value']) 496 | ->if( 497 | fn(Collection $collection) => $collection->contains('this is a value'), 498 | then: fn(Collection $collection) => $collection->map(fn(string $item) => strtoupper($item)), 499 | else: fn(Collection $collection) => $collection->map(fn(string $item) => Str::kebab($item)) 500 | ); 501 | ``` 502 | 503 | ### `ifAny` 504 | 505 | Executes the passed callable if the collection isn't empty. The entire collection will be returned. 506 | 507 | ```php 508 | collect()->ifAny(function(Collection $collection) { // empty collection so this won't get called 509 | echo 'Hello'; 510 | }); 511 | 512 | collect([1, 2, 3])->ifAny(function(Collection $collection) { // non-empty collection so this will get called 513 | echo 'Hello'; 514 | }); 515 | ``` 516 | 517 | ### `ifEmpty` 518 | 519 | Executes the passed callable if the collection is empty. The entire collection will be returned. 520 | 521 | ```php 522 | collect()->ifEmpty(function(Collection $collection) { // empty collection so this will called 523 | echo 'Hello'; 524 | }); 525 | 526 | collect([1, 2, 3])->ifEmpty(function(Collection $collection) { // non-empty collection so this won't get called 527 | echo 'Hello'; 528 | }); 529 | ``` 530 | 531 | ### `insertAfter` 532 | 533 | Inserts an item after the first occurrence of a given item and returns the updated Collection instance. 534 | Optionally a key can be given. 535 | 536 | ```php 537 | collect(['zero', 'two', 'three'])->insertAfter('zero', 'one'); 538 | // Collection contains ['zero', 'one', 'two', 'three'] 539 | 540 | collect(['zero' => 0, 'two' => 2, 'three' => 3]->insertAfter(0, 5, 'five'); 541 | // Collection contains ['zero' => 0, 'five' => 5, 'two' => 2, 'three' => 3] 542 | ``` 543 | 544 | ### `insertAfterKey` 545 | 546 | Inserts an item after a given key and returns the updated Collection instance. 547 | Optionally a key for the new item can be given. 548 | 549 | ```php 550 | collect(['zero', 'two', 'three'])->insertAfterKey(0, 'one'); 551 | // Collection contains ['zero', 'one', 'two', 'three'] 552 | 553 | collect(['zero' => 0, 'two' => 2, 'three' => 3]->insertAfterKey('zero', 5, 'five'); 554 | // Collection contains ['zero' => 0, 'five' => 5, 'two' => 2, 'three' => 3] 555 | ``` 556 | 557 | ### `insertAt` 558 | 559 | Inserts an item at a given index and returns the updated Collection instance. Optionally a key can be given. 560 | 561 | ```php 562 | collect(['zero', 'two', 'three'])->insertAt(1, 'one'); 563 | // Collection contains ['zero', 'one', 'two', 'three'] 564 | 565 | collect(['zero' => 0, 'two' => 2, 'three' => 3]->insertAt(1, 5, 'five'); 566 | // Collection contains ['zero' => 0, 'five' => 5, 'two' => 2, 'three' => 3] 567 | ``` 568 | 569 | ### `insertBefore` 570 | 571 | Inserts an item before the first occurrence of a given item and returns the updated Collection instance. 572 | Optionally a key can be given. 573 | 574 | ```php 575 | collect(['zero', 'two', 'three'])->insertBefore('two', 'one'); 576 | // Collection contains ['zero', 'one', 'two', 'three'] 577 | 578 | collect(['zero' => 0, 'two' => 2, 'three' => 3]->insertBefore(2, 5, 'five'); 579 | // Collection contains ['zero' => 0, 'five' => 5, 'two' => 2, 'three' => 3] 580 | ``` 581 | 582 | ### `insertBeforeKey` 583 | 584 | Inserts an item before a given key and returns the updated Collection instance. 585 | Optionally a key for the new item can be given. 586 | 587 | ```php 588 | collect(['zero', 'two', 'three'])->insertBeforeKey(1, 'one'); 589 | // Collection contains ['zero', 'one', 'two', 'three'] 590 | 591 | collect(['zero' => 0, 'two' => 2, 'three' => 3]->insertBeforeKey('two', 5, 'five'); 592 | // Collection contains ['zero' => 0, 'five' => 5, 'two' => 2, 'three' => 3] 593 | ``` 594 | 595 | ### `none` 596 | 597 | Checks whether a collection doesn't contain any occurrences of a given item, key-value pair, or passing truth test. The function accepts the same parameters as the `contains` collection method. 598 | 599 | ```php 600 | collect(['foo'])->none('bar'); // returns true 601 | collect(['foo'])->none('foo'); // returns false 602 | 603 | collect([['name' => 'foo']])->none('name', 'bar'); // returns true 604 | collect([['name' => 'foo']])->none('name', 'foo'); // returns false 605 | 606 | collect(['name' => 'foo'])->none(function ($key, $value) { 607 | return $key === 'name' && $value === 'bar'; 608 | }); // returns true 609 | ``` 610 | 611 | ### `paginate` 612 | 613 | Create a `LengthAwarePaginator` instance for the items in the collection. 614 | 615 | ```php 616 | collect($posts)->paginate(5); 617 | ``` 618 | 619 | This paginates the contents of `$posts` with 5 items per page. `paginate` accepts quite some options, head over to [the Laravel docs](https://laravel.com/docs/5.4/pagination) for an in-depth guide. 620 | 621 | ``` 622 | paginate(int $perPage = 15, string $pageName = 'page', ?int $page = null, ?int $total = null, array $options = []) 623 | ``` 624 | 625 | ### `path` 626 | 627 | Returns an item from the collection with multidimensional data using "dot" notation. 628 | Works the same way as native Collection's `pull` method, but without removing an item from the collection. 629 | 630 | ```php 631 | $collection = new Collection([ 632 | 'foo' => [ 633 | 'bar' => [ 634 | 'baz' => 'value', 635 | ] 636 | ] 637 | ]); 638 | 639 | $collection->path('foo.bar.baz') // 'value' 640 | ``` 641 | 642 | ### `pluckMany` 643 | 644 | Returns a collection with only the specified keys. 645 | 646 | ```php 647 | $collection = collect([ 648 | ['a' => 1, 'b' => 10, 'c' => 100], 649 | ['a' => 2, 'b' => 20, 'c' => 200], 650 | ]); 651 | 652 | $collection->pluckMany(['a', 'b']); 653 | 654 | // returns 655 | // collect([ 656 | // ['a' => 1, 'b' => 10], 657 | // ['a' => 2, 'b' => 20], 658 | // ]); 659 | ``` 660 | 661 | ### `pluckManyValues` 662 | 663 | Returns a collection with only the specified keys' values. 664 | 665 | ```php 666 | $collection = collect([ 667 | ['a' => 1, 'b' => 10, 'c' => 100], 668 | ['a' => 2, 'b' => 20, 'c' => 200], 669 | ]); 670 | 671 | $collection->pluckMany(['a', 'b']); 672 | 673 | // returns 674 | // collect([ 675 | // [1, 10], 676 | // [2, 20], 677 | // ]); 678 | ``` 679 | 680 | ### `pluckToArray` 681 | 682 | Returns array of values of a given key. 683 | 684 | ```php 685 | $collection = collect([ 686 | ['a' => 1, 'b' => 10], 687 | ['a' => 2, 'b' => 20], 688 | ['a' => 3, 'b' => 30] 689 | ]); 690 | 691 | $collection->pluckToArray('a'); // returns [1, 2, 3] 692 | ``` 693 | 694 | ### `prioritize` 695 | 696 | Move elements to the start of the collection. 697 | 698 | ```php 699 | $collection = collect([ 700 | ['id' => 1], 701 | ['id' => 2], 702 | ['id' => 3], 703 | ]); 704 | 705 | $collection 706 | ->prioritize(function(array $item) { 707 | return $item['id'] === 2; 708 | }) 709 | ->pluck('id') 710 | ->toArray(); // returns [2, 1, 3] 711 | ``` 712 | 713 | ### `recursive` 714 | 715 | Convert an array and its children to collection using recursion. 716 | 717 | ```php 718 | collect([ 719 | 'item' => [ 720 | 'children' => [] 721 | ] 722 | ])->recursive(); 723 | 724 | // subsequent arrays are now collections 725 | ``` 726 | 727 | In some cases you may not want to turn all the children into a collection. You can convert only to a certain depth by providing a number to the recursive method. 728 | 729 | ```php 730 | collect([ 731 | 'item' => [ 732 | 'children' => [ 733 | 'one' => [1], 734 | 'two' => [2] 735 | ] 736 | ] 737 | ])->recursive(1); // Collection(['item' => Collection(['children' => ['one' => [1], 'two' => [2]]])]) 738 | ``` 739 | 740 | This can be useful when you know that at a certain depth it'll not be necessary or that it may break your code. 741 | 742 | ```php 743 | collect([ 744 | 'item' => [ 745 | 'children' => [ 746 | 'one' => [1], 747 | 'two' => [2] 748 | ] 749 | ] 750 | ]) 751 | ->recursive(1) 752 | ->map(function ($item) { 753 | return $item->map(function ($children) { 754 | return $children->mapInto(Model::class); 755 | }); 756 | }); // Collection(['item' => Collection(['children' => ['one' => Model(), 'two' => Model()]])]) 757 | 758 | // If we do not pass a max depth we will get the error "Argument #1 ($attributes) must be of type array" 759 | ``` 760 | 761 | ### `rotate` 762 | 763 | Rotate the items in the collection with given offset 764 | 765 | ```php 766 | $collection = collect([1, 2, 3, 4, 5, 6]); 767 | 768 | $rotate = $collection->rotate(1); 769 | 770 | $rotate->toArray(); 771 | 772 | // [2, 3, 4, 5, 6, 1] 773 | ``` 774 | 775 | ### `sectionBy` 776 | 777 | Splits a collection into sections grouped by a given key. Similar to `groupBy` but respects the order of the items in the collection and reuses existing keys. 778 | 779 | ```php 780 | $collection = collect([ 781 | ['name' => 'Lesson 1', 'module' => 'Basics'], 782 | ['name' => 'Lesson 2', 'module' => 'Basics'], 783 | ['name' => 'Lesson 3', 'module' => 'Advanced'], 784 | ['name' => 'Lesson 4', 'module' => 'Advanced'], 785 | ['name' => 'Lesson 5', 'module' => 'Basics'], 786 | ]); 787 | 788 | $collection->sectionBy('module'); 789 | 790 | // [ 791 | // ['Basics', [ 792 | // ['name' => 'Lesson 1', 'module' => 'Basics'], 793 | // ['name' => 'Lesson 2', 'module' => 'Basics'], 794 | // ]], 795 | // ['Advanced', [ 796 | // ['name' => 'Lesson 3', 'module' => 'Advanced'], 797 | // ['name' => 'Lesson 4', 'module' => 'Advanced'], 798 | // ]], 799 | // ['Basics', [ 800 | // ['name' => 'Lesson 5', 'module' => 'Basics'], 801 | // ]], 802 | // ]; 803 | ``` 804 | 805 | Full signature: `sectionBy($callback, $preserveKeys, $sectionKey, $itemsKey)` 806 | 807 | ### `simplePaginate` 808 | 809 | Create a `Paginator` instance for the items in the collection. 810 | 811 | ```php 812 | collect($posts)->simplePaginate(5); 813 | ``` 814 | 815 | This paginates the contents of `$posts` with 5 items per page. `simplePaginate` accepts quite some options, head over to [the Laravel docs](https://laravel.com/docs/5.4/pagination) for an in-depth guide. 816 | 817 | ``` 818 | simplePaginate(int $perPage = 15, string $pageName = 'page', ?int $page = null, ?int $total = null, array $options = []) 819 | ``` 820 | 821 | For a in-depth guide on pagination, check out [the Laravel docs](https://laravel.com/docs/5.4/pagination). 822 | 823 | ### `sliceBefore` 824 | 825 | Slice the values out from a collection before the given callback is true. If the optional parameter `$preserveKeys` as `true` is passed, it will preserve the original keys. 826 | 827 | ```php 828 | collect([20, 51, 10, 50, 66])->sliceBefore(function($item) { 829 | return $item > 50; 830 | }); // return collect([[20],[51, 10, 50], [66]) 831 | ``` 832 | 833 | ### `tail` 834 | 835 | Extract the tail from a collection. So everything except the first element. It's a shorthand for `slice(1)->values()`, but nevertheless very handy. If the optional parameter `$preserveKeys` as `true` is passed, it will preserve the keys and fallback to `slice(1)`. 836 | 837 | ```php 838 | collect([1, 2, 3])->tail(); // return collect([2, 3]) 839 | ``` 840 | 841 | ### `toPairs` 842 | 843 | Transform a collection into an array with pairs. 844 | 845 | ```php 846 | $collection = collect(['a' => 'b', 'c' => 'd', 'e' => 'f'])->toPairs(); 847 | 848 | $collection->toArray(); // returns ['a', 'b'], ['c', 'd'], ['e', 'f'] 849 | ``` 850 | 851 | ### `transpose` 852 | 853 | The goal of transpose is to rotate a multidimensional array, turning the rows into columns and the columns into rows. 854 | 855 | ```php 856 | collect([ 857 | ['Jane', 'Bob', 'Mary'], 858 | ['jane@example.com', 'bob@example.com', 'mary@example.com'], 859 | ['Doctor', 'Plumber', 'Dentist'], 860 | ])->transpose()->toArray(); 861 | 862 | // [ 863 | // ['Jane', 'jane@example.com', 'Doctor'], 864 | // ['Bob', 'bob@example.com', 'Plumber'], 865 | // ['Mary', 'mary@example.com', 'Dentist'], 866 | // ] 867 | ``` 868 | 869 | ### `try` 870 | 871 | If any of the methods between `try` and `catch` throw an exception, then the exception can be handled in `catch`. 872 | 873 | ```php 874 | collect(['a', 'b', 'c', 1, 2, 3]) 875 | ->try() 876 | ->map(fn ($letter) => strtoupper($letter)) 877 | ->each(function() { 878 | throw new Exception('Explosions in the sky'); 879 | }) 880 | ->catch(function (Exception $exception) { 881 | // handle exception here 882 | }) 883 | ->map(function() { 884 | // further operations can be done, if the exception wasn't rethrow in the `catch` 885 | }); 886 | ``` 887 | 888 | While the methods are named `try`/`catch` for familiarity with PHP, the collection itself behaves more like a database transaction. So when an exception is thrown, the original collection (before the try) is returned. 889 | 890 | You may gain access to the collection within catch by adding a second parameter to your handler. You may also manipulate the collection within catch by returning a value. 891 | 892 | ```php 893 | $collection = collect(['a', 'b', 'c', 1, 2, 3]) 894 | ->try() 895 | ->map(function ($item) { 896 | throw new Exception(); 897 | }) 898 | ->catch(function (Exception $exception, $collection) { 899 | return collect(['d', 'e', 'f']); 900 | }) 901 | ->map(function ($item) { 902 | return strtoupper($item); 903 | }); 904 | 905 | // ['D', 'E', 'F'] 906 | ``` 907 | 908 | ### `validate` 909 | 910 | Returns `true` if the given `$callback` returns true for every item. If `$callback` is a string or an array, regard it as a validation rule. 911 | 912 | ```php 913 | collect(['foo', 'foo'])->validate(function ($item) { 914 | return $item === 'foo'; 915 | }); // returns true 916 | 917 | 918 | collect(['sebastian@spatie.be', 'bla'])->validate('email'); // returns false 919 | collect(['sebastian@spatie.be', 'freek@spatie.be'])->validate('email'); // returns true 920 | ``` 921 | 922 | ### `weightedRandom` 923 | 924 | Returns a random item by a weight. In this example, the item with `a` has the most chance to get picked, and the item with `c` the least. 925 | 926 | ```php 927 | // pass the field name that should be used as a weight 928 | 929 | $randomItem = collect([ 930 | ['value' => 'a', 'weight' => 30], 931 | ['value' => 'b', 'weight' => 20], 932 | ['value' => 'c', 'weight' => 10], 933 | ])->weightedRandom('weight'); 934 | ``` 935 | 936 | Alternatively, you can pass a callable to get the weight. 937 | 938 | ```php 939 | $randomItem = collect([ 940 | ['value' => 'a', 'weight' => 30], 941 | ['value' => 'b', 'weight' => 20], 942 | ['value' => 'c', 'weight' => 10], 943 | ])->weightedRandom(function(array $item) { 944 | return $item['weight']; 945 | }); 946 | ``` 947 | 948 | 949 | 950 | ### `withSize` 951 | 952 | Create a new collection with the specified amount of items. 953 | 954 | ```php 955 | Collection::withSize(1)->toArray(); // return [1]; 956 | Collection::withSize(5)->toArray(); // return [1,2,3,4,5]; 957 | ``` 958 | 959 | ## Changelog 960 | 961 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 962 | 963 | ## Testing 964 | 965 | ``` bash 966 | $ composer test 967 | ``` 968 | 969 | ## Contributing 970 | 971 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 972 | 973 | ## Security 974 | 975 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. 976 | 977 | ## Credits 978 | 979 | - [Freek Van der Herten](https://github.com/freekmurze) 980 | - [Sebastian De Deyne](https://github.com/sebastiandedeyne) 981 | - [All Contributors](../../contributors) 982 | 983 | ## About Spatie 984 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 985 | 986 | ## License 987 | 988 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 989 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-collection-macros", 3 | "description": "A set of useful Laravel collection macros", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-collection-macros" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-collection-macros", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "homepage": "https://github.com/freekmurze", 15 | "role": "Developer" 16 | }, 17 | { 18 | "name": "Sebastian De Deyne", 19 | "email": "sebastian@spatie.be", 20 | "homepage": "https://github.com/sebastiandedeyne", 21 | "role": "Developer" 22 | } 23 | ], 24 | "require": { 25 | "php": "^8.2", 26 | "illuminate/support": "^12.0" 27 | }, 28 | "require-dev": { 29 | "amphp/parallel": "^2.2", 30 | "amphp/parallel-functions": "^2.0", 31 | "mockery/mockery": "^1.6.2", 32 | "orchestra/testbench": "^10.0", 33 | "pestphp/pest": "^3.7", 34 | "phpunit/phpunit": "^11.5.3", 35 | "spatie/laravel-ray": "^1.32.5", 36 | "symfony/stopwatch": "^6.3|^7.0" 37 | }, 38 | "suggest": { 39 | "amphp/parallel-functions": "Required when using the parallel*-macros." 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "Spatie\\CollectionMacros\\": "src" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "Spatie\\CollectionMacros\\Test\\": "tests" 49 | } 50 | }, 51 | "scripts": { 52 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes", 53 | "test": "vendor/bin/pest" 54 | }, 55 | "config": { 56 | "sort-packages": true, 57 | "allow-plugins": { 58 | "pestphp/pest-plugin": true 59 | } 60 | }, 61 | "extra": { 62 | "laravel": { 63 | "providers": [ 64 | "Spatie\\CollectionMacros\\CollectionMacroServiceProvider" 65 | ] 66 | } 67 | }, 68 | "minimum-stability": "dev", 69 | "prefer-stable": true 70 | } 71 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests 6 | 7 | 8 | 9 | 10 | 11 | ./app 12 | ./src 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/CollectionMacroServiceProvider.php: -------------------------------------------------------------------------------- 1 | macros()) 13 | ->reject(fn ($class, $macro) => Collection::hasMacro($macro)) 14 | ->each(fn ($class, $macro) => Collection::macro($macro, app($class)())); 15 | } 16 | 17 | private function macros(): array 18 | { 19 | return [ 20 | 'at' => \Spatie\CollectionMacros\Macros\At::class, 21 | 'chunkBy' => \Spatie\CollectionMacros\Macros\ChunkBy::class, 22 | 'collectBy' => \Spatie\CollectionMacros\Macros\CollectBy::class, 23 | 'containsAll' => \Spatie\CollectionMacros\Macros\ContainsAll::class, 24 | 'containsAny' => \Spatie\CollectionMacros\Macros\ContainsAny::class, 25 | 'eachCons' => \Spatie\CollectionMacros\Macros\EachCons::class, 26 | 'eighth' => \Spatie\CollectionMacros\Macros\Eighth::class, 27 | 'extract' => \Spatie\CollectionMacros\Macros\Extract::class, 28 | 'fifth' => \Spatie\CollectionMacros\Macros\Fifth::class, 29 | 'filterMap' => \Spatie\CollectionMacros\Macros\FilterMap::class, 30 | 'firstOrFail' => \Spatie\CollectionMacros\Macros\FirstOrFail::class, 31 | 'firstOrPush' => \Spatie\CollectionMacros\Macros\FirstOrPush::class, 32 | 'fourth' => \Spatie\CollectionMacros\Macros\Fourth::class, 33 | 'fromPairs' => \Spatie\CollectionMacros\Macros\FromPairs::class, 34 | 'getCaseInsensitive' => \Spatie\CollectionMacros\Macros\GetCaseInsensitive::class, 35 | 'getNth' => \Spatie\CollectionMacros\Macros\GetNth::class, 36 | 'glob' => \Spatie\CollectionMacros\Macros\Glob::class, 37 | 'groupByModel' => \Spatie\CollectionMacros\Macros\GroupByModel::class, 38 | 'hasCaseInsensitive' => \Spatie\CollectionMacros\Macros\HasCaseInsensitive::class, 39 | 'head' => \Spatie\CollectionMacros\Macros\Head::class, 40 | 'if' => \Spatie\CollectionMacros\Macros\IfMacro::class, 41 | 'ifAny' => \Spatie\CollectionMacros\Macros\IfAny::class, 42 | 'ifEmpty' => \Spatie\CollectionMacros\Macros\IfEmpty::class, 43 | 'insertAfter' => \Spatie\CollectionMacros\Macros\InsertAfter::class, 44 | 'insertAfterKey' => \Spatie\CollectionMacros\Macros\InsertAfterKey::class, 45 | 'insertAt' => \Spatie\CollectionMacros\Macros\InsertAt::class, 46 | 'insertBefore' => \Spatie\CollectionMacros\Macros\InsertBefore::class, 47 | 'insertBeforeKey' => \Spatie\CollectionMacros\Macros\InsertBeforeKey::class, 48 | 'ninth' => \Spatie\CollectionMacros\Macros\Ninth::class, 49 | 'none' => \Spatie\CollectionMacros\Macros\None::class, 50 | 'paginate' => \Spatie\CollectionMacros\Macros\Paginate::class, 51 | 'parallelMap' => \Spatie\CollectionMacros\Macros\ParallelMap::class, 52 | 'path' => \Spatie\CollectionMacros\Macros\Path::class, 53 | 'pluckMany' => \Spatie\CollectionMacros\Macros\PluckMany::class, 54 | 'pluckManyValues' => \Spatie\CollectionMacros\Macros\PluckManyValues::class, 55 | 'pluckToArray' => \Spatie\CollectionMacros\Macros\PluckToArray::class, 56 | 'prioritize' => \Spatie\CollectionMacros\Macros\Prioritize::class, 57 | 'recursive' => \Spatie\CollectionMacros\Macros\Recursive::class, 58 | 'rotate' => \Spatie\CollectionMacros\Macros\Rotate::class, 59 | 'second' => \Spatie\CollectionMacros\Macros\Second::class, 60 | 'sectionBy' => \Spatie\CollectionMacros\Macros\SectionBy::class, 61 | 'seventh' => \Spatie\CollectionMacros\Macros\Seventh::class, 62 | 'simplePaginate' => \Spatie\CollectionMacros\Macros\SimplePaginate::class, 63 | 'sixth' => \Spatie\CollectionMacros\Macros\Sixth::class, 64 | 'sliceBefore' => \Spatie\CollectionMacros\Macros\SliceBefore::class, 65 | 'tail' => \Spatie\CollectionMacros\Macros\Tail::class, 66 | 'tenth' => \Spatie\CollectionMacros\Macros\Tenth::class, 67 | 'third' => \Spatie\CollectionMacros\Macros\Third::class, 68 | 'toPairs' => \Spatie\CollectionMacros\Macros\ToPairs::class, 69 | 'transpose' => \Spatie\CollectionMacros\Macros\Transpose::class, 70 | 'try' => \Spatie\CollectionMacros\Macros\TryCatch::class, 71 | 'validate' => \Spatie\CollectionMacros\Macros\Validate::class, 72 | 'weightedRandom' => \Spatie\CollectionMacros\Macros\WeightedRandom::class, 73 | 'withSize' => \Spatie\CollectionMacros\Macros\WithSize::class, 74 | ]; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Exceptions/CollectionItemNotFound.php: -------------------------------------------------------------------------------- 1 | collection = $collection; 22 | } 23 | 24 | public function __call(string $method, array $parameters): self 25 | { 26 | $this->calledMethods[] = ['name' => $method, 'parameters' => $parameters]; 27 | 28 | return $this; 29 | } 30 | 31 | public function catch(Closure ...$handlers): Enumerable 32 | { 33 | $originalCollection = $this->collection; 34 | 35 | try { 36 | foreach ($this->calledMethods as $calledMethod) { 37 | $this->collection = $this->collection->{$calledMethod['name']}(...$calledMethod['parameters']); 38 | } 39 | } catch (Throwable $exception) { 40 | foreach ($handlers as $callable) { 41 | $type = $this->exceptionType($callable); 42 | if ($exception instanceof $type) { 43 | return $callable($exception, $originalCollection) ?? $originalCollection; 44 | } 45 | } 46 | 47 | throw $exception; 48 | } 49 | 50 | return $this->collection; 51 | } 52 | 53 | private function exceptionType(Closure $callable): string 54 | { 55 | $reflection = new ReflectionFunction($callable); 56 | 57 | if (empty($reflection->getParameters())) { 58 | return Throwable::class; 59 | } 60 | 61 | return optional($reflection->getParameters()[0]->getType())->getName() ?? Throwable::class; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Macros/At.php: -------------------------------------------------------------------------------- 1 | slice($index, 1)->first(); 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Macros/ChunkBy.php: -------------------------------------------------------------------------------- 1 | sliceBefore(function ($item, $prevItem) use ($callback) { 19 | return $callback($item) !== $callback($prevItem); 20 | }, $preserveKeys); 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Macros/CollectBy.php: -------------------------------------------------------------------------------- 1 | items, $key, $default)); 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Macros/ContainsAll.php: -------------------------------------------------------------------------------- 1 | unique(); 22 | 23 | return $this->intersect($values)->count() == $values->count(); 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Macros/ContainsAny.php: -------------------------------------------------------------------------------- 1 | unique(); 22 | 23 | return $this->intersect($values)->count() > 0; 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Macros/EachCons.php: -------------------------------------------------------------------------------- 1 | count() - $chunkSize + 1; 18 | $result = collect(range(0, $size))->reduce(function ($result, $index) use ($chunkSize, $preserveKeys) { 19 | $next = $this->slice($index, $chunkSize); 20 | 21 | return $next->count() === $chunkSize ? $result->push($preserveKeys ? $next : $next->values()) : $result; 22 | }, new static([])); 23 | 24 | return $preserveKeys ? $result : $result->values(); 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Macros/Eighth.php: -------------------------------------------------------------------------------- 1 | skip(7)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/Extract.php: -------------------------------------------------------------------------------- 1 | push( 25 | data_get($this->items, $key) 26 | ); 27 | }, new static()); 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Macros/Fifth.php: -------------------------------------------------------------------------------- 1 | skip(4)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/FilterMap.php: -------------------------------------------------------------------------------- 1 | map($callback)->filter(); 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Macros/FirstOrFail.php: -------------------------------------------------------------------------------- 1 | first())) { 20 | return $item; 21 | } 22 | 23 | throw new CollectionItemNotFound('No items found in collection.'); 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Macros/FirstOrPush.php: -------------------------------------------------------------------------------- 1 | first($callback) ?? tap( 27 | value($value), 28 | fn ($resolved) => ($instance ?? $this)->push($resolved) 29 | ); 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Macros/Fourth.php: -------------------------------------------------------------------------------- 1 | skip(3)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/FromPairs.php: -------------------------------------------------------------------------------- 1 | reduce(function ($assoc, array $keyValuePair): Collection { 18 | [$key, $value] = $keyValuePair; 19 | $assoc[$key] = $value; 20 | 21 | return $assoc; 22 | }, new static()); 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Macros/GetCaseInsensitive.php: -------------------------------------------------------------------------------- 1 | search(function ($value, $collectionKey) use ($key) { 24 | return strcasecmp($collectionKey, $key) === 0; 25 | }); 26 | 27 | if ($matchingKey === false) { 28 | return null; 29 | } 30 | 31 | return $this->get($matchingKey); 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Macros/GetNth.php: -------------------------------------------------------------------------------- 1 | slice($nth - 1, 1)->first(); 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Macros/Glob.php: -------------------------------------------------------------------------------- 1 | valueRetriever($callback); 25 | 26 | return $this->groupBy(function ($item) use ($callback) { 27 | return $callback($item)->getKey(); 28 | }, $preserveKeys)->map(function (Collection $items) use ($callback, $modelKey, $itemsKey) { 29 | return [ 30 | $modelKey => $callback($items->first()), 31 | $itemsKey => $items, 32 | ]; 33 | })->values(); 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Macros/HasCaseInsensitive.php: -------------------------------------------------------------------------------- 1 | search(function ($value, $collectionKey) use ($key) { 24 | return strcasecmp($collectionKey, $key) === 0; 25 | }); 26 | 27 | return $matchingKey !== false; 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Macros/Head.php: -------------------------------------------------------------------------------- 1 | first(); 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Macros/IfAny.php: -------------------------------------------------------------------------------- 1 | isEmpty()) { 22 | $callback($this); 23 | } 24 | 25 | return $this; 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Macros/IfEmpty.php: -------------------------------------------------------------------------------- 1 | isEmpty()) { 18 | $callback($this); 19 | } 20 | 21 | return $this; 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Macros/IfMacro.php: -------------------------------------------------------------------------------- 1 | items); 24 | 25 | return $this->insertAfterKey($afterKey, $item, $key); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Macros/InsertAfterKey.php: -------------------------------------------------------------------------------- 1 | items)); 24 | 25 | return $this->insertAt($index + 1, $item, $key); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Macros/InsertAt.php: -------------------------------------------------------------------------------- 1 | splice($index); 24 | $this->items = isset($key) 25 | ? $this->put($key, $item)->merge($after)->toArray() 26 | : $this->push($item)->merge($after)->toArray(); 27 | 28 | return $this; 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Macros/InsertBefore.php: -------------------------------------------------------------------------------- 1 | items); 24 | 25 | return $this->insertBeforeKey($beforeKey, $item, $key); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Macros/InsertBeforeKey.php: -------------------------------------------------------------------------------- 1 | items)); 24 | 25 | return $this->insertAt($index, $item, $key); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Macros/Ninth.php: -------------------------------------------------------------------------------- 1 | skip(8)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/None.php: -------------------------------------------------------------------------------- 1 | contains($key, $value); 26 | } 27 | 28 | return ! $this->contains($key); 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Macros/Paginate.php: -------------------------------------------------------------------------------- 1 | forPage($page, $perPage)->values(); 27 | 28 | $total = $total ?: $this->count(); 29 | 30 | $options += [ 31 | 'path' => LengthAwarePaginator::resolveCurrentPath(), 32 | 'pageName' => $pageName, 33 | ]; 34 | 35 | return new LengthAwarePaginator($results, $total, $perPage, $page, $options); 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Macros/ParallelMap.php: -------------------------------------------------------------------------------- 1 | items, $callback, $pool); 42 | 43 | return new static(wait($promises)); 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Macros/Path.php: -------------------------------------------------------------------------------- 1 | items, $key, $default); 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Macros/PluckMany.php: -------------------------------------------------------------------------------- 1 | map(function ($item) use ($keys) { 26 | if ($item instanceof Collection) { 27 | if (Arr::accessible($item->all())) { 28 | return new static(pluckManyHelper($item->all(), $keys)); 29 | } 30 | 31 | return $item->only($keys); 32 | } 33 | 34 | if (Arr::accessible($item)) { 35 | return pluckManyHelper($item, $keys); 36 | } 37 | 38 | // ArrayAccess handling not required, Arr::accessible includes it. 39 | 40 | return (object) pluckManyHelper(get_object_vars($item), $keys); 41 | }); 42 | }; 43 | } 44 | } 45 | 46 | function pluckManyHelper($item, $keys): array 47 | { 48 | $result = []; 49 | foreach ($keys as $key) { 50 | if (Arr::has($item, $key)) { 51 | $result[$key] = Arr::get($item, $key); 52 | } 53 | } 54 | 55 | return $result; 56 | } 57 | -------------------------------------------------------------------------------- /src/Macros/PluckManyValues.php: -------------------------------------------------------------------------------- 1 | pluckMany($keys)->map(function ($item) { 25 | if ($item instanceof Collection) { 26 | return $item->values(); 27 | } 28 | 29 | if (is_array($item)) { 30 | return array_values($item); 31 | } 32 | 33 | return (object) array_values(get_object_vars($item)); 34 | }); 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Macros/PluckToArray.php: -------------------------------------------------------------------------------- 1 | pluck($value, $key)->toArray(); 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Macros/Prioritize.php: -------------------------------------------------------------------------------- 1 | reject($callable); 22 | 23 | return $this 24 | ->filter($callable) 25 | ->union($nonPrioritized); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Macros/Recursive.php: -------------------------------------------------------------------------------- 1 | map(function ($value) use ($depth, $maxDepth) { 23 | if ($depth > $maxDepth) { 24 | return $value; 25 | } 26 | 27 | if (is_array($value) || is_object($value)) { 28 | return collect($value)->recursive($maxDepth, $depth + 1); 29 | } 30 | 31 | return $value; 32 | }); 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Macros/Rotate.php: -------------------------------------------------------------------------------- 1 | isEmpty()) { 22 | return new static(); 23 | } 24 | 25 | $count = $this->count(); 26 | 27 | $offset %= $count; 28 | 29 | if ($offset < 0) { 30 | $offset += $count; 31 | } 32 | 33 | return new static($this->slice($offset)->merge($this->take($offset))); 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Macros/Second.php: -------------------------------------------------------------------------------- 1 | skip(1)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/SectionBy.php: -------------------------------------------------------------------------------- 1 | valueRetriever($key); 25 | 26 | $results = new Collection(); 27 | 28 | foreach ($this->items as $key => $value) { 29 | $sectionName = $sectionNameRetriever($value); 30 | 31 | if (! $results->last() || $results->last()->get($sectionKey) !== $sectionName) { 32 | $results->push(new Collection([ 33 | $sectionKey => $sectionName, 34 | $itemsKey => new Collection(), 35 | ])); 36 | } 37 | 38 | $results->last()->get($itemsKey)->offsetSet($preserveKeys ? $key : null, $value); 39 | } 40 | 41 | return $results; 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Macros/Seventh.php: -------------------------------------------------------------------------------- 1 | skip(6)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/SimplePaginate.php: -------------------------------------------------------------------------------- 1 | slice(($page - 1) * $perPage)->take($perPage + 1); 26 | 27 | $options += [ 28 | 'path' => Paginator::resolveCurrentPath(), 29 | 'pageName' => $pageName, 30 | ]; 31 | 32 | return new Paginator($results, $perPage, $page, $options); 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Macros/Sixth.php: -------------------------------------------------------------------------------- 1 | skip(5)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/SliceBefore.php: -------------------------------------------------------------------------------- 1 | isEmpty()) { 23 | return new static(); 24 | } 25 | 26 | if (! $preserveKeys) { 27 | $sliced = new static([ 28 | new static([$this->first()]), 29 | ]); 30 | 31 | return $this->eachCons(2)->reduce(function ($sliced, $previousAndCurrent) use ($callback) { 32 | [$previousItem, $item] = $previousAndCurrent; 33 | 34 | $callback($item, $previousItem) 35 | ? $sliced->push(new static([$item])) 36 | : $sliced->last()->push($item); 37 | 38 | return $sliced; 39 | }, $sliced); 40 | } 41 | 42 | $sliced = new static([$this->take(1)]); 43 | 44 | return $this->eachCons(2, $preserveKeys)->reduce(function ($sliced, $previousAndCurrent) use ($callback) { 45 | $previousItem = $previousAndCurrent->take(1); 46 | $item = $previousAndCurrent->take(-1); 47 | 48 | $itemKey = $item->keys()->first(); 49 | $valuesItem = $item->first(); 50 | $valuesPreviousItem = $previousItem->first(); 51 | 52 | $callback($valuesItem, $valuesPreviousItem) 53 | ? $sliced->push($item) 54 | : $sliced->last()->put($itemKey, $valuesItem); 55 | 56 | return $sliced; 57 | }, $sliced); 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Macros/Tail.php: -------------------------------------------------------------------------------- 1 | slice(1)->values() : $this->slice(1); 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Macros/Tenth.php: -------------------------------------------------------------------------------- 1 | skip(9)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/Third.php: -------------------------------------------------------------------------------- 1 | skip(2)->first(); 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Macros/ToPairs.php: -------------------------------------------------------------------------------- 1 | keys()->map(function ($key) { 20 | return [$key, $this->items[$key]]; 21 | }); 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Macros/Transpose.php: -------------------------------------------------------------------------------- 1 | isEmpty()) { 22 | return new static(); 23 | } 24 | 25 | $firstItem = $this->first(); 26 | 27 | $expectedLength = is_countable($firstItem) ? count($firstItem) : 0; 28 | 29 | array_walk($this->items, function ($row) use ($expectedLength) { 30 | if (is_countable($row) && count($row) !== $expectedLength) { 31 | throw new \LengthException("Element's length must be equal."); 32 | } 33 | }); 34 | 35 | $items = array_map(function (...$items) { 36 | return new static($items); 37 | }, ...array_map(function ($items) { 38 | return $this->getArrayableItems($items); 39 | }, array_values($this->items))); 40 | 41 | return new static($items); 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Macros/TryCatch.php: -------------------------------------------------------------------------------- 1 | $item]; 26 | } 27 | 28 | if (! is_array($validationRule)) { 29 | $validationRule = ['default' => $validationRule]; 30 | } 31 | 32 | return app('validator')->make($item, $validationRule)->passes(); 33 | }; 34 | } 35 | 36 | foreach ($this->items as $item) { 37 | if (! $callback($item)) { 38 | return false; 39 | } 40 | } 41 | 42 | return true; 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Macros/WeightedRandom.php: -------------------------------------------------------------------------------- 1 | items) 21 | ->map(function ($item) use ($weightAttribute, &$range) { 22 | $weightAttribute = $weightAttribute($item); 23 | $range += $weightAttribute; 24 | 25 | return [ 26 | 'range' => $range, 27 | 'weight' => $weightAttribute, 28 | 'item' => $item, 29 | ]; 30 | }) 31 | ->filter(function (array $weightedItem) { 32 | return $weightedItem['weight'] > 0; 33 | }); 34 | 35 | if ($weightedItems->isEmpty()) { 36 | return $this->random(); 37 | } 38 | 39 | 40 | $randomNumber = rand(1, $range); 41 | 42 | $itemAndRange = $weightedItems 43 | ->first(function ($weightedItem) use ($randomNumber) { 44 | return $weightedItem['range'] >= $randomNumber; 45 | }); 46 | 47 | return $itemAndRange['item'] ?? $default; 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Macros/WithSize.php: -------------------------------------------------------------------------------- 1 |