Collection::filter

Keeps only the collection items that pass a truth test, preserving their original keys.

Signature
Collection::filter(?callable $callback = null): static

Parameters

ParameterTypeDescription
$callbackoptional(callable(TValue, TKey): bool)|null= nullTruth test invoked with the item value first and its key second. Return a truthy value to keep the item. Omit it entirely to drop every item that is loosely equal to false.

Return value

staticA new collection containing only the items that passed the truth test. The original collection is not modified and surviving items keep the keys they had.

filter walks the collection and keeps the items whose callback returns a truthy value. The callback receives the value first and the key second, so the same method covers both plain lists and associative data — you can filter on the key just as easily as on the value.

When to reach for it

Use it when the rows are already in memory: a paginated result you are trimming for display, an array of config entries, an API payload you decoded. If the data still lives in the database, filter in the query builder instead — filter loads every row first and discards them in PHP.

The thing that trips people up

filter preserves keys. Filtering [91, 54, 78] down to the first and third items leaves you with keys 0 and 2, not 0 and 1. PHP will happily print_r that, but toJson sees a gap in the numeric keys and encodes an object rather than an array — so a JavaScript client that expected [91, 78] suddenly receives {"0":91,"2":78}. Chain values() before serialising to re-index.

Called with no arguments, it applies PHP's own falsy rules: 0, '', null, false and [] are all removed. That makes filter() a quick way to clean a sparse array, but a legitimate zero disappears with it.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Trim an in-memory result set down to the rows a user is allowed to see before rendering.
  • Strip null and empty-string entries out of a decoded API payload in one call.
  • Pick out the configuration keys sharing a prefix by testing the key rather than the value.

Frequently asked questions

Why does my filtered collection turn into an object in JSON?

Because filter preserves the original keys, and a numeric array with gaps cannot be represented as a JSON array. Chain values() to re-index the collection before calling toJson() or returning it from a controller.

What is the difference between filter and reject?

They are exact inverses: filter keeps items whose callback returns truthy, reject keeps items whose callback returns falsy. Both preserve keys, so both usually want a values() call before serialisation.

Does filter modify the original collection?

No. filter returns a new collection and leaves the receiver untouched, so you can filter the same collection several different ways without re-fetching the data.

More examples

Filtering on the key

The callback's second argument is the key, which is what makes prefix-based selection possible without a separate loop. The first block prints db_host and db_port; the second prints only app_name. reject() runs the same test and keeps everything that fails it, so the two outputs together account for every original entry.

Example · php
<?php
$config = collect([
    'db_host'  => '127.0.0.1',
    'db_port'  => 5432,
    'app_name' => 'demo',
]);

$database = $config->filter(fn ($value, $key) => str_starts_with($key, 'db_'));

print_r($database->all());

// reject is the exact inverse of filter
print_r($config->reject(fn ($value, $key) => str_starts_with($key, 'db_'))->all());

Filtering models already loaded in memory

This is the common shape in an application: the relation is already loaded, so filtering in PHP avoids a second query. Note the values() call before the collection goes into a JSON response — without it the filtered orders serialise as an object keyed by their original offsets. This snippet needs a booted application with Eloquent, so it is shown rather than executed.

Example · php
<?php
$orders = $customer->orders; // an Eloquent collection

$unpaid = $orders->filter(fn ($order) => $order->paid_at === null);

return response()->json([
    'count'  => $unpaid->count(),
    'orders' => $unpaid->values(), // re-index before serialising
]);

Zero is falsy, which is rarely what you want

The first output contains only ['desk' => 3]. The second contains ['desk' => 3, 'chair' => 0]. Whenever a zero, an empty string or the boolean false is a meaningful value in your data, pass an explicit callback rather than relying on the argument-free form.

Example · php
<?php
$quantities = collect(['desk' => 3, 'chair' => 0, 'lamp' => null]);

// Bare filter() removes the null AND the legitimate zero:
print_r($quantities->filter()->all());

// An explicit test keeps the zero:
print_r($quantities->filter(fn ($qty) => $qty !== null)->all());

Chaining after a filter

Aggregates such as count() and avg() ignore keys entirely, so the key-preservation gotcha never bites when a filter feeds straight into one. The output is 3, then 84, then bool(true) — the last line being the idiomatic way to ask whether anything survived the filter.

Example · php
<?php
$scores = collect([91, 54, 78, 40, 83]);

echo $scores->filter(fn (int $s) => $s >= 70)->count(), PHP_EOL;
echo $scores->filter(fn (int $s) => $s >= 70)->avg(), PHP_EOL;
var_dump($scores->filter(fn (int $s) => $s > 100)->isEmpty());

Discussion

  • Be the first to comment on this lesson.