ZetCode

PHP ValueError

last modified October 22, 2026

In this article, we explore the ValueError class in PHP. A ValueError is thrown when a function receives an argument of the correct type but with an invalid value.

Basic definition

ValueError was introduced in PHP 8.0. Before that, most built-in functions emitted a warning and returned false or null when given an invalid argument value; since PHP 8.0, many of them throw a ValueError instead. It complements TypeError, which is thrown when an argument has the wrong type.

ValueError extends Error, not Exception. User code may throw it as well, to signal that an argument has an unacceptable value.

ValueError in built-in functions

The following example triggers a ValueError in several built-in functions.

builtin.php
<?php

declare(strict_types=1);

$calls = [
    'str_repeat' => fn() => str_repeat('ab', -1),
    'array_chunk' => fn() => array_chunk([1, 2, 3], 0),
    'array_combine' => fn() => array_combine(['a', 'b'], [1]),
    'random_int' => fn() => random_int(10, 1),
    'explode' => fn() => explode('', 'a,b,c'),
    'range' => fn() => range(1, 5, 0),
];

foreach ($calls as $name => $call) {
    try {
        $call();
    } catch (ValueError $e) {
        printf("%-14s %s\n", $name, $e->getMessage());
    }
}

Each closure calls a function with an argument of the correct type but an invalid value: a negative repeat count, a zero chunk size, arrays of different lengths, a minimum greater than the maximum, an empty separator, and a zero step. The messages name the function and the offending argument.

$ php builtin.php
str_repeat     str_repeat(): Argument #2 ($times) must be greater than or equal to 0
array_chunk    array_chunk(): Argument #2 ($length) must be greater than 0
array_combine  array_combine(): Argument #1 ($keys) and argument #2 ($values) must have the same number of elements
random_int     random_int(): Argument #1 ($min) must be less than or equal to argument #2 ($max)
explode        explode(): Argument #1 ($separator) must not be empty
range          range(): Argument #3 ($step) cannot be 0

TypeError versus ValueError

The two errors are easy to tell apart: TypeError concerns the type of an argument, ValueError its value.

type_vs_value.php
<?php

declare(strict_types=1);

$tests = [
    'wrong type' => fn() => str_repeat('x', '3'),
    'wrong value' => fn() => str_repeat('x', -3),
];

foreach ($tests as $label => $test) {
    try {
        $test();
    } catch (TypeError | ValueError $e) {
        printf("%-12s %-10s %s\n", $label, $e::class, $e->getMessage());
    }
}

With strict types enabled, passing the string '3' to str_repeat is a type error. Passing the integer -3 has the right type, but the value is out of range. A single catch block can handle both errors with the | syntax.

$ php type_vs_value.php
wrong type   TypeError  str_repeat(): Argument #2 ($times) must be of type int, string given
wrong value  ValueError str_repeat(): Argument #2 ($times) must be greater than or equal to 0

The class hierarchy

Because ValueError is an Error, a catch (Exception $e) block does not catch it.

hierarchy.php
<?php

declare(strict_types=1);

$e = new ValueError("bad value");

var_dump($e instanceof Error);
var_dump($e instanceof Exception);
var_dump($e instanceof Throwable);

echo implode(' > ', [ValueError::class, ...array_values(class_parents($e))]), "\n";

try {
    try {
        str_repeat('x', -3);
    } catch (Exception $ex) {
        echo "never reached\n";
    }
} catch (ValueError $ex) {
    echo "ValueError is not an Exception: ", $ex->getMessage(), "\n";
}

The inner catch block for Exception is skipped and the outer block for ValueError handles the error. To catch every error and exception, use Throwable.

$ php hierarchy.php
bool(true)
bool(false)
bool(true)
ValueError > Error
ValueError is not an Exception: str_repeat(): Argument #2 ($times) must be greater than or equal to 0

Invalid enum value

The from method of a backed enum throws a ValueError when no case has the given value.

enum_from.php
<?php

declare(strict_types=1);

enum PaymentStatus: string
{
    case Pending = 'pending';
    case Completed = 'completed';
}

try {
    $status = PaymentStatus::from('failed');
} catch (ValueError $e) {
    echo "Caught ValueError: ", $e->getMessage(), "\n";
}

$status = PaymentStatus::tryFrom('failed') ?? PaymentStatus::Pending;
echo "Status: ", $status->value, "\n";

The value 'failed' does not match any case of PaymentStatus. The tryFrom method returns null instead of throwing, which combines well with the null coalescing operator to provide a default case.

$ php enum_from.php
Caught ValueError: "failed" is not a valid backing value for enum PaymentStatus
Status: pending

Invalid JSON depth

Not every problem in a built-in function is reported with a ValueError. The json_decode function shows the difference between an invalid argument and invalid data.

json_depth.php
<?php

declare(strict_types=1);

try {
    json_decode('{"a": 1}', true, 0);
} catch (ValueError $e) {
    echo "Caught ValueError: ", $e->getMessage(), "\n";
}

$data = json_decode('{"a": {"b": {"c": 1}}}', true, 2);
var_dump($data);
echo json_last_error_msg(), "\n";

A depth of 0 is an invalid argument, so a ValueError is thrown. A document that is nested deeper than the allowed depth is a data problem; json_decode returns null and the error is available through json_last_error_msg, unless the JSON_THROW_ON_ERROR flag is used.

$ php json_depth.php
Caught ValueError: json_decode(): Argument #3 ($depth) must be greater than 0
NULL
Maximum stack depth exceeded

Throwing ValueError in your code

This example validates a discount percentage in an e-commerce system.

discount.php
<?php

declare(strict_types=1);

function applyDiscount(float $price, float $discount): float
{
    if ($discount < 0 || $discount > 100) {
        throw new ValueError("Discount must be between 0 and 100");
    }

    return $price * (1 - $discount / 100);
}

foreach ([20.0, 150.0] as $discount) {
    try {
        printf("Price: %.2f\n", applyDiscount(50.0, $discount));
    } catch (ValueError $e) {
        echo "Caught ValueError: ", $e->getMessage(), "\n";
    }
}

The applyDiscount function throws a ValueError if the discount percentage is outside the range of 0 to 100. This ensures that invalid discounts do not silently produce incorrect prices.

$ php discount.php
Price: 40.00
Caught ValueError: Discount must be between 0 and 100

Invalid date

This example validates a booking date in a reservation system.

date.php
<?php

declare(strict_types=1);

function bookDate(string $date): DateTimeImmutable
{
    $dt = DateTimeImmutable::createFromFormat('!Y-m-d', $date);

    if ($dt === false || $dt->format('Y-m-d') !== $date) {
        throw new ValueError("Invalid or non-existent date: $date");
    }

    return $dt;
}

foreach (['2026-04-30', '2026-04-31', '30.04.2026'] as $date) {
    try {
        $booked = bookDate($date);
        echo "Booked: ", $booked->format('l, F j, Y'), "\n";
    } catch (ValueError $e) {
        echo "Caught ValueError: ", $e->getMessage(), "\n";
    }
}

DateTimeImmutable::createFromFormat silently rolls over non-existent dates, so 2026-04-31 would become May 1. Formatting the result and comparing it with the input catches such dates as well as input in a different format. The ! in the format resets the time fields to zero.

$ php date.php
Booked: Thursday, April 30, 2026
Caught ValueError: Invalid or non-existent date: 2026-04-31
Caught ValueError: Invalid or non-existent date: 30.04.2026

Invalid email format

This example validates an email address during user registration.

email.php
<?php

declare(strict_types=1);

function registerUser(string $email): void
{
    if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
        throw new ValueError("Invalid email address: $email");
    }

    echo "User registered with email: $email\n";
}

foreach (['jane@example.com', 'user@.com'] as $email) {
    try {
        registerUser($email);
    } catch (ValueError $e) {
        echo "Caught ValueError: ", $e->getMessage(), "\n";
    }
}

The filter_var function with FILTER_VALIDATE_EMAIL returns false for an invalid address, and the function turns this into a ValueError.

$ php email.php
User registered with email: jane@example.com
Caught ValueError: Invalid email address: user@.com

Invalid currency code

This example validates a currency code against a list of supported codes.

currency.php
<?php

declare(strict_types=1);

function setCurrency(string $code): string
{
    $validCurrencies = ['USD', 'EUR', 'GBP'];

    if (!in_array($code, $validCurrencies, true)) {
        throw new ValueError(sprintf("Invalid currency code '%s', expected one of: %s",
            $code, implode(', ', $validCurrencies)));
    }

    return $code;
}

foreach (['EUR', 'JPY'] as $code) {
    try {
        $currency = setCurrency($code);
        echo "Currency set to: $currency\n";
    } catch (ValueError $e) {
        echo "Caught ValueError: ", $e->getMessage(), "\n";
    }
}

The in_array check is called with true as the third argument, which enables strict comparison. The error message lists the accepted values, which helps the caller fix the input.

$ php currency.php
Currency set to: EUR
Caught ValueError: Invalid currency code 'JPY', expected one of: USD, EUR, GBP

ValueError or InvalidArgumentException

Many libraries signal invalid arguments with the InvalidArgumentException class from the SPL.

invalid_argument.php
<?php

declare(strict_types=1);

function setStockQuantity(int $quantity): int
{
    if ($quantity < 0 || $quantity > 1000) {
        throw new InvalidArgumentException("Stock quantity must be between 0 and 1000, got $quantity");
    }

    return $quantity;
}

try {
    setStockQuantity(-5);
} catch (Exception $e) {
    echo get_class($e), ": ", $e->getMessage(), "\n";
}

InvalidArgumentException is an Exception, so code that catches Exception handles it. Use ValueError when you want to mirror the behaviour of built-in functions, and InvalidArgumentException (or a custom exception) for recoverable validation errors in application code. Whichever you choose, be consistent across a project.

$ php invalid_argument.php
InvalidArgumentException: Stock quantity must be between 0 and 1000, got -5

Source

PHP ValueError - Documentation

In this article, we have shown when PHP throws ValueError, how it differs from TypeError and Exception, and how to use it for value checks in our own code.

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.