Type Declarations

PHP is dynamically typed, but you can declare the types a function accepts and returns, and the types of class properties. PHP checks them at run time, your editor uses them for autocompletion and error-spotting, and static analysis tools can find bugs before your code ever runs. Modern PHP code declares types almost everywhere.

Parameter and return types

PHP
<?php

declare(strict_types=1);

function formatPrice(float $amount, string $currency = "EUR"): string
{
    return number_format($amount, 2) . " $currency";
}

echo formatPrice(19.5), "\n";
echo formatPrice(19.5, ["EUR"]), "\n";
Output
19.50 EUR

Fatal error: Uncaught TypeError: formatPrice(): Argument #2 ($currency) must be of type string, array given, called in main.php on line 11 and defined in main.php:5
Stack trace:
#0 main.php(11): formatPrice(19.5, Array)
#1 {main}
  thrown in main.php on line 5

Passing the wrong type throws a TypeError with a precise message — far better than a wrong result appearing somewhere much later.

Available types

TypeAccepts
int, float, string, boolScalars
array, iterable, callable, objectArrays; anything you can loop over; anything callable; any object
A class or interface nameInstances of it (or subclasses)
self, staticThe current class (inside classes)
mixedAnything at all
voidReturn type only: returns nothing
neverReturn type only: never returns (always throws or exits)
null, true, falseExactly that value (usually in a union)

Nullable and union types

?Type means "Type or null". A union type A|B accepts any of several types:

PHP
<?php

declare(strict_types=1);

function findUser(int $id): ?array
{
    $users = [1 => ["name" => "Ana"]];
    return $users[$id] ?? null;
}

function toCents(int|float|string $amount): int
{
    return (int) round((float) $amount * 100);
}

var_dump(findUser(1));
var_dump(findUser(7));
echo toCents(12), " ", toCents(12.345), " ", toCents("9.99"), "\n";
Output
array(1) {
  ["name"]=>
  string(3) "Ana"
}
NULL
1200 1235 999

A parameter with a default of null must still be declared nullable explicitly — ?string $x = null. Writing string $x = null (an "implicitly nullable" type) is deprecated since PHP 8.4.

Coercive mode versus strict mode

Without declare(strict_types=1), PHP coerces scalar arguments to the declared type when it can: "42" becomes 42 for an int parameter. With strict types, only an exact match is accepted (an int is still allowed where a float is expected):

PHP coercive.php
<?php

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

var_dump(double("21"));
var_dump(double(true));
var_dump(double("21 apples"));
Output
int(42)
int(2)

Fatal error: Uncaught TypeError: double(): Argument #1 ($n) must be of type int, string given, called in coercive.php on line 10 and defined in coercive.php:3
Stack trace:
#0 coercive.php(10): double('21 apples')
#1 {main}
  thrown in coercive.php on line 3
PHP strict.php
<?php

declare(strict_types=1);

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

function half(float $n): float
{
    return $n / 2;
}

var_dump(half(5));        // int to float is allowed
var_dump(double("21"));
Output
float(2.5)

Fatal error: Uncaught TypeError: double(): Argument #1 ($n) must be of type int, string given, called in strict.php on line 16 and defined in strict.php:5
Stack trace:
#0 strict.php(16): double('21')
#1 {main}
  thrown in strict.php on line 5

The declare line applies to calls made from that file. Strict mode catches bugs earlier and is the norm in modern projects; convert input explicitly at the edges of your program (for example with filter_var on form fields), and keep everything inside strictly typed.

Return types are checked too

PHP
<?php

declare(strict_types=1);

function percentage(int $part, int $whole): int
{
    return $part / $whole * 100;
}

echo percentage(50, 200), "\n";
echo percentage(1, 3), "\n";
Output
Fatal error: Uncaught TypeError: percentage(): Return value must be of type int, float returned in main.php:7
Stack trace:
#0 main.php(10): percentage(50, 200)
#1 {main}
  thrown in main.php on line 7

The declared return type caught a real bug on the very first call: / produces a float — even 50 / 200 * 100 is 25.0, not 25 — and strict mode won't silently turn a float into an int. The fix is to decide what's actually wanted: (int) round(…), or a float return type.

void and never

PHP
<?php

declare(strict_types=1);

function logLine(string $message): void
{
    echo date('H:i', 0), " $message\n";
    // return $message;   // not allowed in a void function
}

function fail(string $message): never
{
    throw new RuntimeException($message);
}

logLine("Starting");
fail("Configuration missing");
Output
00:00 Starting

Fatal error: Uncaught RuntimeException: Configuration missing in main.php:13
Stack trace:
#0 main.php(17): fail('Configuration m...')
#1 {main}
  thrown in main.php on line 13

never tells readers and tools that code after a call to fail() is unreachable.

Intersection and DNF types

An intersection type A&B requires an object to implement both interfaces. Since PHP 8.2 they can be combined with unions in "disjunctive normal form", such as (A&B)|null:

PHP
<?php

declare(strict_types=1);

function describe((Countable&ArrayAccess)|null $collection): string
{
    return $collection === null ? "nothing" : count($collection) . " items";
}

echo describe(new ArrayObject([1, 2, 3])), "\n";
echo describe(null), "\n";
Output
3 items
nothing

Typed properties

Class properties can have types as well; you'll use them throughout the object-oriented lessons:

PHP
<?php

declare(strict_types=1);

class Product
{
    public string $name = "";
    public float $price = 0.0;
    public ?string $sku = null;
}

$p = new Product();
$p->name = "Lamp";
$p->price = 34.5;
$p->price = "cheap";
Output
Fatal error: Uncaught TypeError: Cannot assign string to property Product::$price of type float in main.php:15
Stack trace:
#0 {main}
  thrown in main.php on line 15

Beyond run-time checks

Types can't express everything — "an array of strings" for instance. Docblock annotations like @param list<string> $names or @return array<int, Product> fill the gap, and static analysers such as PHPStan and Psalm check them across your whole codebase without running it (lesson 63).

Try it yourself

Add types to every parameter and return value, then try calling the function with a string to see strict mode in action.

Try it yourself · main.php
Edit the code, copy it into main.php, then run it with php main.php (PHP 8.5). How to install PHP
Output
BMI 22.9: healthy