ZetCode

PHP Predicates

last modified October 22, 2026

In this article, we explore predicates in PHP. A predicate is a function that returns a boolean value. Predicates are used to test conditions, filter collections, validate data, and make decisions in code.

Basic definition

A predicate takes one or more arguments and returns true or false. In PHP, a predicate can be a named function, an anonymous function, an arrow function, or a static or instance method. Functions such as array_filter, array_find, array_any and array_all accept predicates as callbacks. Many built-in functions, like is_int or ctype_digit, are predicates themselves.

Simple predicate

The following example defines a predicate that checks if a number is even.

simple.php
<?php

declare(strict_types=1);

function isEven(int $number): bool
{
    return $number % 2 === 0;
}

foreach ([4, 7, 0, -2] as $n) {
    printf("%d is even: %s\n", $n, isEven($n) ? 'true' : 'false');
}

The isEven function returns true if the remainder after division by two is zero. The bool return type documents that the function is a predicate.

$ php simple.php
4 is even: true
7 is even: false
0 is even: true
-2 is even: true

Filtering arrays with a named predicate

The following example uses a named function as a predicate for array_filter.

filter_named.php
<?php

declare(strict_types=1);

function isPositive(int $number): bool
{
    return $number > 0;
}

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

$positive = array_filter($numbers, 'isPositive');
print_r($positive);

$positive = array_filter($numbers, isPositive(...));
print_r(array_values($positive));

A named function can be passed as a string, 'isPositive', or with the first-class callable syntax isPositive(...), available since PHP 8.1. The latter is checked by static analysis tools and IDEs.

Note that array_filter preserves the keys of the original array. The array_values function renumbers them from zero.

$ php filter_named.php
Array
(
    [1] => 2
    [3] => 4
)
Array
(
    [0] => 2
    [1] => 4
)

Anonymous and arrow functions

Short predicates are often written inline.

anonymous.php
<?php

declare(strict_types=1);

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

$even = array_filter($numbers, function (int $number): bool {
    return $number % 2 === 0;
});

$odd = array_filter($numbers, fn(int $n): bool => $n % 2 !== 0);

echo implode(', ', $even), "\n";
echo implode(', ', $odd), "\n";

The first predicate is an anonymous function, the second an arrow function. An arrow function contains a single expression and captures variables of the enclosing scope automatically.

$ php anonymous.php
2, 4, 6
1, 3, 5

Validation predicates

Predicates are a natural fit for validation.

emails.php
<?php

declare(strict_types=1);

function isValidEmail(string $email): bool
{
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

$emails = ['test@example.com', 'invalid-email', 'user@domain.com', 'user@.com'];
$valid = array_filter($emails, isValidEmail(...));

print_r($valid);

The isValidEmail predicate wraps filter_var, which returns the filtered value on success and false on failure. The strict comparison turns the result into a boolean.

$ php emails.php
Array
(
    [0] => test@example.com
    [2] => user@domain.com
)

Predicates on objects

A predicate can test properties of objects.

objects.php
<?php

declare(strict_types=1);

final class User
{
    public function __construct(public string $name, public int $age) {}
}

function isAdult(User $user): bool
{
    return $user->age >= 18;
}

$users = [
    new User("Peter", 15),
    new User("Lucia", 20),
    new User("Martin", 17),
    new User("Eva", 34),
];

foreach (array_filter($users, isAdult(...)) as $user) {
    echo "$user->name ($user->age)\n";
}

The isAdult predicate checks the age of a user. Only adults are printed.

$ php objects.php
Lucia (20)
Eva (34)

Predicate factories

A function can create predicates with a configurable parameter.

factory.php
<?php

declare(strict_types=1);

function longerThan(int $length): Closure
{
    return fn(string $text): bool => mb_strlen($text) > $length;
}

function startsWith(string $prefix): Closure
{
    return fn(string $text): bool => str_starts_with($text, $prefix);
}

$words = ['sky', 'skyscraper', 'small', 'smartphone', 'cloud'];

print_r(array_filter($words, longerThan(5)));
print_r(array_filter($words, startsWith('sm')));

The longerThan and startsWith functions return closures. The arrow functions capture the $length and $prefix parameters, so each call produces a new, specialized predicate.

$ php factory.php
Array
(
    [1] => skyscraper
    [3] => smartphone
)
Array
(
    [2] => small
    [3] => smartphone
)

Combining predicates

Simple predicates can be combined into more complex ones with higher-order functions.

combinators.php
<?php

declare(strict_types=1);

function not(callable $p): Closure
{
    return fn(mixed $x): bool => !$p($x);
}

function allOf(callable ...$ps): Closure
{
    return function (mixed $x) use ($ps): bool {
        foreach ($ps as $p) {
            if (!$p($x)) {
                return false;
            }
        }
        return true;
    };
}

function anyOf(callable ...$ps): Closure
{
    return function (mixed $x) use ($ps): bool {
        foreach ($ps as $p) {
            if ($p($x)) {
                return true;
            }
        }
        return false;
    };
}

$isEven = fn(int $n): bool => $n % 2 === 0;
$isPositive = fn(int $n): bool => $n > 0;
$isBig = fn(int $n): bool => abs($n) > 100;

$numbers = [-150, -4, -3, 0, 7, 12, 250];

echo implode(' ', array_filter($numbers, allOf($isEven, $isPositive))), "\n";
echo implode(' ', array_filter($numbers, not($isEven))), "\n";
echo implode(' ', array_filter($numbers, anyOf($isBig, not($isPositive)))), "\n";

The not function negates a predicate, allOf requires all predicates to pass, and anyOf requires at least one. The combined predicates are built from three small ones, which keeps each condition readable and testable.

$ php combinators.php
12 250
-3 7
-150 -4 -3 0 250

Testing keys and values

By default, array_filter passes only values to the predicate. A flag changes this.

keys.php
<?php

declare(strict_types=1);

$stock = ['apples' => 12, 'pears' => 0, 'plums' => 5, 'peaches' => 0];

$inStock = array_filter($stock, fn(int $qty): bool => $qty > 0);
print_r($inStock);

$startsWithP = array_filter($stock, fn(string $key): bool => $key[0] === 'p',
    ARRAY_FILTER_USE_KEY);
print_r($startsWithP);

$both = array_filter($stock, fn(int $qty, string $key): bool => $qty > 0 && $key[0] === 'p',
    ARRAY_FILTER_USE_BOTH);
print_r($both);

With ARRAY_FILTER_USE_KEY, the predicate receives the key; with ARRAY_FILTER_USE_BOTH, it receives the value and the key.

$ php keys.php
Array
(
    [apples] => 12
    [plums] => 5
)
Array
(
    [pears] => 0
    [plums] => 5
    [peaches] => 0
)
Array
(
    [plums] => 5
)

array_find, array_any and array_all

PHP 8.4 added four functions that use predicates to search arrays.

find_any_all.php
<?php

declare(strict_types=1);

$temperatures = ['Mon' => 18, 'Tue' => 21, 'Wed' => 26, 'Thu' => 29, 'Fri' => 24];

$isHot = fn(int $t): bool => $t > 25;

echo "First hot day: ", array_find_key($temperatures, $isHot), "\n";
echo "First hot temperature: ", array_find($temperatures, $isHot), "\n";

var_dump(array_any($temperatures, $isHot));
var_dump(array_all($temperatures, fn(int $t): bool => $t > 15));
var_dump(array_find($temperatures, fn(int $t): bool => $t > 40));

array_find returns the first value that satisfies the predicate and array_find_key its key; both return null when nothing matches. array_any checks whether at least one element satisfies the predicate and array_all whether all of them do. All four stop as soon as the result is known. The predicate also receives the key as its second argument.

$ php find_any_all.php
First hot day: Wed
First hot temperature: 26
bool(true)
bool(true)
NULL

Built-in predicates

PHP has many built-in predicate functions that can be passed directly as callbacks.

builtin.php
<?php

declare(strict_types=1);

$values = [1, '2', 3.5, 'sky', null, true, [], '42'];

echo "ints: ", count(array_filter($values, 'is_int')), "\n";
echo "strings: ", count(array_filter($values, 'is_string')), "\n";
echo "numeric: ", count(array_filter($values, 'is_numeric')), "\n";

$digits = array_filter(['123', '12a', '007', ''], 'ctype_digit');
print_r($digits);

$words = preg_grep('/^s/', ['sky', 'cloud', 'sun', 'rain']);
print_r($words);

The is_* functions test types, is_numeric accepts numbers and numeric strings, and ctype_digit tests whether a string consists of digits only. The preg_grep function filters an array with a regular expression, which works like a predicate.

$ php builtin.php
ints: 1
strings: 3
numeric: 4
Array
(
    [0] => 123
    [2] => 007
)
Array
(
    [0] => sky
    [2] => sun
)

Source

PHP array_filter - Documentation

In this article, we have shown how to write predicates in PHP, how to create and combine them, and how to use them for filtering, searching, and validation.

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.