Type Declarations in Functions
What Are Type Declarations? In simple terms: Type declarations state what kind of value a function expects for each parameter and what kind of value it returns — for example, "this parameter must be a
What Are Type Declarations?
In simple terms: Type declarations state what kind of value a function expects for each parameter and what kind of value it returns — for example, "this parameter must be an integer" or "this function returns a string".
Technically: A type declaration is written before a parameter name (int $quantity) or after the parameter list (): string). PHP checks the value at runtime when the function is called and when it returns. If a value does not match (and cannot be converted, in coercive mode), PHP throws a TypeError.
function lineTotal(int $quantity, float $unitPrice): float
{
return $quantity * $unitPrice;
}
Why Type Declarations Matter
| Benefit | Explanation |
|---|---|
| Errors appear early | A wrong value fails immediately at the function boundary, not later in confusing ways. |
| Self-documenting code | The signature shows exactly what a function needs and produces. |
| Better tools | Editors autocomplete and warn; static analyzers (PHPStan, Psalm) detect bugs before code runs. |
| Safer refactoring | Changing a function's contract makes incompatible calls fail visibly. |
Without types, lineTotal("3 pcs", null) would silently produce a wrong number. With types, PHP reports the problem.
Available Types
| Type | Accepts | Example |
|---|---|---|
int | Integers | int $quantity |
float | Floats (and integers, which are converted) | float $price |
string | Strings | string $name |
bool | true / false | bool $isActive |
array | Arrays | array $items |
callable | Functions, closures, callable strings/arrays | callable $callback |
iterable | Arrays and Traversable objects (generators, iterators) | iterable $rows |
object | Any object | object $entity |
| Class / interface name | Objects of that class, a subclass, or an implementing class | DateTimeInterface $date |
self, static, parent | The current class (and related) — used in classes | ): static |
mixed | Any value, including null (PHP 8.0+) | mixed $value |
void | Return type only: returns nothing | ): void |
never | Return type only: never returns (PHP 8.1+) | ): never |
null, false, true | Exactly that value; mostly used in unions (standalone since PHP 8.2) | ): int|false |
Composite Types
| Form | Syntax | Meaning | Since |
|---|---|---|---|
| Nullable | ?string | string or null | PHP 7.1 |
| Union | int|string | Either type | PHP 8.0 |
| Intersection | Countable&Traversable | Must satisfy all types (classes/interfaces only) | PHP 8.1 |
| DNF (disjunctive normal form) | (Countable&Traversable)|null | Unions of intersections | PHP 8.2 |
Scalar Types
Example
<?php
function area(float $width, float $height): float
{
return $width * $height;
}
var_dump(area(4, 2.5));
var_dump(area('3', '2'));
try {
area('wide', 3);
} catch (TypeError $e) {
echo $e->getMessage() . "\n";
}
Output
float(10)
float(6)
area(): Argument #1 ($width) must be of type float, string given, called in /path/script.php on line 11
How It Works
area(4, 2.5): the integer4is accepted for afloatparameter and converted to4.0.area('3', '2'): in the default coercive mode, numeric strings are converted to floats.area('wide', 3):'wide'cannot be converted, so PHP throws aTypeError. The message names the function, the argument position, the parameter, the expected and actual types, and the line of the call.
Coercive Mode vs Strict Mode
By default, PHP converts compatible scalar values (coercive typing). Adding declare(strict_types=1); as the first statement of a file makes calls from that file strict:
<?php
declare(strict_types=1);
function area(float $width, float $height): float
{
return $width * $height;
}
var_dump(area(4, 2.5));
try {
area('3', '2');
} catch (TypeError $e) {
echo "TypeError: numeric strings are rejected in strict mode\n";
}
Output
float(10)
TypeError: numeric strings are rejected in strict mode
| Argument | Coercive Mode (default) | Strict Mode |
|---|---|---|
int → float parameter | Accepted (converted) | Accepted (the only allowed conversion) |
"3" → int/float parameter | Accepted (converted) | TypeError |
3.0 → int parameter | Accepted (converted to 3) | TypeError |
3.5 → int parameter | Converted to 3 with a deprecation notice (PHP 8.1+) | TypeError |
"abc" → int parameter | TypeError | TypeError |
Strict mode is covered in detail in Strict Types. Many modern projects enable it in every file.
Nullable Types
A ? before a type allows null as well:
<?php
function greet(?string $name): string
{
return 'Hello, ' . ($name ?? 'guest');
}
echo greet('Meena') . "\n";
echo greet(null) . "\n";
Output
Hello, Meena
Hello, guest
?string is equivalent to the union string|null. Note that a nullable parameter is not automatically optional: greet() without an argument still fails. To make it optional, add a default: ?string $name = null.
Union Types (PHP 8.0+)
A union type accepts values of any of the listed types:
<?php
function formatId(int|string $id): string
{
return is_int($id) ? sprintf('#%06d', $id) : strtoupper($id);
}
echo formatId(42) . "\n";
echo formatId('ord-77') . "\n";
Output
#000042
ORD-77
Inside the function, is_int() determines which kind of value was received. Union types are useful when a value legitimately comes in different forms; if a parameter accepts many unrelated types, the function is probably doing too much.
false in Unions
Functions that return a value or false on failure can say so precisely:
<?php
function firstWordLength(string $text): int|false
{
$space = strpos($text, ' ');
return $space === false ? false : $space;
}
var_dump(firstWordLength('Hello world'));
var_dump(firstWordLength('Hello'));
Output
int(5)
bool(false)
Class and Interface Types
A class or interface name as a type accepts objects of that class, its subclasses, or classes implementing that interface:
<?php
function formatDate(DateTimeInterface $date): string
{
return $date->format('d M Y');
}
echo formatDate(new DateTimeImmutable('2026-10-09')) . "\n";
echo formatDate(new DateTime('2026-12-25')) . "\n";
Output
09 Oct 2026
25 Dec 2026
Both DateTime and DateTimeImmutable implement DateTimeInterface, so the function accepts either. Typing against an interface rather than a specific class makes functions more flexible. This is a key idea in object-oriented design (see PHP Interfaces).
Intersection Types (PHP 8.1+)
An intersection type requires an object to implement all listed types:
<?php
function describeCollection(Countable&Traversable $items): string
{
$values = [];
foreach ($items as $item) {
$values[] = $item;
}
return count($items) . ' items: ' . implode(', ', $values);
}
echo describeCollection(new ArrayObject(['PHP', 'MySQL'])) . "\n";
Output
2 items: PHP, MySQL
The function uses both count() (requires Countable) and foreach (requires Traversable). ArrayObject implements both interfaces.
iterable and callable
<?php
function total(iterable $numbers): int
{
$sum = 0;
foreach ($numbers as $number) {
$sum += $number;
}
return $sum;
}
function moreNumbers(): Generator
{
yield 4;
yield 5;
}
function applyTwice(callable $operation, int $value): int
{
return $operation($operation($value));
}
echo total([1, 2, 3]) . "\n";
echo total(moreNumbers()) . "\n";
echo applyTwice(fn(int $x) => $x * 3, 2) . "\n";
Output
6
9
18
iterableaccepts both arrays and generators, sototal()works with either. Generators are covered in PHP Generators.callableaccepts anything that can be called.applyTwice()calls the given function twice:3 × (3 × 2) = 18.
mixed
mixed explicitly means "any type, including null". It is equivalent to the union of all types.
function debugValue(mixed $value): string
{
return get_debug_type($value) . ': ' . var_export($value, true);
}
Use mixed when a function genuinely accepts anything (debugging helpers, generic containers). Prefer a more specific type whenever possible, because mixed gives tools and readers little information. See Mixed and Never Types.
Return Types
Return types are written after the parameter list:
| Return Type | Meaning |
|---|---|
: int, : string, … | Returns that type |
: ?User | Returns a User object or null |
: int|false | Returns an integer or false |
: void | Returns nothing |
: never | Never returns (throws or exits) |
: static | Returns an instance of the class the method was called on (PHP 8.0+, used in classes) |
Return types are covered in Return Values.
Practical Example: A Typed Price Calculator
<?php
declare(strict_types=1);
function priceWithDiscount(
float $price,
int $quantity,
?float $discountPercent = null,
): float {
if ($quantity < 1) {
throw new InvalidArgumentException('Quantity must be at least 1.');
}
$total = $price * $quantity;
if ($discountPercent !== null) {
$total -= $total * $discountPercent / 100;
}
return round($total, 2);
}
echo priceWithDiscount(499.0, 2) . "\n";
echo priceWithDiscount(499.0, 2, 10.0) . "\n";
echo priceWithDiscount(250, 4, 5.0) . "\n";
try {
echo priceWithDiscount(499.0, '2');
} catch (TypeError $e) {
echo "TypeError: quantity must be an int\n";
}
Output
998
898.2
950
TypeError: quantity must be an int
Step-by-Step Explanation
- Strict mode ensures values arrive with exactly the declared types, except that integers are accepted for
floatparameters (priceWithDiscount(250, 4, 5.0)works). ?float $discountPercent = nullmakes the discount optional and usesnullto mean "no discount".- Types guarantee the kind of value, but not its range; the
$quantity < 1check enforces a business rule that the type alone cannot express. - The string
'2'is rejected in strict mode, so the bug in the caller is found immediately.
Common Mistakes
Confusing Nullable With Optional
Effect: function f(?string $x) still requires an argument.
Fix: Add a default: ?string $x = null.
Assuming Types Validate Business Rules
Effect: int $age accepts -5 and 5000.
Fix: Validate ranges and formats inside the function (or use value objects).
Using mixed or No Type Everywhere
Effect: Errors surface late, and tools cannot help.
Fix: Use the most specific type that fits.
Expecting Strict Behavior Without strict_types
Effect: "5" is silently accepted for an int parameter.
Fix: Add declare(strict_types=1); at the top of the calling file if strict checking is wanted.
Implicitly Nullable Parameters
Effect: string $x = null is deprecated in PHP 8.4.
Fix: Write ?string $x = null.
Best Practices
- Declare types for every parameter and return value.
- Prefer specific types and interfaces over
mixed. - Use nullable and union types to describe real possibilities honestly.
- Combine types with validation for value ranges and formats.
- Consider enabling
declare(strict_types=1);in all new files.
Official reference: https://www.php.net/manual/en/language.types.declarations.php