Type Declarations Overview
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan, psalm, and phpcs — all specialist static analysis tools. Missing type declarations or absent strict_types=1 are not caught by the PHP interpreter or a default linter; they require running these dedicated tools over the codebase.
Closest to 'simple parameterised fix' (e3). The quick_fix is 'add declare(strict_types=1) at the top of every file and add parameter/return types to every function signature.' This is more than a single one-line patch — it requires touching multiple function signatures and file headers — but it is a mechanical, pattern-based change within the same codebase rather than a cross-cutting architectural refactor.
Closest to 'localised tax' (b3). Applies broadly across web, cli, and queue-worker contexts in PHP 7+, but each addition or correction is localised to the function or file in question. Missing declarations don't impose a gravitational pull on architecture — they create a per-function productivity tax without reshaping the whole system.
Closest to 'notable trap' (t5). The misconception field directly states the canonical wrong belief: developers treat type declarations as mere documentation hints, unaware that PHP 7+ enforces them at runtime as TypeErrors, and that without strict_types=1 PHP silently coerces types. This is a documented, well-known gotcha that most PHP developers eventually encounter, matching the t5 anchor.
Also Known As
TL;DR
Explanation
PHP's type system has grown substantially: PHP 7.0 added scalar type hints (int, float, string, bool) and return types; 7.1 added nullable (?Type) and void; 7.4 added typed properties; 8.0 added union types (A|B), mixed, static return type, and the never placeholder; 8.1 added intersection types (A&B), never return type, and readonly properties; 8.2 added standalone null, false, true types. Combine with declare(strict_types=1) for strict runtime enforcement. Static analysers (PHPStan, Psalm) use these to verify type correctness without execution.
Common Misconception
Why It Matters
Common Mistakes
- Not enabling strict_types=1 — without it PHP coerces types silently, defeating much of the safety benefit.
- Using mixed or no type at all "just in case" — be as specific as possible, loosen only when truly needed.
- Declaring nullable types (?string) out of habit when null is never a valid input.
- Forgetting return type declarations — parameter types alone leave the output unguarded.
Avoid When
- Avoid mixed as a type declaration — it disables type checking entirely and signals unclear design.
- Do not use type declarations as a substitute for input validation on data coming from outside PHP.
When To Use
- Declare parameter and return types on all public methods — they serve as enforced documentation.
- Use declare(strict_types=1) at the top of every PHP file to prevent silent type coercion.
Code Examples
// No type declarations — bugs hide until runtime:
function applyDiscount($price, $percent) {
return $price - ($price * $percent / 100);
}
applyDiscount('ten dollars', '20%'); // No error — returns 0
// With type declarations:
function applyDiscount(float $price, float $percent): float {
return $price - ($price * $percent / 100);
}
applyDiscount('ten dollars', '20%'); // TypeError immediately
<?php declare(strict_types=1);
class UserService {
// Typed property (PHP 7.4)
private array $cache = [];
// Parameter + return types
public function findById(int $id): ?User { return null; }
// Union type (PHP 8.0)
public function findByIdOrEmail(int|string $identifier): ?User { return null; }
// Intersection type (PHP 8.1) — must be Countable AND Iterator
public function processAll(\Countable&\Iterator $items): void {}
// never return type (PHP 8.1)
public function fail(string $msg): never {
throw new \RuntimeException($msg);
}
// Enum param (PHP 8.1)
public function filterByStatus(OrderStatus $status): array { return []; }
}