PHPType Declarations & Return Types

Type Declarations & Return Types

PHP started life as an untyped, "anything goes" language, but modern PHP lets you declare exactly what type a function parameter should accept and what type it should return. These are called type declarations (sometimes "type hints"), and while they are optional, using them turns a whole category of bugs — passing the wrong kind of value into a function — into an immediate, obvious error instead of a subtle runtime surprise several calls later.

Scalar type hints on parameters

You can prefix a parameter with a type such as int, float, string, or bool, and PHP will enforce it every time the function is called. By default PHP is somewhat forgiving here: it will coerce compatible values rather than reject them outright.

Scalar type hints with default coercion

PHP
<?php
function repeat(string $text, int $times): string
{
    return str_repeat($text, $times);
}

echo repeat("ab", 3);
echo "\n";

// "5" is a numeric string - PHP coerces it to int(5) here
echo repeat("x", "5");
ababab
xxxxx
Return type declarations

Adding : type after the parameter list declares what the function promises to hand back. This is checked the same way as parameter types — PHP will coerce a compatible value to match, or raise a TypeError if it truly cannot.

A return type of int

PHP
<?php
function average(array $numbers): float
{
    return array_sum($numbers) / count($numbers);
}

var_dump(average([2, 4, 6]));
float(4)
Nullable types

Sometimes a parameter or return value can legitimately be either a specific type or null — for example, a "find user by id" function that returns null when nothing matches. Prefixing the type with a question mark, like ?int or ?string, declares that null is an acceptable value alongside the base type.

A nullable return type

PHP
<?php
function findUserName(int $id): ?string
{
    $users = [1 => "Amara", 2 => "Ben"];
    return $users[$id] ?? null;
}

var_dump(findUserName(1));
var_dump(findUserName(99));
string(5) "Amara"
NULL
The void return type

void is a special return type for functions whose purpose is a side effect — logging, printing, mutating something — rather than producing a value. A void function is not allowed to return anything with a value (a bare return; to exit early is still fine), and calling code should not try to use its result.

void signals no meaningful return value

PHP
<?php
function printReceipt(string $item, float $price): void
{
    echo "{$item}: \$" . number_format($price, 2) . "\n";
    return; // fine - returning early with no value
}

printReceipt("Coffee", 4.5);
Coffee: $4.50
declare(strict_types=1)

By default, PHP runs in weak/coercive typing mode, silently converting compatible scalar values — "5" becomes int(5), 1 becomes float(1.0), and so on — to match a declared type. Adding declare(strict_types=1); at the very top of a file switches that file's calls to strict mode: scalar type mismatches on parameters and return values throw a TypeError instead of being coerced. Note that strict mode still allows an int to widen to a float parameter, since no data is lost, but it will not accept a string where an int is declared even if that string looks numeric.

strict_types turns coercion into a TypeError

PHP
<?php
declare(strict_types=1);

function double(int $n): int
{
    return $n * 2;
}

echo double(4);
echo "\n";

try {
    echo double("4"); // no longer coerced - this is now an error
} catch (TypeError $e) {
    echo "Caught: " . $e->getMessage();
}
8
Caught: double(): Argument #1 ($n) must be of type int, string given, called in ...
declare(strict_types=1) must be the very first statement
`declare(strict_types=1);` only has an effect if it is the first statement in the file, before any other code including whitespace that produces output — in practice, it must come immediately after the opening `<?php` tag. It also only affects **that file's** function calls; it does not change how functions defined elsewhere behave when called from a file that lacks the declaration.
Union types

When a parameter or return value can genuinely be one of several unrelated types, you can declare a union type by separating them with a pipe, such as int|string. This is different from mixed, which accepts literally anything — a union type still restricts the value to the listed options.

A union type parameter

PHP
<?php
function formatId(int|string $id): string
{
    return "ID-" . $id;
}

echo formatId(42);
echo "\n";
echo formatId("A9");
ID-42
ID-A9
  • Type declarations are optional on both parameters and return values; omitting one leaves that spot dynamically typed.

  • Object/class types (e.g. a parameter typed as DateTime) are always checked strictly, even without strict_types - coercion only applies to scalar types.

  • self and static are valid return types inside a class, meaning "an instance of this class."

  • A TypeError is a genuine PHP Error, not an Exception - you must catch Error or Throwable (not just Exception) if you want to handle it.

TypeError vs a silent bug
Without any type declarations at all, passing the wrong kind of value into a function often does not fail loudly — it just produces a wrong result further down the line (e.g. string concatenation where you expected numeric addition). Type declarations convert that class of bug into an immediate, traceable `TypeError` at the exact call site where the mismatch happened.
Tip
Turn on `declare(strict_types=1);` in new files from day one rather than retrofitting it later. It is far easier to write functions with strict typing in mind from the start than to add it to an existing file and then chase down every place that was quietly relying on coercion.