Skip to main content

strict_types and Strict Typing in PHP Production

PHP · 29.09.2026

What declare(strict_types=1) Actually Does

The declare(strict_types=1) directive must be the first executable line in a file, right after the opening <?php tag. It does not enable typing as such — parameter and return types can be declared without it too. It changes only one thing: how PHP behaves when a passed value does not match the declared type.

Without strict_types, PHP tries to coerce the value automatically: the string "42" becomes the int 42, the number 1 becomes the bool true. With strict_types=1, any type mismatch, except the safe widening of int to float, throws a TypeError immediately at the point the function is called, not somewhere inside it after a few lines of logic.

Weak Typing: an Example of Implicit Conversion

The function below declares an int parameter, but without strict_types PHP will accept both a string and a floating-point number, silently cutting off the fractional part.

function applyDiscount(int $percent): float {
    return 100 - $percent;
}

echo applyDiscount("15abc"); // works as applyDiscount(15), no error

The string "15abc" is not a number, but PHP in weak mode cuts off the part that looks like a number and runs the function. The bug shows up not here but wherever the result turns out unexpectedly wrong — you end up hunting for it through the logs, as covered in the article on reading PHP logs.

With strict_types=1: the Same Error Is Caught Immediately

The same code with the directive added at the top of the file behaves differently — instead of a silent conversion, it fails explicitly.

declare(strict_types=1);

function applyDiscount(int $percent): float {
    return 100 - $percent;
}

echo applyDiscount("15abc");
// TypeError: applyDiscount(): Argument #1 ($percent) must be of type int, string given

The error appears right at the call site, with an exact line number, instead of after several steps of business logic. This matters especially for code written for PHP 8.3 and 8.4 — the new features of these versions are covered in the article on PHP 8.3 and 8.4, and they assume strict typing by default.

Property, Parameter, and Return Types: Examples

ConstructExampleWhat strict_types Checks
Parameter typefunction f(int $x)argument type at the call site
Return typefunction f(): stringvalue type after return
Class property typeprivate float $pricetype when assigning to the field
Nullable typefunction f(?int $x)allows int or null, not a string

Nullable types and union types like int|string work just as strictly with strict_types: any value outside the declared list throws a TypeError.

Where Strict Typing Breaks Existing Code

  • Data from $_GET and $_POST always arrives as strings — cast it explicitly with (int) before passing it into a typed function.
  • Values from PDO are also strings by default, even for numeric columns — without an explicit cast, strict_types throws a TypeError.
  • Old libraries without declared parameter types keep working as before; strict_types does not affect them.
  • JSON after json_decode returns numbers as int or float depending on the content — check the type before passing it further.
  • Tests written assuming automatic type coercion start failing and need explicit casts in the mocks.

Summary: Checklist for Adopting strict_types

  • declare(strict_types=1) is the first line after <?php in every file of the project.
  • All input data from $_GET, $_POST, and PDO is cast to the right type explicitly, instead of relying on automatic coercion.
  • Parameters and return values of public methods have declared types, including nullable and union types.
  • New TypeError occurrences are tracked through error_log, not through display_errors in production.
  • The change is rolled out gradually, module by module, with tests run after every file.
← Back to Knowledge Base Ask Support