Type Declarations and Strict Types
PHP started as a loosely typed language, but since PHP 7 it has steadily grown a real type system layered on top: scalar type declarations (int, float, string, bool), compound types (array, iterable, callable, object), class/interface types, self/static, void/never for return position, nullable types via a leading ?, and union/intersection types in modern versions. These declarations can appear on function/method parameters, return types, and — since PHP 7.4 — typed class properties. Type declarations are enforced at runtime: if a value doesn't match (and can't be coerced), PHP throws a TypeError, catching a class of bugs that would otherwise surface much later.
Cricket analogy: A team sheet enforces that the 'captain' slot must be filled by a player object, not a placeholder, allows a 'vice-captain' slot to be nullable if none is named, and rejects an ineligible entry outright at selection time the way a TypeError halts a mismatched call.
Coercive mode versus strict_types
By default PHP runs in 'coercive' (weak) typing mode: passing the string "5" to an int parameter is silently converted to the integer 5. Adding declare(strict_types=1); as the very first statement in a file switches that file to strict mode for calls made from within it: scalar type mismatches — passing a string where int is declared — now throw a TypeError instead of coercing, with the sole exception that an int is still accepted for a float parameter (a widening conversion PHP always allows). Crucially, strict_types is a per-file declaration that governs the calling file, not the receiving function's file — a strict-mode caller enforces strict checks even when calling a function defined in a non-strict file, and vice versa.
Cricket analogy: A relaxed net session lets a bowler practice off any run-up length without correction, but a strict academy session enforces exact run-up measurements from the first drill, though it still accepts a slightly longer, more precise run-up as an upgrade, and this strictness is set per-academy, declared in the very first session rule.
<?php
declare(strict_types=1);
function applyDiscount(float $price, int $percentOff): float
{
return $price * (1 - $percentOff / 100);
}
echo applyDiscount(100.0, 20); // 80.0 - fine, types match exactly
echo applyDiscount(100, 20); // 80.0 - int widens to float automatically
try {
echo applyDiscount("100", 20); // TypeError under strict_types: string is not float
} catch (\TypeError $e) {
echo 'Caught: ' . $e->getMessage();
}Nullable, union, and intersection types
A ?Type prefix (e.g. ?string) means the parameter accepts either that type or null, shorthand for the union Type|null. PHP 8.0 introduced full union types, letting a signature declare int|string $id, matching any value of either listed type. PHP 8.1 added intersection types (Countable&Iterator), requiring a value to satisfy every listed type simultaneously — useful for generic collection parameters that must both be iterable and countable. PHP 8.0 also introduced the mixed type, meaning literally any value is accepted, and never for functions that always throw or otherwise never return control to the caller.
Cricket analogy: A 'next batter' slot might be nullable, shorthand for 'player or none if all out,' a 'man of the match' field might accept a batter-or-bowler union, and a 'genuine all-rounder' role requires satisfying both batting and bowling criteria simultaneously, an intersection, while a pundit's comment field accepts literally anything, and a 'retired hurt, never returning' status never resumes play.
<?php
declare(strict_types=1);
function findUser(int|string $identifier): ?array
{
// returns null if not found, or an array if found
return match (true) {
is_int($identifier) => ['id' => $identifier, 'name' => 'Ada'],
default => null,
};
}
class ValidationException extends \RuntimeException {}
function assertPositive(int $value): void
{
if ($value <= 0) {
throw new ValidationException('Value must be positive');
}
}A useful analogy: coercive typing is like a lenient border guard who'll let you through if your documents are 'close enough' and quietly stamps them into the right format. strict_types is a strict guard who rejects anything that isn't exactly the declared type — except the one deliberate exemption of letting an int pass as a float, since no precision is lost widening an integer into a floating-point value.
A common misconception is that declare(strict_types=1) affects the whole application once set anywhere. It does not — it only governs type-checking for calls made *from* that specific file. Mixing strict and non-strict files in the same codebase is legal but can produce confusing inconsistencies, so most modern PHP style guides recommend declaring strict_types=1 in every file.
- Type declarations can appear on parameters, return types, and typed properties, and are enforced at runtime with
TypeErroron mismatch. - Coercive (default) mode silently converts compatible scalar values;
declare(strict_types=1)disables most of that coercion. strict_typesis a per-file setting that governs the calling file's outgoing calls, not the definition file.- Even under strict_types, an
intargument is still accepted for afloatparameter as a safe widening conversion. - Union types (
int|string) accept any of the listed types; intersection types (Countable&Iterator) require all of them simultaneously. mixedaccepts any value andnevermarks functions that never return control normally (always throw or exit).
Practice what you learned
1. Where must `declare(strict_types=1);` appear for it to take effect?
2. Under strict_types, which conversion is still permitted automatically?
3. If file A has `declare(strict_types=1)` and calls a function defined in file B, which does NOT declare strict_types, what typing mode applies to that call?
4. What does the type `?string` mean in a parameter declaration?
5. What is required for a value to satisfy the intersection type `Countable&Iterator`?
Was this page helpful?
You May Also Like
Functions in PHP
Learn how PHP functions are declared, typed, and invoked, including default parameters, variadics, references, and closures for reusable logic.
Named Arguments and the Nullsafe Operator
Named arguments let callers pass function parameters by name in any order, and the nullsafe operator short-circuits chained property/method access safely when any link is null.
Enums in PHP
PHP 8.1 enums provide a first-class way to define a fixed set of named values, with optional scalar backing, methods, interfaces, and constants attached.
Data Types in PHP
A tour of PHP's scalar, compound, and special types, plus how PHP's dynamic typing and type juggling behave in practice.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics