ZetCode

PHP Array Filtering

last modified October 22, 2026

PHP offers several tools for filtering arrays and objects, such as array_filter, array_map, array_reduce, and the filter_var family of functions. This tutorial explores them with practical examples.

Predicates, the functions that decide whether an element is kept, are covered in more detail in the PHP predicates article.

Basic array filtering

The array_filter function passes each element to a callback and returns a new array with the elements for which the callback returns true.

basic_filter.php
<?php

$numbers = [1, 2, 3, 4, 5, 6];

$evenNumbers = array_filter($numbers, fn($num) => $num % 2 === 0);
print_r($evenNumbers);

print_r(array_values($evenNumbers));

The arrow function keeps the even numbers. The original array is not changed.

Note that array_filter preserves the keys: the result has the keys 1, 3 and 5, not 0, 1 and 2. If a list with consecutive keys is needed, for example before encoding it to JSON, wrap the result in array_values.

$ php basic_filter.php
Array
(
    [1] => 2
    [3] => 4
    [5] => 6
)
Array
(
    [0] => 2
    [1] => 4
    [2] => 6
)

Filtering without a callback

Without a callback, array_filter removes all values that are falsy.

no_callback.php
<?php

$values = ['sky', '', 0, 'blue', null, '0', false, 12, []];

var_export(array_filter($values));
echo "\n";

$withZeros = array_filter($values, fn($v) => $v !== null && $v !== '');
var_export($withZeros);
echo "\n";

The empty string, 0, null, the string '0', false and the empty array are all removed. This is often too aggressive; the second call removes only null and empty strings and keeps legitimate zeros. We use var_export because print_r shows false as an empty string.

$ php no_callback.php
array (
  0 => 'sky',
  3 => 'blue',
  7 => 12,
)
array (
  0 => 'sky',
  2 => 0,
  3 => 'blue',
  5 => '0',
  6 => false,
  7 => 12,
  8 => 
  array (
  ),
)

Filtering with keys

With the ARRAY_FILTER_USE_BOTH flag, the callback receives both the value and the key.

filter_with_keys.php
<?php

$data = ["a" => 1, "b" => 2, "c" => 3, "d" => 4];

$filtered = array_filter($data, fn($v, $k) => $k === "b" || $v > 2,
    ARRAY_FILTER_USE_BOTH);

print_r($filtered);

The callback keeps an element if its key is "b" or its value is greater than 2. Element "b" => 2 is kept because of its key, "c" and "d" because of their values. The ARRAY_FILTER_USE_KEY flag passes only the key.

$ php filter_with_keys.php
Array
(
    [b] => 2
    [c] => 3
    [d] => 4
)

Keeping allowed keys

When only a known set of keys is wanted, array_intersect_key is simpler than a callback.

allowed_keys.php
<?php

$input = [
    'name' => 'Jane',
    'email' => 'jane@example.com',
    'is_admin' => true,
    'age' => '31',
];

$allowed = ['name', 'email', 'age'];

$safe = array_intersect_key($input, array_flip($allowed));
print_r($safe);

The array_flip function turns the list of allowed keys into an array with those keys, and array_intersect_key keeps only the matching entries of the input. This is a common way to drop unexpected fields, such as is_admin, from user input.

$ php allowed_keys.php
Array
(
    [name] => Jane
    [email] => jane@example.com
    [age] => 31
)

Filtering objects

Callbacks can test object properties.

filter_objects.php
<?php

class Product
{
    public function __construct(
        public string $name,
        public float $price,
    ) {}
}

$products = [
    new Product("Laptop", 1000),
    new Product("Phone", 500),
    new Product("Tablet", 300),
];

$affordable = array_filter($products, fn(Product $p) => $p->price < 800);

foreach ($affordable as $i => $product) {
    echo "$i: $product->name ($product->price)\n";
}

The callback keeps products cheaper than 800. The laptop is removed, and the phone and tablet keep their original indices 1 and 2.

$ php filter_objects.php
1: Phone (500)
2: Tablet (300)

Filtering associative arrays

Rows from a database or an API are usually associative arrays.

filter_associative.php
<?php

$users = [
    ["name" => "John", "age" => 25],
    ["name" => "Jane", "age" => 30],
    ["name" => "Doe", "age" => 17],
];

$adults = array_filter($users, fn($user) => $user["age"] >= 18);

echo implode(', ', array_column($adults, 'name')), "\n";

The callback keeps users who are 18 or older. The array_column function then extracts their names.

$ php filter_associative.php
John, Jane

Filtering by multiple criteria

Conditions can be combined with logical operators.

filter_multi_criteria.php
<?php

class Item
{
    public function __construct(
        public string $name,
        public float $price,
        public int $stock,
    ) {}
}

$items = [
    new Item("Laptop", 1000, 5),
    new Item("Phone", 500, 10),
    new Item("Tablet", 300, 0),
    new Item("Mouse", 25, 40),
];

$available = array_filter($items, fn(Item $i) => $i->price < 800 && $i->stock > 0);

foreach ($available as $item) {
    echo "$item->name: $item->price, $item->stock in stock\n";
}

An item is kept when it costs less than 800 and is in stock. The laptop fails the price check and the tablet the stock check.

$ php filter_multi_criteria.php
Phone: 500, 10 in stock
Mouse: 25, 40 in stock

Combining array_filter and array_map

Filtering and transformation are often done in sequence.

combine_filter_map.php
<?php

$numbers = [1, 2, 3, 4, 5, 6];

$evens = array_filter($numbers, fn($num) => $num % 2 === 0);
$doubled = array_map(fn($num) => $num * 2, $evens);

print_r($doubled);

$sum = array_sum($doubled);
echo "Sum: $sum\n";

The even numbers are selected first and then doubled. Filtering first means that the transformation runs on fewer elements. The array_map function preserves the keys of a single input array, so the result still has the keys 1, 3 and 5.

$ php combine_filter_map.php
Array
(
    [1] => 4
    [3] => 8
    [5] => 12
)
Sum: 24

Partitioning with array_reduce

array_reduce can split an array into several groups in one pass.

reduce_group.php
<?php

$users = [
    ["name" => "John", "age" => 25],
    ["name" => "Jane", "age" => 30],
    ["name" => "Doe", "age" => 17],
    ["name" => "Anna", "age" => 15],
];

$groups = array_reduce($users, function (array $carry, array $user) {
    $key = $user['age'] >= 18 ? 'adults' : 'minors';
    $carry[$key][] = $user['name'];

    return $carry;
}, ['adults' => [], 'minors' => []]);

print_r($groups);

The accumulator starts with two empty groups. The callback puts each user's name into one of them and returns the updated accumulator.

$ php reduce_group.php
Array
(
    [adults] => Array
        (
            [0] => John
            [1] => Jane
        )

    [minors] => Array
        (
            [0] => Doe
            [1] => Anna
        )

)

Filtering unique values

The array_unique function removes duplicate values.

filter_unique.php
<?php

$values = [1, 2, 2, 3, 3, 4];

$uniqueSquares = array_unique(array_map(fn($v) => $v * $v, $values));
print_r($uniqueSquares);

$emails = ['Jane@Example.com', 'john@example.com', 'jane@example.com'];
$unique = array_values(array_unique(array_map('strtolower', $emails)));
print_r($unique);

array_unique keeps the first occurrence of each value together with its key, so the squared values keep the keys 0, 1, 3 and 5. In the second part, the email addresses are normalized to lowercase before deduplication, and array_values renumbers the result.

$ php filter_unique.php
Array
(
    [0] => 1
    [1] => 4
    [3] => 9
    [5] => 16
)
Array
(
    [0] => jane@example.com
    [1] => john@example.com
)

Filtering nested arrays

To filter the values of a nested array, we can flatten it first.

filter_nested.php
<?php

function flatten(array $data): array
{
    $result = [];

    array_walk_recursive($data, function ($value) use (&$result) {
        $result[] = $value;
    });

    return $result;
}

$data = [1, [2, 3], [4, [5, 6]]];

$evens = array_filter(flatten($data), fn($n) => $n % 2 === 0);
echo implode(' ', $evens), "\n";

The array_walk_recursive function visits every leaf value of the nested array. The closure captures $result by reference and collects the values. The flat list is then filtered with array_filter.

$ php filter_nested.php
2 4 6

Recursive filtering

Sometimes the structure must be kept and only the unwanted leaves removed.

filter_recursive.php
<?php

function filterRecursive(array $data, callable $keep): array
{
    $result = [];

    foreach ($data as $key => $value) {
        if (is_array($value)) {
            $value = filterRecursive($value, $keep);
            if ($value !== []) {
                $result[$key] = $value;
            }
        } elseif ($keep($value)) {
            $result[$key] = $value;
        }
    }

    return $result;
}

$config = [
    'db' => ['host' => 'localhost', 'password' => null, 'port' => 5432],
    'cache' => ['driver' => null],
    'debug' => false,
    'name' => 'app',
];

var_export(filterRecursive($config, fn($v) => $v !== null));
echo "\n";

The filterRecursive function descends into nested arrays and removes values for which the callback returns false. Arrays that become empty, like cache, are removed as well. A named function is used because an arrow function cannot easily call itself.

$ php filter_recursive.php
array (
  'db' => 
  array (
    'host' => 'localhost',
    'port' => 5432,
  ),
  'debug' => false,
  'name' => 'app',
)

Validating data with filter_var_array

The filter extension validates and converts input data, such as values from a form.

filter_var.php
<?php

$input = [
    'age' => '31',
    'email' => 'jane@example.com',
    'homepage' => 'not a url',
    'newsletter' => 'yes',
];

$rules = [
    'age' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => 0, 'max_range' => 130]],
    'email' => FILTER_VALIDATE_EMAIL,
    'homepage' => FILTER_VALIDATE_URL,
    'newsletter' => ['filter' => FILTER_VALIDATE_BOOL, 'flags' => FILTER_NULL_ON_FAILURE],
];

var_dump(filter_var_array($input, $rules));

Each key gets its own filter. FILTER_VALIDATE_INT converts the string to an integer and checks the range, FILTER_VALIDATE_EMAIL and FILTER_VALIDATE_URL check the format, and FILTER_VALIDATE_BOOL accepts values like yes, on or 1. A value that fails validation becomes false, or null with the FILTER_NULL_ON_FAILURE flag.

$ php filter_var.php
array(4) {
  ["age"]=>
  int(31)
  ["email"]=>
  string(16) "jane@example.com"
  ["homepage"]=>
  bool(false)
  ["newsletter"]=>
  bool(true)
}

Lazy filtering with generators

array_filter needs the whole array in memory. A generator filters items one by one, which works for large or even infinite sequences.

generator.php
<?php

function filterLazy(iterable $items, callable $keep): Generator
{
    foreach ($items as $key => $item) {
        if ($keep($item)) {
            yield $key => $item;
        }
    }
}

function numbers(): Generator
{
    for ($i = 1; ; $i++) {
        echo "producing $i\n";
        yield $i;
    }
}

foreach (filterLazy(numbers(), fn($n) => $n % 3 === 0) as $n) {
    echo "got $n\n";

    if ($n >= 6) {
        break;
    }
}

The numbers generator never ends, yet the program finishes, because values are produced only on demand. The output shows that producing and filtering are interleaved.

$ php generator.php
producing 1
producing 2
producing 3
got 3
producing 4
producing 5
producing 6
got 6

Filtering users by age

This example filters user objects by age, calculated from their date of birth.

filter_users_by_age.php
<?php

class User
{
    public function __construct(
        public string $firstName,
        public string $lastName,
        public string $city,
        public DateTimeImmutable $dateOfBirth,
    ) {}

    public function ageOn(DateTimeImmutable $date): int
    {
        return $this->dateOfBirth->diff($date)->y;
    }
}

$users = [
    new User("John", "Doe", "New York", new DateTimeImmutable("2000-05-15")),
    new User("Jane", "Smith", "Los Angeles", new DateTimeImmutable("1995-08-22")),
    new User("Alice", "Johnson", "Chicago", new DateTimeImmutable("2010-03-10")),
    new User("Bob", "Brown", "Houston", new DateTimeImmutable("1998-11-30")),
    new User("Emma", "Davis", "Seattle", new DateTimeImmutable("2008-10-23")),
    new User("Mike", "Wilson", "Boston", new DateTimeImmutable("1990-01-25")),
    new User("Sarah", "Taylor", "Miami", new DateTimeImmutable("2008-10-22")),
];

$today = new DateTimeImmutable("2026-10-22");

$adults = array_filter($users, fn(User $u) => $u->ageOn($today) >= 18);

foreach ($adults as $user) {
    printf("%-6s %-8s %-12s %d\n", $user->firstName, $user->lastName,
        $user->city, $user->ageOn($today));
}

The ageOn method computes the age in whole years with diff. A fixed date is used instead of the current one, so the result is reproducible: Sarah turns 18 on this day and is included, while Emma, who is one day younger, is not.

$ php filter_users_by_age.php
John   Doe      New York     26
Jane   Smith    Los Angeles  31
Bob    Brown    Houston      27
Mike   Wilson   Boston       36
Sarah  Taylor   Miami        18

Best practices for array filtering

Source

PHP array_filter - Documentation

In this article, we have covered filtering arrays and objects in PHP with array_filter, array_reduce, recursive and lazy filtering, and the filter extension.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all PHP tutorials.