PHP 8 introduced and enhanced several features related to strict typing, also known as “strict mode.” This feature enforces stricter type checks during function calls. Here’s an overview of how it works:

1. Strict Types Declaration

  • PHP allows you to enable strict typing on a per-file basis by declaring strict_types=1 at the beginning of the file. When strict types are enabled, PHP will enforce strict type checking for function calls and return types.
  Copy code
<?php
declare(strict_types=1);

function add(int $a, int $b): int {
    return $a + $b;
}

echo add(1, 2); // 3
echo add(1.5, 2.5); // TypeError: Argument 1 passed to add() must be of the type int, float given
  

2. Behavior of Strict Mode

  • With strict_types=1: PHP requires the exact data type specified in the function signature. If you pass a float where an integer is expected, a TypeError will be thrown.
  • Without strict_types=1 (Default): PHP will attempt to coerce the type, converting a float to an integer, for example.
  // Without strict_types=1
function add(int $a, int $b): int {
    return $a + $b;
}

echo add(1.5, 2.5); // 3
  

3. Return Type Declarations

  • PHP 8 allows you to specify the return type of a function. In strict mode, the returned value must match the declared return type exactly.
  function multiply(float $a, float $b): float {
    return $a * $b;
}

echo multiply(2.5, 4.2); // 10.5
  

4. TypeError Exceptions

  • If a function is called with arguments that do not match the declared types, or if a function returns a value that does not match the declared return type, a TypeError exception will be thrown.

5. Nullable Types

  • PHP 8 supports nullable types, which allow null to be passed to a function or returned from it, even if the type is otherwise strict.
  function getName(?string $name): ?string {
    return $name;
}

echo getName(null); // null
  

6. Union Types

  • PHP 8 introduced union types, allowing a parameter or return type to accept more than one type.
  function processInput(int|string $input): int|string {
    if (is_int($input)) {
        return $input * 2;
    } else {
        return strtoupper($input);
    }
}

echo processInput(5); // 10
echo processInput("hello"); // HELLO
  

Strict Types Across Files

declare(strict_types=1) applies only to the file where it is declared. If FileA.php calls a function defined in FileB.php, strictness depends on the calling file:

  // FileA.php — strict
declare(strict_types=1);
require 'FileB.php';
add(1.5, 2.5); // TypeError

// FileB.php — no strict declaration
function add(int $a, int $b): int { return $a + $b; }
  

Enable strict types in every project file for consistent behavior.

Scalar Type Declarations

PHP 7+ supports scalar hints on parameters and returns:

  declare(strict_types=1);

function formatPrice(float $amount): string {
    return number_format($amount, 2);
}
  

Intersection Types (PHP 8.1+)

  declare(strict_types=1);

function process(Countable&Iterator $collection): void {
    echo count($collection);
}
  

Readonly Properties (PHP 8.2+)

Combine strict typing with immutable objects:

  declare(strict_types=1);

class User {
    public function __construct(
        public readonly int $id,
        public readonly string $email,
    ) {}
}
  

Best Practices

  • Add declare(strict_types=1); as the first statement after <?php in every new file.
  • Use union and nullable types instead of loose checks like is_numeric().
  • Let TypeError surface bugs early rather than silently coercing "123abc" to 123.

Common Pitfalls

  • Enabling strict types in only some files leads to inconsistent coercion behavior.
  • Forgetting that JSON-decoded numbers arrive as int or float, but form data arrives as string.
  • Using weak comparison (==) alongside strict types — prefer === everywhere.