In short: PHP is dynamically typed: the type of a value is only determined at runtime, and unless told otherwise PHP converts values automatically when needed. Since PHP 7, however, parameters, return values and properties can be given types, and declare(strict_types=1) makes the check strict. Anyone who knows the rules and uses strict comparisons with === avoids a whole class of logic and security errors.
Dynamically typed, but not typeless
In PHP you do not have to specify a type for $preis = 19.90;. PHP recognises that it is a floating-point number. If the value is later combined with a string, PHP converts it. This behaviour is called type juggling, that is, automatic type conversion. It makes getting started easy but can lead to surprising results.
For decision-makers this is relevant because many older PHP applications were written without type declarations. Such code is harder to review and modernise. Newer projects use types consistently, which makes errors visible earlier and lets static analysis with tools such as PHPStan or Psalm become truly effective.
The PHP types at a glance
The PHP manual names nine internal types that any expression can have. They are usually divided into three groups:
| Group | Types | Example |
|---|---|---|
| Scalar types | bool, int, float, string |
true, 42, 3.5, "Text" |
| Compound types | array, object, callable |
[1, 2], new DateTime() |
| Special types | null, resource |
null, an opened file |
For type checks, the manual recommends the is_*() functions such as is_int(). For inspection, get_debug_type() is suitable; since PHP 8.0.0 it returns the commonly used type names, whereas the older gettype() returns, for example, "integer" and "double".
Besides automatic conversion there is explicit conversion: with a cast such as (int) $wert or with settype(), the code itself decides which type a value should have. This is common for input from forms, because everything that arrives via $_GET or $_POST is initially a string or an array. A cast does not check whether the value made sense, though: "abc" silently becomes 0. For input, a genuine check with filter_var() is therefore usually the better choice.
Classes and interfaces are also types in PHP. A function can therefore require that a parameter be an object of a particular class or satisfy a particular interface. How classes, interfaces and enums work together is explained on the page Object orientation in PHP.
Type declarations: from PHP 7.1 to 8.3
Type declarations define which values a function accepts and returns. The type system has grown over many versions. According to the manual's changelog, these capabilities were added, among others:
| Version | New |
|---|---|
| 7.1.0 | nullable types (?int), void, iterable |
| 7.2.0 | object |
| 7.4.0 | typed class properties |
| 8.0.0 | union types (several permitted types in one declaration), mixed, return type static |
| 8.1.0 | intersection types (A&B), return type never |
| 8.2.0 | DNF types, literal type true, null and false as standalone types |
| 8.3.0 | typed class, interface, trait and enum constants |
DNF stands for disjunctive normal form and allows combinations such as (A&B)|null. Anyone planning PHP versions can also see from this table which language features an application requires.
strict_types: conversion or TypeError
Without further declaration, PHP works in coercive mode. A function with int parameters then also accepts 1.5 and turns it into 1; since PHP 8.1 this lossy conversion is marked as deprecated. With declare(strict_types=1) at the top of the file, PHP throws a TypeError instead.
<?php
declare(strict_types=1);
function summe(int $a, int $b): int
{
return $a + $b;
}
echo summe(1, 2); // 3
echo summe(1.5, 2.5); // TypeError, instead of silently 3
Two subtleties are important. First, strict_types applies to calls made from the file in which it appears, not to the functions defined there. If a file without strict_types calls the same function, values are converted. Second, an integer where a float is expected is permitted even in strict mode.
Loose and strict comparisons
The == operator compares after type conversion; === compares value and type without conversion. In most cases the manual recommends === and !==, because the conversion rules are not always obvious.
<?php
var_dump(0 == "a"); // PHP 7: true, PHP 8: false
var_dump("1" == "01"); // true, both are numeric strings
var_dump("1" === "01");// false, same type, but a different string
var_dump(1 == true); // true
var_dump(1 === true); // false
With PHP 8.0, comparisons between numbers and non-numeric strings became more sensible. Older applications can behave differently after the upgrade, for example in switch blocks. The match expression, likewise introduced with PHP 8.0, always compares strictly. How loose comparisons lead to security vulnerabilities, for example in token or password checks, is described in the article Type juggling: loose comparisons as a security risk.
Types, upgrades and PHP2Shell
A strict type system makes application code more robust, but it does not protect against flaws in the interpreter itself. The PHP2Shell vulnerabilities CVE-2026-109618 and CVE-2026-109619 lie in PHP's memory and request handling. They are fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35; afterwards all PHP processes must restart.
The type system still plays a role, namely during the upgrade. Applications still stuck on PHP 8.1 or older no longer receive official updates from the PHP project. Well-typed code is much easier to move to a supported version, because tools find incompatibilities early. For older projects, our migration to a supported PHP version can help.
Enable strict_types step by step in new and reworked files. That way type safety grows without old code breaking all at once.
How does PHP SOS help?
- We check which PHP version your application needs at a minimum and compare that with the fixed versions.
- We run static analysis over the code and flag places with loose comparisons in security-relevant checks.
- We plan upgrades so that changed comparison rules are tested beforehand.
- We apply the PHP update, restart all workers and document the status.
Frequently asked questions
Is PHP strongly or weakly typed?
PHP is dynamically typed: the type of a variable is only determined at runtime. Without strict_types, PHP converts values when needed. With type declarations and strict_types, however, code can be made very strict.
What does declare(strict_types=1) do?
It switches off the automatic conversion of scalar values for calls made from that file. If a value does not match the declared type, PHP throws a TypeError. The only exception is an integer where a float is expected.
Why is 0 == "a" false in PHP 8 but true in PHP 7?
Up to PHP 7, a non-numeric string compared with a number was converted to a number, so "a" became 0. Since PHP 8.0, PHP no longer compares numerically in this case, and the result is false.
Do types protect against vulnerabilities in PHP itself?
No. Types prevent errors in your application code. Flaws in the interpreter such as PHP2Shell are closed only by a PHP update. Modern type features do require current PHP versions, though, and are therefore a good reason to upgrade.