All posts

PHP TypeError: Causes, Debugging, and Fixes

Fix PHP TypeError exceptions by reading the signature, inspecting the actual value, and validating or correcting null, scalar, and return types.

A PHP TypeError means a value does not match a declared parameter, property, or return type, or that an internal function received the wrong argument type. Read the exception message from right to left: identify the expected type, inspect the actual value at the first application frame, then either validate and convert the input or correct the declaration. Do not hide the error with a broad try/catch or by disabling type declarations.

Start with the exact exception message

A typical message already contains the broken contract:

TypeError: OrderService::calculateTotal():
Argument #1 ($price) must be of type float, string given

Log the exception class, message, file, line, and stack trace. At the first frame in your own code, inspect the value and its source. Values from JSON, form fields, environment variables, and database drivers may not have the type your domain method expects.

Message patternLikely causeCorrect fix
`must be of type int, string given`Unvalidated external inputValidate, then convert deliberately
`must be of type X, null given`Missing data or a nullable caseReject missing data or declare `?X` when null is valid
`Return value must be of type X`A branch returns the wrong valueMake every return path satisfy the declaration
`Cannot assign X to property ...`Typed property receives incompatible dataNormalize before assignment

Validate before calling typed code

Keep parsing at the boundary and pass known types into the rest of the application:

<?php
declare(strict_types=1);

function parsePrice(mixed $value): float
{
    $price = filter_var($value, FILTER_VALIDATE_FLOAT);

    if ($price === false) {
        throw new InvalidArgumentException('price must be numeric');
    }

    return (float) $price;
}

final class OrderService
{
    public function calculateTotal(float $price, int $quantity): float
    {
        if ($price < 0 || $quantity < 1) {
            throw new InvalidArgumentException('invalid order values');
        }

        return round($price * $quantity, 2);
    }
}

$quantity = filter_var(
    $_POST['quantity'] ?? null,
    FILTER_VALIDATE_INT,
    ['options' => ['min_range' => 1]],
);

if ($quantity === false) {
    throw new InvalidArgumentException('quantity must be a positive integer');
}

$total = (new OrderService())->calculateTotal(
    parsePrice($_POST['price'] ?? null),
    $quantity,
);

A cast is appropriate only after you have decided that conversion is valid. Blindly casting malformed input can turn a useful failure into incorrect data.

Null is a domain decision

If null is valid, express that in the type and handle it explicitly:

function displayName(?string $name): string
{
    return $name ?? 'Anonymous';
}

If null means required data is missing, validate at the boundary instead of making every downstream parameter nullable.

What strict_types does and does not do

declare(strict_types=1) selects strict scalar type checking for calls made from that file. It helps surface unintended coercion, but it does not repair bad input and it is not required for every TypeError: incompatible object types, typed properties, and return values can still raise TypeError. The PHP manual documents both `TypeError` and strict typing.

Why it can appear only after deployment

When the same code works locally, compare the production PHP version, extensions, environment data, database driver behavior, and the request that triggered the exception. Avoid fixing a production-only failure by widening every declaration to mixed; reproduce the actual value and preserve the useful contract.

Paste the exception into Bugsly's free error explainer for a quick reading, then verify the proposed fix against the value at the failing frame.

Catch at the application boundary

Catch TypeError only where you can add context, return a safe response, or report the failure. Preserve the original exception as the previous exception when wrapping it. Bugsly can receive PHP exceptions through its Sentry-compatible endpoint and group repeated failures with their stack traces and release context.

Try Bugsly Free

Track up to 100 issues per month on the free plan, with unlimited events and no credit card required.

Get Started Free