PHP declarations constrain parameters, returns, properties, and supported class constants with atomic, nullable, union, intersection, and DNF types. Strict scalar behavior is selected per calling file and does not replace boundary parsing.
Strong contracts use narrow meaningful types, initialize state, preserve variance and named arguments, convert external data once, supplement native types with runtime validation and static analysis, and label every version-specific feature.
<?php
declare(strict_types=1);
function total(float $price, int $quantity): float
{
return $price * $quantity;
}
echo total(49.5, 2);
99
Without strict_types, PHP may coerce a scalar argument when conversion is possible. With declare(strict_types=1), incompatible scalar arguments throw TypeError, except that an int is accepted for a float declaration.
Strict typing is enabled per file and follows the calling file for scalar calls. Place the declaration immediately after the opening tag.
?string means string|null. A union such as int|string accepts either listed type. Use a union only when both shapes are meaningful to callers, not to avoid normalizing input.
<?php
declare(strict_types=1);
function findLabel(int $id): ?string
{
return $id === 7 ? 'PHP' : null;
}
echo findLabel(7) ?? 'Not found';
PHP
A typed property must receive a compatible value before it is read. Constructor promotion can declare and assign simple dependencies and value fields in one signature.
<?php
declare(strict_types=1);
final class Lesson
{
public function __construct(
public string $title,
public int $durationMinutes
) {
}
}
$lesson = new Lesson('PHP Types', 20);
echo $lesson->title;
PHP Types
PHP type declarations can constrain parameters, return values, properties, and, on supported versions, class constants. A mismatch produces TypeError, while an uninitialized typed property has its own state distinct from null.
Declare the narrowest useful contract at public boundaries. Types improve local guarantees and tool feedback but do not validate string format, numeric range, authorization, collection element types, or external data shape.
Initialize typed properties through constructors or controlled factories before reading them. Nullable does not mean optional initialization; it means null is a permitted value after initialization.
The resource type cannot be used as a normal userland type declaration. Wrap resource ownership behind a class or validate with runtime APIs when a boundary accepts a stream-like handle.
Without strict typing, PHP may coerce compatible scalar arguments and returns among int, float, string, and bool according to language rules. Coercion can turn input into a surprising but valid value, so external boundaries should parse explicitly.
`declare(strict_types=1)` applies per file and affects scalar function calls made from that file. The caller preference governs argument coercion even when the function was declared elsewhere. An integer is still accepted for a float declaration.
Strict mode also checks scalar return behavior in the declaring execution context, but it does not transform PHP into a fully static language. Array contents, object state, dynamic properties, and decoded data still need design and analysis.
Place the strict declaration at the beginning of project source files and test calls from both strict and legacy callers during migration. Do not assume enabling it in one bootstrap automatically changes every included file call site.
Built-in declarations include scalar types, array, object, callable, iterable, mixed, void, never, null, true, false, and class types subject to version support and position rules. Use the exact PHP spelling such as `bool`, not aliases such as `boolean`.
`mixed` accepts every PHP value and communicates an intentionally unresolved boundary; it should trigger immediate narrowing. `object` accepts any object but says nothing about behavior, so prefer an interface when the caller needs a capability.
`void` means the caller cannot use a meaningful returned value, while `never` means the function does not return normally because it throws, exits, or otherwise terminates. Use never only when every path satisfies that contract.
`iterable` accepts arrays and Traversable objects. It does not promise rewindability, countability, element type, or repeated traversal. Document those properties or use a narrower interface where consumers depend on them.
`?T` and `T|null` express the same two-value type for one base type. Use null only when absence is a real domain state, and distinguish it from an omitted argument by the function signature and default.
A union such as `int|string` accepts any listed type. Before adding a union, ask whether callers truly use two meaningful representations or whether parsing should convert both into one domain value at the boundary.
Literal true and false types support APIs with specific sentinel contracts on suitable PHP versions, but a result object or exception is often clearer than mixing a useful value with false. Preserve existing extension contracts when wrapping them.
Redundant unions are rejected when PHP can determine redundancy without loading classes. Keep unions small and behaviorally coherent; a long union often signals that one function owns unrelated responsibilities.
An intersection such as `Countable&Iterator` requires one object that satisfies every listed class-type contract. It describes combined capabilities without inventing a new named interface, but a domain interface is clearer when the combination has business meaning.
Intersection types require PHP 8.1 or later and use class types rather than scalar types. Compatibility and redundancy rules apply, so test the syntax on the project minimum runtime.
PHP 8.2 added disjunctive normal form types, allowing a union whose member can be a parenthesized intersection, such as `(A&B)|C`. Parentheses are part of the required grammar and should be used only when callers genuinely supply those alternatives.
Complex composite signatures can be precise yet difficult to explain and mock. Introduce a named adapter or interface when the type expression appears repeatedly or when additional invariants go beyond capability membership.
Implementations and overrides must remain compatible with inherited method contracts. Parameter types may become broader under contravariance, while return types may become narrower under covariance, subject to the language rules.
Property type compatibility is stricter because callers may both read and write, though modern property capabilities can add version-specific variance rules. Preserve visibility, readonly behavior, and hook contracts across supported runtimes.
Named arguments make public parameter names observable even though type compatibility focuses on types and positions. Keep inherited names stable so an override does not break callers that use the parent contract by name.
A subtype must preserve behavioral promises beyond the signature: valid states, exceptions, side effects, ordering, and result meaning. Run one contract suite against every implementation rather than trusting successful compilation.
Request data, JSON, database rows, cache values, environment strings, and unserialized data enter PHP without trustworthy domain types. Validate shape and convert them into typed value objects or commands before core logic.
Use `is_*`, `instanceof`, enum parsing, and domain factories to narrow uncertain values. Assertions may help static analysis inside proven internal paths but should not be the sole validation for hostile input or essential production invariants.
Array type declarations do not specify keys or element types. Use typed records, iterators, and supported static-analysis annotations for internal precision, plus runtime validation wherever data crosses a trust boundary.
Catch TypeError only at a boundary that can translate a programming or input error responsibly. Catching it around arbitrary application code can misclassify a defect as a client mistake and leave partial effects.
Test accepted and rejected values at every public function boundary, including null, falsy scalars, subclasses, anonymous implementations, generators, and uninitialized properties where relevant. Run compile checks on all supported PHP versions.
Static analysis can infer generics, array shapes, callable signatures, unreachable branches, and nullable flow beyond native declarations. Configure a meaningful level and baseline existing debt rather than suppressing new findings globally.
Reflection can inspect declared types for frameworks and diagnostics, but avoid making business behavior depend on fragile string formatting of composite types. Work with ReflectionType subclasses and named components.
During migration, add return types and internal declarations before narrowing widely called inputs, observe deprecations, and update callers in small steps. Public libraries should treat every signature change as a compatibility decision.
0 of 2 checked
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.