The Maginium\Framework\Support\Collection class provides a fluent, convenient wrapper for working with arrays of data. For example, check out the following code. We'll use the collect helper to create a new collection instance from the array, run the strtoupper function on each element, and then remove all empty elements:
As you can see, the Collection class allows you to chain its methods to perform fluent mapping and reducing of the underlying array. In general, collections are immutable, meaning every Collection method returns an entirely new Collection instance.
As mentioned above, the collect helper returns a new Maginium\Framework\Support\Collection instance for the given array. So, creating a collection is as simple as:
$collection = collect([1, 2, 3]);
The results of Eloquent queries are always returned as Collection instances.
Collections are "macroable", which allows you to add additional methods to the Collection class at run time. The Maginium\Framework\Support\Collection class' macro method accepts a closure that will be executed when your macro is called. The macro closure may access the collection's other methods via $this, just as if it were a real method of the collection class. For example, the following code adds a toUpper method to the Collection class:
use Maginium\Framework\Support\Collection;
use Maginium\Framework\Support\Str;
Collection::macro('toUpper', function () {
return $this->map(function (string $value) {
return Str::upper($value);
});
});
$collection = collect(['first', 'second']);
$upper = $collection->toUpper();
// ['FIRST', 'SECOND']
Typically, you should declare collection macros in the boot method of a service provider.
For the majority of the remaining collection documentation, we'll discuss each method available in the Collection class. Remember, all of these methods may be chained to fluently manipulate the underlying array. Furthermore, almost every method returns a new Collection instance, allowing you to preserve the original copy of the collection when necessary:
This method searches for the given item using "loose" comparison, meaning a string containing an integer value will be considered equal to an integer of the same value. To use "strict" comparison, you may provide the strict argument to the method:
The before method is the opposite of the after method. It returns the item before the given item. null is returned if the given item is not found or is the first item:
This method is especially useful in views when working with a grid system such as Bootstrap. For example, imagine you have a collection of Eloquent models you want to display in a grid:
@foreach ($products->chunk(3) as $chunk)
<div class="row">
@foreach ($chunk as $product)
<div class="col-xs-4">{{ $product->name }}</div>
@endforeach
</div>
@endforeach
The chunkWhile method breaks the collection into multiple, smaller collections based on the evaluation of the given callback. The $chunk variable passed to the closure may be used to inspect the previous element:
The \`collect\` method is especially useful when you have an instance of \`Enumerable\` and need a non-lazy collection instance. Since \`collect()\` is part of the \`Enumerable\` contract, you can safely use it to get a \`Collection\` instance.
The concat method numerically reindexes keys for items concatenated onto the original collection. To maintain keys in associative collections, see the merge method.
The contains method determines whether the collection contains a given item. You may pass a closure to the contains method to determine if an element exists in the collection matching a given truth test:
The contains method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the containsStrict method to filter using "strict" comparisons.
For the inverse of contains, see the doesntContain method.
The countBy method counts the occurrences of values in the collection. By default, the method counts the occurrences of every element, allowing you to count certain "types" of elements in the collection:
The crossJoin method cross joins the collection's values among the given arrays or collections, returning a Cartesian product with all possible permutations:
The diff method compares the collection against another collection or a plain PHP array based on its values. This method will return the values in the original collection that are not present in the given collection:
The diffAssoc method compares the collection against another collection or a plain PHP array based on its keys and values. This method will return the key / value pairs in the original collection that are not present in the given collection:
The callback must be a comparison function that returns an integer less than, equal to, or greater than zero. For more information, refer to the PHP documentation on array_diff_uassoc, which is the PHP function that the diffAssocUsing method utilizes internally.
The diffKeys method compares the collection against another collection or a plain PHP array based on its keys. This method will return the key / value pairs in the original collection that are not present in the given collection:
The doesntContain method determines whether the collection does not contain a given item. You may pass a closure to the doesntContain method to determine if an element does not exist in the collection matching a given truth test:
The doesntContain method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value.
The ensure method may be used to verify that all elements of a collection are of a given type or list of types. Otherwise, an UnexpectedValueException will be thrown:
The firstOrFail method is identical to the first method; however, if no result is found, an Maginium\Framework\Support\ItemNotFoundException exception will be thrown:
You may also call the firstOrFail method with no arguments to get the first element in the collection. If the collection is empty, an Maginium\Framework\Support\ItemNotFoundException exception will be thrown:
Like the where method, you may pass one argument to the firstWhere method. In this scenario, the firstWhere method will return the first item where the given item key's value is "truthy":
The flatMap method iterates through the collection and passes each value to the given closure. The closure is free to modify the item and return it, thus forming a new collection of modified items. Then, the array is flattened by one level:
In this example, calling flatten without providing the depth would have also flattened the nested arrays, resulting in ['iPhone 6S', 'Apple', 'Galaxy S7', 'Samsung']. Providing a depth allows you to specify the number of levels nested arrays will be flattened.
[!WARNING]
Unlike most other collection methods, forget does not return a new modified collection; it modifies and returns the collection it is called on.
The forPage method returns a new collection containing the items that would be present on a given page number. The method accepts the page number as its first argument and the number of items to show per page as its second argument:
The implode method joins items in a collection. Its arguments depend on the type of items in the collection. If the collection contains arrays or objects, you should pass the key of the attributes you wish to join, and the "glue" string you wish to place between the values:
The intersect method removes any values from the original collection that are not present in the given array or collection. The resulting collection will preserve the original collection's keys:
The intersectAssoc method compares the original collection against another collection or array, returning the key / value pairs that are present in all of the given collections:
The intersectByKeys method removes any keys and their corresponding values from the original collection that are not present in the given array or collection:
The join method joins the collection's values with a string. Using this method's second argument, you may also specify how the final element should be appended to the string:
collect(['a', 'b', 'c'])->join(', '); // 'a, b, c'
collect(['a', 'b', 'c'])->join(', ', ', and '); // 'a, b, and c'
collect(['a', 'b'])->join(', ', ' and '); // 'a and b'
collect(['a'])->join(', ', ' and '); // 'a'
collect([])->join(', ', ' and '); // ''
By converting the collection to a LazyCollection, we avoid having to allocate a ton of additional memory. Though the original collection still keeps its values in memory, the subsequent filters will not. Therefore, virtually no additional memory will be allocated when filtering the collection's results.
The static macro method allows you to add methods to the Collection class at run time. Refer to the documentation on extending collections for more information.
The map method iterates through the collection and passes each value to the given callback. The callback is free to modify the item and return it, thus forming a new collection of modified items:
[!WARNING]
Like most other collection methods, map returns a new collection instance; it does not modify the collection it is called on. If you want to transform the original collection, use the transform method.
The mapSpread method iterates over the collection's items, passing each nested item value into the given closure. The closure is free to modify the item and return it, thus forming a new collection of modified items:
The mapToGroups method groups the collection's items by the given closure. The closure should return an associative array containing a single key / value pair, thus forming a new collection of grouped values:
The mapWithKeys method iterates through the collection and passes each value to the given callback. The callback should return an associative array containing a single key / value pair:
The merge method merges the given array or collection with the original collection. If a string key in the given items matches a string key in the original collection, the given item's value will overwrite the value in the original collection:
The mergeRecursive method merges the given array or collection recursively with the original collection. If a string key in the given items matches a string key in the original collection, then the values for these keys are merged together into an array, and this is done recursively:
The pad method will fill the array with the given value until the array reaches the specified size. This method behaves like the array_pad PHP function.
To pad to the left, you should specify a negative size. No padding will take place if the absolute value of the given size is less than or equal to the length of the array:
By default, the percentage will be rounded to two decimal places. However, you may customize this behavior by providing a second argument to the method:
You may pass an integer to random to specify how many items you would like to randomly retrieve. A collection of items is always returned when explicitly passing the number of items you wish to receive:
The reduceSpread method reduces the collection to an array of values, passing the results of each iteration into the subsequent iteration. This method is similar to the reduce method; however, it can accept multiple initial values:
The reject method filters the collection using the given closure. The closure should return true if the item should be removed from the resulting collection:
The replace method behaves similarly to merge; however, in addition to overwriting matching items that have string keys, the replace method will also overwrite items in the collection that have matching numeric keys:
The search is done using a "loose" comparison, meaning a string with an integer value will be considered equal to an integer of the same value. To use "strict" comparison, pass true as the second argument to the method:
The skipUntil method skips over items from the collection while the given callback returns false. Once the callback returns true all of the remaining items in the collection will be returned as a new collection:
The skipWhile method skips over items from the collection while the given callback returns true. Once the callback returns false all of the remaining items in the collection will be returned as a new collection:
You may also pass a key / value pair to the sole method, which will return the first element in the collection that matches the given pair, but only if it exactly one element matches:
If there are no elements in the collection that should be returned by the sole method, an \Illuminate\Collections\ItemNotFoundException exception will be thrown. If there is more than one element that should be returned, an \Illuminate\Collections\MultipleItemsFoundException will be thrown.
The sort method sorts the collection. The sorted collection keeps the original array keys, so in the following example we will use the values method to reset the keys to consecutively numbered indexes:
If your sorting needs are more advanced, you may pass a callback to sort with your own algorithm. Refer to the PHP documentation on uasort, which is what the collection's sort method calls utilizes internally.
If you need to sort a collection of nested arrays or objects, see the \[\`sortBy\`]\(collections.md#method-sortby) and \[\`sortByDesc\`]\(collections.md#method-sortbydesc) methods.
The sortBy method sorts the collection by the given key. The sorted collection keeps the original array keys, so in the following example we will use the values method to reset the keys to consecutively numbered indexes:
If you would like to sort your collection by multiple attributes, you may pass an array of sort operations to the sortBy method. Each sort operation should be an array consisting of the attribute that you wish to sort by and the direction of the desired sort:
The callback must be a comparison function that returns an integer less than, equal to, or greater than zero. For more information, refer to the PHP documentation on uksort, which is the PHP function that sortKeysUsing method utilizes internally.
The splitIn method breaks a collection into the given number of groups, filling non-terminal groups completely before allocating the remainder to the final group:
The tap method passes the collection to the given callback, allowing you to "tap" into the collection at a specific point and do something with the items while not affecting the collection itself. The collection is then returned by the tap method:
The toArray method converts the collection into a plain PHP array. If the collection's values are Eloquent models, the models will also be converted to arrays:
[!WARNING]
toArray also converts all of the collection's nested objects that are an instance of Arrayable to an array. If you want to get the raw array underlying the collection, use the all method instead.
The transform method iterates over the collection and calls the given callback with each item in the collection. The items in the collection will be replaced by the values returned by the callback:
[!WARNING]
Unlike most other collection methods, transform modifies the collection itself. If you wish to create a new collection instead, use the map method.
The union method adds the given array to the collection. If the given array contains keys that are already in the original collection, the original collection's values will be preferred:
The unique method returns all of the unique items in the collection. The returned collection keeps the original array keys, so in the following example we will use the values method to reset the keys to consecutively numbered indexes:
The unique method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the uniqueStrict method to filter using "strict" comparisons.
This method's behavior is modified when using \[Eloquent Collections]\(../docs/%7B%7Bversion%7D%7D/eloquent-collections/#method-unique).
A second callback may be passed to the unless method. The second callback will be executed when the first argument given to the unless method evaluates to true:
The when method will execute the given callback when the first argument given to the method evaluates to true. The collection instance and the first argument given to the when method will be provided to the closure:
$collection = collect([1, 2, 3]);
$collection->when(true, function (Collection $collection, int $value) {
return $collection->push(4);
});
$collection->when(false, function (Collection $collection, int $value) {
return $collection->push(5);
});
$collection->all();
// [1, 2, 3, 4]
A second callback may be passed to the when method. The second callback will be executed when the first argument given to the when method evaluates to false:
$collection = collect([1, 2, 3]);
$collection->when(false, function (Collection $collection, int $value) {
return $collection->push(4);
}, function (Collection $collection) {
return $collection->push(5);
});
$collection->all();
// [1, 2, 3, 5]
The where method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the whereStrict method to filter using "strict" comparisons.
Optionally, you may pass a comparison operator as the second parameter. Supported operators are: '===', '!==', '!=', '==', '=', '<>', '>', '<', '>=', and '<=':
The whereIn method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the whereInStrict method to filter using "strict" comparisons.
The whereInstanceOf method filters the collection by a given class type:
use App\Models\User;
use App\Models\Post;
$collection = collect([
new User,
new User,
new Post,
]);
$filtered = $collection->whereInstanceOf(User::class);
$filtered->all();
// [App\Models\User, App\Models\User]
The whereNotIn method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the whereNotInStrict method to filter using "strict" comparisons.
Each higher order message can be accessed as a dynamic property on a collection instance. For instance, let's use the each higher order message to call a method on each object within a collection:
use App\Models\User;
$users = User::where('votes', '>', 500)->get();
$users->each->markAsVip();
Likewise, we can use the sum higher order message to gather the total number of "votes" for a collection of users:
[!WARNING]
Before learning more about Maginium's lazy collections, take some time to familiarize yourself with PHP generators.
To supplement the already powerful Collection class, the LazyCollection class leverages PHP's generators to allow you to work with very large datasets while keeping memory usage low.
For example, imagine your application needs to process a multi-gigabyte log file while taking advantage of Maginium's collection methods to parse the logs. Instead of reading the entire file into memory at once, lazy collections may be used to keep only a small part of the file in memory at a given time:
use App\Models\LogEntry;
use Maginium\Framework\Support\LazyCollection;
LazyCollection::make(function () {
$handle = fopen('log.txt', 'r');
while (($line = fgets($handle)) !== false) {
yield $line;
}
})->chunk(4)->map(function (array $lines) {
return LogEntry::fromLines($lines);
})->each(function (LogEntry $logEntry) {
// Process the log entry...
});
Or, imagine you need to iterate through 10,000 Eloquent models. When using traditional Maginium collections, all 10,000 Eloquent models must be loaded into memory at the same time:
However, the query builder's cursor method returns a LazyCollection instance. This allows you to still only run a single query against the database but also only keep one Eloquent model loaded in memory at a time. In this example, the filter callback is not executed until we actually iterate over each user individually, allowing for a drastic reduction in memory usage:
Almost all methods available on the Collection class are also available on the LazyCollection class. Both of these classes implement the Maginium\Framework\Support\Enumerable contract, which defines the following methods:
The takeUntilTimeout method returns a new lazy collection that will enumerate values until the specified time. After that time, the collection will then stop enumerating:
To illustrate the usage of this method, imagine an application that submits invoices from the database using a cursor. You could define a scheduled task that runs every 15 minutes and only processes invoices for a maximum of 14 minutes:
use App\Models\Invoice;
use Maginium\Framework\Support\Carbon;
Invoice::pending()->cursor()
->takeUntilTimeout(
Carbon::createFromTimestamp(MAGINIUM_START)->add(14, 'minutes')
)
->each(fn (Invoice $invoice) => $invoice->submit());
While the each method calls the given callback for each item in the collection right away, the tapEach method only calls the given callback as the items are being pulled out of the list one by one:
// Nothing has been dumped so far...
$lazyCollection = LazyCollection::times(INF)->tapEach(function (int $value) {
dump($value);
});
// Three items are dumped...
$array = $lazyCollection->take(3)->all();
// 1
// 2
// 3
The throttle method will throttle the lazy collection such that each value is returned after the specified number of seconds. This method is especially useful for situations where you may be interacting with external APIs that rate limit incoming requests:
The remember method returns a new lazy collection that will remember any values that have already been enumerated and will not retrieve them again on subsequent collection enumerations:
// No query has been executed yet...
$users = User::cursor()->remember();
// The query is executed...
// The first 5 users are hydrated from the database...
$users->take(5)->all();
// First 5 users come from the collection's cache...
// The rest are hydrated from the database...
$users->take(20)->all();