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 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:18Common 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
- Note the failing method, argument index, declared type, and the type actually given.
- Trace the caller shown in the stack trace to where the wrong-typed value originates.
- Cast or convert at the boundary, or fix the source so the right type flows through.
// 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.
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.