Collections¶
pluck and keyBy¶
pluck and keyBy resolve their column against the collection's value type,
including nested paths and callbacks:
$users->pluck('name', 'id'); // Collection<int, string>
$users->keyBy('name'); // EloquentCollection<string, App\User>
$posts->pluck('user.name'); // Collection<int, string>
$users->keyBy(fn ($u) => $u->id); // EloquentCollection<int, App\User>
pluck rewrites both halves, so plucking a column off a collection of models
gives a support collection of whatever that column holds. keyBy rewrites only
the keys, so the value type and the collection class carry over.
On a builder or a relation¶
pluck also resolves on an Eloquent builder, and on a relation, which forwards
it to the builder underneath:
User::query()->pluck('name'); // Collection<int, string>
$user->accounts()->pluck('name'); // Collection<int, string>
$user->accounts()->pluck('name', 'id'); // Collection<int, string>
$post->user()->pluck('name'); // Collection<int, string>
The column is read from the related model, so $post->user()->pluck('name')
resolves name on User rather than on Post. Builder methods in the middle
of the chain do not break it, since they return the relation.
only¶
Arr::only narrows an array shape to the keys you ask for. A key the array does
not have is dropped, since the call is a key intersection:
/** @var array{id: int, name: string, email: string} $row */
Arr::only($row, ['id', 'name']); // array{id: int, name: string}
Arr::only($row, 'id'); // array{id: int}
Arr::only($row, ['id', 'nope']); // array{id: int}
Keys that are not known while analysing cannot be intersected. Since the call
can only ever drop entries, the result keeps the shape with every entry optional
rather than falling back to array<string, mixed>:
Where there is no shape to intersect, the keys still narrow the key type:
No dot notation
Arr::only is an array_intersect_key over the top-level keys, so a dotted
key matches a literal key that happens to contain a dot, and never a nested
one. Collection::only behaves the same way:
That is what the framework returns at runtime, so the inferred type is
reporting the bug rather than causing it. Reach for Arr::get or data_get
when you want a path, or pluck, which does resolve one.
Model::only builds a shape too, but out of a model's attributes, and answers
differently for a key that is not there. See
reading a subset of attributes.
value¶
value reads a column off the first matching item, the same lookup pluck
uses, including nested paths:
$users->value('name'); // string|null
$posts->value('user.name'); // string|null
$users->value('name', 'n/a'); // string
countBy¶
countBy rewrites the keys the same way groupBy does, and the values are
the counts:
$users->countBy('email'); // Collection<string, int>
$users->countBy(fn ($u) => $u->id); // Collection<int, int>
An Eloquent collection becomes a support collection, because the values are no longer models.
sum, min, max¶
A column or callback is resolved rather than left as mixed:
$users->sum('id'); // int
$users->min('email'); // string|null
$users->max(fn ($u) => $u->id); // int|null
collapse¶
collapse unwraps one level of collections or arrays. collapseWithKeys
keeps the inner keys instead of reindexing:
/** @var Collection<int, Collection<string, User>> $nested */
$nested->collapse(); // Collection<int, User>
$nested->collapseWithKeys(); // Collection<string, User>
mapToGroups¶
The callback returns one key/value pair per item. The key is the group, the value goes into it. A literal stays a literal, because that callback can only ever produce that group; a property or a branch is the union of what it can actually return. Runtime only reads the first pair, so a second key in the same array is ignored.
mapToDictionary is the same pair, but the values stay arrays. Eloquent
collections keep their class there (new static()), and toBase() the outer
collection for mapToGroups because those items are groups rather than
models:
$users->mapToGroups(fn ($u) => [$u->email => $u]);
// Collection<string, EloquentCollection<int, User>>
$users->mapToGroups(fn ($u) => ['foo' => $u->id]);
// Collection<'foo', EloquentCollection<int, int>>
$users->mapToDictionary(fn ($u) => [$u->email => $u]);
// EloquentCollection<string, array<int, User>>
transform¶
transform is map in place. @phpstan-this-out rewrites $this after the
call:
toArray¶
toArray walks Arrayable items, including nested collections, instead of
widening to mixed:
collect([1, 2, 3])->toArray(); // array<int, int>
$users->toArray(); // array<int, array<string, mixed>>
$users->groupBy('email')->toArray(); // array<string, array<int, array<string, mixed>>>
duplicates¶
duplicates maps through a column or callback the way pluck does, then
keeps the original keys of the values that appear more than once:
$users->duplicates('email'); // Collection<int, string>
$users->duplicates(fn ($u) => $u->id); // Collection<int, int>
No argument leaves the value type alone. duplicatesStrict is the same map.
flatten¶
A known depth unwraps that many levels. Omitted, it unwraps all the way:
/** @var Collection<int, Collection<int, Collection<int, User>>> $deep */
$deep->flatten(1); // Collection<int, Collection<int, User>>
$deep->flatten(); // Collection<int, User>
dot¶
Nested arrays become string keys and a union of the leaves:
/** @var Collection<string, array{name: string, age: int}> $rows */
$rows->dot(); // Collection<string, int|string>
Arr collapse, flatten, and dot¶
The array helpers unwrap the same way the collection methods do:
/** @var list<list<User>> $nested */
Arr::collapse($nested); // list<User>
Arr::flatten($nested); // list<User>
/** @var array<string, array{name: string, age: int}> $rows */
Arr::dot($rows); // array<string, int|string>
Arr::random and Arr::sole keep the array's value type. Arr::mapSpread
spreads each nested chunk into the callback, the same as Collection::mapSpread.
groupBy¶
groupBy nests one level per grouper, and an array argument means successive
levels rather than a nested path:
$users->groupBy('name');
// Collection<string, Collection<int, App\User>>
$users->groupBy(['name', 'id']);
// Collection<string, Collection<int, Collection<int, App\User>>>
preserveKeys decides the innermost keys:
/** @var Collection<string, App\User> $keyed */
$keyed->groupBy('id'); // Collection<int, Collection<int, App\User>>
$keyed->groupBy('id', true); // Collection<int, Collection<string, App\User>>
Precision, and widening it where you want it¶
Keys and values are resolved as precisely as the input allows. A grouper returning a backed enum's value gives the literal union, and an interpolated key gives the product of its parts:
$items->groupBy(fn ($i) => $i->priority->value); // Collection<10|20, ...>
$items->keyBy(fn ($i) => "{$i->a->value}|{$i->b->value}"); // Collection<'10|x'|'10|y'|..., ...>
That precision is not decoration. It survives to wherever you consume the collection, so a refined key still reads as refined:
foreach ($items->keyBy(fn ($i) => "row-{$i->id}") as $key => $item) {
// $key is non-falsy-string, not string
}
Collection declares TKey and TValue invariantly, so an exact type is what
an annotation has to match. Where you would rather accept the general type, ask
for it at the annotation with covariant:
/** @return Collection<covariant string, Item> */
public function keyed(): Collection
{
return $this->items->keyBy(fn ($i) => "{$i->a->value}|{$i->b->value}");
}
That is use-site variance, and it applies to values the same way:
array-key also works for a key, being a benevolent union. A plain
int|string does not, despite reading like the safer choice: it is matched
invariantly and accepts neither int nor string.
Higher order proxies¶
The proxy forms resolve the same way as the argument forms: