Skip to content
Latchkey

PHP Fatal error: Uncaught TypeError - Wrong Argument or Return Type in CI

PHP throws a TypeError when a value does not match a declared parameter or return type. With scalar type declarations and (especially) strict_types enabled, a string where an int is expected, or null where a non-nullable type is declared, becomes a fatal error instead of a silent coercion.

What this error means

A test or command in CI dies with "Uncaught TypeError: Argument #N (\$x) must be of type int, string given" or "Return value must be of type X, null returned". It often surfaces only in CI when stricter PHP versions or strict_types are in effect.

php
PHP Fatal error:  Uncaught TypeError: App\Money::fromCents():
Argument #1 (\$cents) must be of type int, string given,
called in /app/src/Cart.php on line 42 in /app/src/Money.php:18

Common causes

A value of the wrong scalar type is passed

Under declare(strict_types=1), PHP does not coerce a numeric string to int (or similar). Passing the wrong type to a typed parameter throws TypeError immediately.

A function returns null for a non-nullable return type

A code path returns null (or nothing) while the signature declares a non-nullable return type, so the return itself raises a TypeError.

How to fix it

Read the message - it names the exact argument and type

  1. Note the failing method, argument index, declared type, and the type actually given.
  2. Trace the caller shown in the stack trace to where the wrong-typed value originates.
  3. Cast or convert at the boundary, or fix the source so the right type flows through.
php
// caller passes a string from input
\$cart->add(Money::fromCents((int) \$request->get('cents')));

Make nullability explicit where null is legitimate

If null is a valid value, declare a nullable type rather than letting a TypeError fire.

php
public function find(int \$id): ?User
{
    return \$this->repo->byId(\$id); // may legitimately be null
}

Decide on strict_types deliberately

strict_types=1 disables scalar coercion file-by-file. Keep it on for safety, but then convert types at input boundaries rather than relying on implicit coercion.

How to prevent it

  • Convert external input to the right type at the boundary, not deep in the call stack.
  • Run static analysis (PHPStan/Psalm) to catch type mismatches before runtime.
  • Keep declare(strict_types=1) consistent across the codebase so behavior is predictable.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →