advanced php language

TypeError, ValueError, and ParseError

TypeError, ValueError, and ParseError are built-in PHP error classes. They all implement Throwable, but they point to different kinds of problems.

You should usually prevent these errors with correct types, validation, linting, tests, and static analysis. Catching them can be useful at an application boundary or in a small demonstration, but normal business logic should not rely on them as its main control flow.

TypeError

TypeError happens when a value has the wrong type for a function, method, property, or return type.

PHP example
<?php

declare(strict_types=1);

function totalPence(int $quantity, int $unitPricePence): int
{
    return $quantity * $unitPricePence;
}

try {
    totalPence('2', 1500);
} catch (TypeError $error) {
    echo 'Wrong type: ' . $error::class . PHP_EOL;
}

// Prints:
// Wrong type: TypeError

With strict_types=1, PHP does not silently coerce the string '2' into an integer for this call. That makes type mistakes easier to find.

In application code, validate raw input before calling typed functions.

PHP example
<?php

declare(strict_types=1);

function parseQuantity(string $input): int
{
    if (!ctype_digit($input)) {
        throw new InvalidArgumentException('Quantity must be a whole number.');
    }

    return (int) $input;
}

echo parseQuantity('2') . PHP_EOL;

// Prints:
// 2

The boundary function turns an external string into a safe integer before the rest of the application uses it.

ValueError

ValueError happens when the type is correct, but the value is invalid for the operation.

PHP example
<?php

declare(strict_types=1);

try {
    array_chunk([1, 2, 3], 0);
} catch (ValueError $error) {
    echo 'Invalid value: ' . $error::class . PHP_EOL;
}

// Prints:
// Invalid value: ValueError

The second argument is an integer, so the type is correct. The value 0 is invalid because chunks must have a positive size.

Prefer validating before calling built-in functions when the value comes from outside the program.

PHP example
<?php

declare(strict_types=1);

function chunkSizeFromInput(string $input): int
{
    if (!ctype_digit($input)) {
        throw new InvalidArgumentException('Chunk size must be numeric.');
    }

    $size = (int) $input;

    if ($size < 1) {
        throw new InvalidArgumentException('Chunk size must be at least 1.');
    }

    return $size;
}

echo chunkSizeFromInput('10') . PHP_EOL;

// Prints:
// 10

InvalidArgumentException is better here because the application is rejecting external input intentionally, before it reaches the built-in function.

ParseError

ParseError happens when PHP cannot parse code. Normal source files with syntax errors usually fail before your application runs. You can catch ParseError when parsing dynamic code with eval(), but application code should avoid eval() unless there is a strong reason.

PHP example
<?php

declare(strict_types=1);

try {
    eval('function broken {');
} catch (ParseError $error) {
    echo 'Could not parse code.' . PHP_EOL;
}

// Prints:
// Could not parse code.

In real projects, prevent parse errors with php -l, automated tests, and CI checks before deployment.

Do Not Hide These Errors

Catching these errors and continuing as if nothing happened is dangerous. A TypeError may mean the code passed the wrong object into a service. A ValueError may mean input validation is missing. A ParseError may mean generated code or a deployment artifact is broken.

At the application boundary, log the error and return a safe response. Deep inside the application, fix the cause.

PHP example
<?php

declare(strict_types=1);

function runCommand(callable $command): string
{
    try {
        $command();

        return 'Command finished.';
    } catch (Throwable $throwable) {
        return 'Command failed: ' . $throwable::class;
    }
}

echo runCommand(fn () => array_chunk([1], 0)) . PHP_EOL;

// Prints:
// Command failed: ValueError

This kind of boundary handling is useful for CLI commands, queue workers, and front controllers. It should also log enough detail for developers to fix the underlying problem.

What You Should Be Able To Do

After this lesson, you should be able to recognise TypeError, ValueError, and ParseError, explain the difference between wrong type and wrong value, and understand why linting and input validation prevent many of these failures.

For junior work, this matters because these errors often appear during refactoring, framework upgrades, form handling, and job processing. Knowing what each one means helps you debug faster and fix the real cause.

Practice

Practice: Identify Built-In Error Types

Create a small PHP example that demonstrates TypeError, ValueError, and ParseError.

Task

Build examples for:

  • passing the wrong type to a strictly typed function
  • passing an invalid value to a built-in function
  • catching a parse error from dynamic code

Use strict types. Keep the expected output in the PHP code block as printed lines or comments.

Check Your Work

Confirm:

  • the wrong type creates TypeError
  • the wrong value creates ValueError
  • invalid dynamic code creates ParseError

Afterward, explain why production code should usually prevent these errors rather than use them as ordinary control flow.

Show solution

This solution shows the three error types in small isolated examples.

PHP example
<?php

declare(strict_types=1);

function double(int $number): int
{
    return $number * 2;
}

try {
    double('2');
} catch (TypeError $error) {
    echo $error::class . PHP_EOL;
}

try {
    array_chunk([1, 2, 3], 0);
} catch (ValueError $error) {
    echo $error::class . PHP_EOL;
}

try {
    eval('function broken {');
} catch (ParseError $error) {
    echo $error::class . PHP_EOL;
}

// Prints:
// TypeError
// ValueError
// ParseError

Production code should usually prevent these errors with types, validation, linting, tests, and static analysis. Catching them at a boundary can protect users, but swallowing them deep in the application hides bugs.