In short: Static analysis checks PHP code without executing it and thereby finds type errors, undefined variables, dead code and calls on possibly empty values. The two common tools are PHPStan (rule levels 0 to 10) and Psalm (error levels 1 to 8, plus a taint analysis for unsafe data flows). They find errors in your application, not in the PHP interpreter; against vulnerabilities such as PHP2Shell, only the PHP update helps.
What is static analysis?
A test executes code and compares the result. Static analysis, by contrast, reads the code like a very thorough reviewer and infers from types, return values and calls where something can go wrong. It finds errors even in branches that no test ever reaches.
For decision-makers the benefit is simple: errors found before going live cost a fraction of errors that customers notice. For developers it is a constant companion that runs along in the development environment and in the CI pipeline. If the analysis raises an alert on a change, that change is not accepted until the error is fixed. That way no new legacy accumulates while the existing one is reduced step by step. Together with tests with PHPUnit and Pest, it forms the foundation of code quality.
PHPStan: rule levels from 0 to 10
PHPStan is installed as a development dependency and needs PHP 7.4 or later to run:
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src tests
Without further settings, PHPStan checks only the bare minimum. You control the strictness through eleven levels, each of which includes those below it:
| Level | What is additionally checked (selection) |
|---|---|
| 0 | unknown classes and functions, wrong number of arguments, always-undefined variables |
| 3 | return types and the types assigned to properties |
| 5 | types of arguments in function and method calls |
| 6 | missing type declarations |
| 8 | method calls and property accesses on values that can be null |
| 10 | strictest handling of mixed, including missing types |
With --level max, the highest level always runs. The documentation warns, however, that an upgrade of PHPStan can then bring many new messages. A typical finding from level 8:
<?php
function kundenname(?Kunde $kunde): string
{
return $kunde->name(); // PHPStan: call on a value that can be null
}
Introducing PHPStan without a flood of errors: the baseline
In grown projects, PHPStan at a high level often reports hundreds of places. The baseline solves the problem: it saves all existing messages in a file, and from then on PHPStan reports only new errors.
vendor/bin/phpstan analyse --level 6 src tests --generate-baseline
You include the generated file phpstan-baseline.neon in the configuration:
includes:
- phpstan-baseline.neon
parameters:
level: 6
paths:
- src
- tests
That way the legacy stock stays visibly documented, while new code has to be clean from the start.
Psalm and taint analysis
Psalm pursues similar goals to PHPStan. The levels run the opposite way: 1 is the strictest, 8 the most lenient, and the default is 2. Psalm requires PHP 8.2 or later and is set up like this:
composer require --dev vimeo/psalm
./vendor/bin/psalm --init
./vendor/bin/psalm --taint-analysis
Taint analysis is particularly interesting for security. “Taint” means contamination: Psalm marks data from sources that an attacker can control, such as $_GET, $_POST, $_COOKIE, $_REQUEST or php://input. It then tracks whether this data arrives unprotected at dangerous places, the so-called sinks: output with echo, include, header or SQL queries. The built-in categories include sql, html, shell, include, eval, ssrf and unserialize.
<?php
// Psalm reports a flow from $_GET into the HTML output (category html)
echo 'Hallo ' . $_GET['name'];
// Fixed: escape output appropriately for its context
echo 'Hallo ' . htmlspecialchars($_GET['name'] ?? '', ENT_QUOTES, 'UTF-8');
The Psalm documentation states the limits openly: the analysis assumes that escaping is done correctly. Subtle errors, such as missing quotation marks around an HTML attribute, can still leave gaps. Psalm therefore recommends parameterised queries for SQL and context-aware templates for HTML. Results can be output in SARIF format, which GitHub Code Scanning, among others, understands.
Static analysis and security after PHP2Shell
An honest delimitation is worthwhile here. PHP2Shell, that is CVE-2026-109618 and CVE-2026-109619, sits in the PHP interpreter. No tool that reads your application code finds these vulnerabilities, and no code change closes them. Only the update to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 does that.
Even so, static analysis contributes to security in three places:
- Upgrade readiness: It finds code that no longer runs on new PHP versions. This lowers the hurdle of moving from 8.1 or 8.2 to a supported version.
- Your own vulnerabilities: Taint analysis finds paths for SQL injection, cross-site scripting or file inclusion. In its security policy, the PHP project makes clear that unchecked input to functions such as
mysqli_query()is not a bug in PHP but the application's responsibility. - Robustness: Many fatal errors arise from calls on null or wrong types. In connection with CVE-2026-109619, php.net expressly advises fixing externally triggerable fatal errors in the application.
Static analysis does not replace a security code review by humans, but it makes it more efficient because routine errors have been filtered out beforehand.
How does PHP SOS help?
As part of a security audit and penetration test, we proceed as follows:
- Set up tools: PHPStan or Psalm with a suitable level and baseline, integrated into your existing pipeline.
- Assess security findings: We review the results of the taint analysis and separate genuine risks from false alarms.
- Prepare the upgrade: We show which places block a move to a supported PHP version.
- Hand over results: You receive a prioritised list with concrete code locations for your team or agency.
Frequently asked questions
What is the difference between PHPStan and Psalm?
Both check types and find errors without execution. PHPStan counts its levels from 0 (lenient) to 10 (strict), Psalm from 8 (lenient) to 1 (strict). Psalm comes with a built-in taint analysis for security problems.
Can I introduce PHPStan into an old project?
Yes. With a baseline, PHPStan saves all existing messages in a file and afterwards reports only new errors. That way the team starts without a flood of errors and reduces legacy issues step by step.
Does static analysis find PHP2Shell?
No. PHP2Shell sits in the PHP interpreter, not in your code. Only the PHP update helps against it. Static analysis does help prepare the code for new PHP versions, though, so that the update is possible faster.
Which level should I choose?
To start, a low level without a baseline or a higher level with a baseline. More important than the number is that the analysis runs automatically with every change and does not let new errors through.