Psalm vs. PHPStan: Unterschiede in Analyse-Tiefe und Ökosystem
AI generated
<?php
8.4
PHP · Static Analysis · Psalm · PHPStan
Psalm vs. PHPStan
Unterschiede in Analyse-Tiefe und Ökosystem

Psalm vs. PHPStan ist selten eine Frage von richtig oder falsch, sondern von Prioritäten: Taint Analysis und aggressives Type Narrowing sprechen für Psalm, ein größeres Plugin-Ökosystem und ausgereifte Generics-Unterstützung sprechen für PHPStan. Dieser Artikel vergleicht beide Tools technisch und liefert einen praktischen Entscheidungsrahmen für Teams.

16 Min. Lesezeit Taint Analysis · Generics · Ökosystem · Entscheidungshilfe PHP 8.4 · Composer · CI/CD

1. Warum Psalm vs. PHPStan keine reine Geschmacksfrage ist

Beide Tools lesen denselben PHP-Code und denselben PHPDoc-Dialekt, beide erkennen falsche Argumenttypen, unmögliche Vergleiche und riskante Null-Zugriffe. Wer nur eine Feature-Liste vergleicht, kommt schnell zu dem Schluss, beide Tools seien austauschbar. Bei Psalm vs. PHPStan lohnt sich aber ein genauerer Blick auf die Analyse-Tiefe in Spezialbereichen und auf das jeweilige Ökosystem drumherum, denn genau dort unterscheiden sich beide Tools spürbar in der Praxis.

Psalm wurde von Vimeo mit einem klaren Fokus auf Typsicherheit und Sicherheitsanalyse entwickelt und bringt eine Taint-Analysis-Engine mit, die es in dieser Form bei PHPStan im Kern nicht gibt. PHPStan wiederum, entwickelt von Ondřej Mirtes, hat sich durch eine sehr aktive Community und ein breites Ökosystem an Erweiterungen für Frameworks, Testbibliotheken und Spezialfälle zum De-facto-Standard in vielen Symfony- und Laravel-nahen Projekten entwickelt. Beide Projekte sind Open Source, beide werden aktiv weiterentwickelt, beide unterstützen PHP 8.4 mit den aktuellen Sprachfeatures.

Die folgenden Abschnitte arbeiten die konkreten Unterschiede zwischen Psalm vs. PHPStan heraus, von Sicherheitsanalyse über Typsystem-Feinheiten bis zur praktischen Frage, welches Tool sich für welches Team eignet, und ob ein Parallelbetrieb beider Tools überhaupt sinnvoll ist.

2. Taint Analysis: Psalms Alleinstellungsmerkmal im Vergleich Psalm vs. PHPStan

Taint Analysis verfolgt, wie unsichere Eingaben, etwa aus $_GET, $_POST oder externen APIs, durch den Code fließen, bis sie an einer gefährlichen Stelle landen, etwa in einer SQL-Query, einem echo-Aufruf ohne Escaping, oder einem Shell-Befehl. Psalm markiert solche Eingaben intern als "tainted" und meldet einen Fehler, sobald ein solcher Wert ungefiltert an eine sensible Senke gelangt, ohne dass eine erkannte Sanitizing-Funktion dazwischenliegt. Das ist ein qualitativ anderer Analysetyp als reine Typprüfung, er verfolgt den Datenfluss über mehrere Funktionsaufrufe hinweg.


<?php

declare(strict_types=1);

namespace App\Http;

final class SearchController
{
    public function __construct(private readonly \PDO $pdo)
    {
    }

    public function search(): void
    {
        // Psalm's taint analysis flags this: user input flows
        // directly into a raw SQL string without a sanitizer.
        $term = $_GET['q'] ?? '';
        $stmt = $this->pdo->query("SELECT * FROM products WHERE name LIKE '%{$term}%'");

        foreach ($stmt->fetchAll() as $row) {
            // Also flagged: unescaped output of tainted data (XSS sink).
            echo $row['name'];
        }
    }
}

PHPStan besitzt keine vergleichbare eingebaute Taint-Engine, es prüft Typen und Kontrollfluss, aber nicht, ob eine string-Variable ursprünglich aus einer nicht vertrauenswürdigen Quelle stammt. Für Projekte mit hohem Sicherheitsanspruch, etwa öffentlich erreichbare APIs oder Formulare mit Datenbankzugriff, ist das ein entscheidender Unterschied bei Psalm vs. PHPStan: Psalm kann SQL-Injection- und XSS-Muster finden, die eine reine Typprüfung grundsätzlich nicht erkennen kann, weil der Datentyp selbst, etwa string, unverändert bleibt, nur seine Herkunft ist relevant.

In der Praxis aktiviert man Taint Analysis über taintAnalysis="true" in psalm.xml und deklariert eigene Sinks und Sources über @psalm-taint-sink und @psalm-taint-source, falls projektspezifische Funktionen wie ein eigener Datenbank-Wrapper von der automatischen Erkennung nicht erfasst werden. Der Aufwand lohnt sich vor allem in Codebasen, die direkten Nutzer-Input verarbeiten, weniger in reinen Batch- oder CLI-Anwendungen ohne externe Angriffsfläche.

3. Immutability und Reinheit: @psalm-immutable und @psalm-pure

Psalm bringt ein eigenes Set an Annotationen für funktionale Eigenschaften mit, die PHPStan im Kern nicht kennt. @psalm-immutable markiert eine Klasse als unveränderlich, Psalm prüft dann statisch, dass keine Methode nach der Konstruktion einen Property-Wert verändert, und meldet einen Fehler, sobald eine solche Klasse doch einen Setter oder eine direkte Zuweisung außerhalb des Konstruktors enthält. @psalm-pure markiert eine Funktion als seiteneffektfrei, ihr Rückgabewert hängt ausschließlich von ihren Argumenten ab, keine globalen Zustände, keine I/O, kein static-Zugriff.


<?php

declare(strict_types=1);

namespace App\ValueObject;

/**
 * @psalm-immutable
 */
final class Money
{
    public function __construct(
        private readonly int $cents,
        private readonly string $currency,
    ) {
    }

    /**
     * @psalm-pure
     */
    public function add(self $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new \InvalidArgumentException('Currency mismatch');
        }

        // Psalm verifies this returns a new instance without mutating $this.
        return new self($this->cents + $other->cents, $this->currency);
    }
}

Der Nutzen dieser Annotationen liegt darin, dass Psalm sie in weiterführenden Analysen ausnutzt: Ein als @psalm-pure markierter Aufruf darf mehrfach im selben Ausdruck vorkommen, ohne dass Seiteneffekte befürchtet werden müssen, und Psalm kann daraus zusätzliche Optimierungs- und Korrektheitsschlüsse ziehen, etwa beim Vergleich zweier Aufrufe mit identischen Argumenten. PHPStan kennt Immutability nur indirekt über readonly-Properties auf Sprachebene seit PHP 8.1, es gibt aber keine vergleichbare Annotation, die eine ganze Klasse oder Funktion als rein deklariert und dies aktiv gegen den Methodenkörper prüft.

Für Value-Objects, DTOs und funktional orientierten Code ist das bei Psalm vs. PHPStan ein spürbarer Unterschied in der Ausdruckskraft: Wer konsequent mit unveränderlichen Objekten arbeitet, bekommt mit Psalm eine zusätzliche, statisch geprüfte Garantie, dass diese Unveränderlichkeit im gesamten Codebestand tatsächlich eingehalten wird, statt sich nur auf Konvention und Code-Review zu verlassen.

4. Type Narrowing und Psalms Typsystem-Erweiterungen

Type Narrowing bezeichnet die Fähigkeit eines Analyzers, den Typ einer Variablen innerhalb eines Codeblocks anhand vorangegangener Prüfungen einzuschränken, etwa nach einem instanceof oder einer is_string()-Prüfung. Psalm gilt hier traditionell als aggressiver: Es verfolgt Typinformation über komplexere Kontrollflüsse hinweg, etwa nach einem frühen return in einer Bedingung, nach Zuweisungen in Schleifen, oder über mehrere verschachtelte if-Bedingungen mit logischen Verknüpfungen.

Zusätzlich bietet Psalm eigene Typsystem-Erweiterungen, die über den PHPDoc-Standard hinausgehen: @psalm-assert deklariert an einer Funktion, welcher Typ nach einem erfolgreichen Aufruf garantiert ist, etwa dass ein Parameter danach garantiert kein null mehr ist, nützlich für eigene Validierungs- und Guard-Funktionen. Conditional Types erlauben es, den Rückgabetyp einer Funktion vom Typ eines Parameters abhängig zu machen, ein Muster, das in generischen Bibliotheken mit mehreren möglichen Rückgabetypen auftaucht.


<?php

declare(strict_types=1);

namespace App\Assert;

final class Guard
{
    /**
     * @psalm-assert !null $value
     */
    public static function notNull(mixed $value, string $message): void
    {
        if ($value === null) {
            throw new \InvalidArgumentException($message);
        }
    }
}

// Usage: after this call, Psalm narrows $order to non-null
// for the rest of the function without an extra if-check.
function processOrder(?Order $order): void
{
    Guard::notNull($order, 'Order must not be null');
    $order->markAsShipped();
}

PHPStan unterstützt inzwischen ebenfalls Assert-artige Annotationen über @phpstan-assert, seit einigen Versionen mit vergleichbarer Funktionalität, sodass dieser konkrete Unterschied bei Psalm vs. PHPStan in den letzten Jahren kleiner geworden ist. Bei komplexeren, tief verschachtelten Kontrollflüssen und bei der Kombination mehrerer Bedingungen bleibt Psalm nach Erfahrung vieler Teams aber weiterhin tendenziell präziser, was insbesondere bei Refactorings älteren, verschachtelten Codes zu weniger False Positives führt.

5. PHPStans Stärken: Ökosystem, Generics-Reife und Baseline-Tooling

Der entscheidende Vorteil von PHPStan bei Psalm vs. PHPStan liegt selten in einem einzelnen Feature, sondern im Umfeld: Für praktisch jedes verbreitete PHP-Framework existiert eine gepflegte PHPStan-Extension, etwa phpstan-symfony, phpstan-doctrine oder phpstan-mockery, die Framework-spezifisches Verhalten wie Dependency Injection oder ORM-Magie korrekt in die Analyse einbezieht. Für Psalm existieren vergleichbare Plugins, das Angebot ist aber insgesamt kleiner und die Pflege einzelner Plugins schwankt stärker.

Generics über @template wurden von PHPStan und Psalm parallel entwickelt, in der Praxis gilt PHPStans Implementierung heute als ausgereifter in Randfällen, etwa bei mehreren Templates in derselben Klasse, bei Vererbungsketten über mehrere Ebenen, oder bei der Kombination von Generics mit Union Types. Für Bibliotheksautoren, die komplexe generische Collections oder Repository-Abstraktionen bauen, ist das ein praktisch relevanter Unterschied, weil Edge Cases in Psalm gelegentlich zu falschen Fehlermeldungen führen, wo PHPStan den Typ korrekt auflöst.

Baseline-Tooling existiert bei beiden Tools, phpstan analyse --generate-baseline und psalm --set-baseline funktionieren nach demselben Prinzip. PHPStans Level-System mit den Stufen 0 bis 9 plus max bietet dabei eine klarere, in der Community stärker etablierte Ratcheting-Konvention als Psalms Error-Level-System 1 bis 8, was die Kommunikation über den aktuellen Strictness-Grad eines Projekts im Team und in Stellenanzeigen erfahrungsgemäß vereinfacht.

6. Konfiguration im Vergleich: psalm.xml vs. phpstan.neon

Beide Tools nutzen ein deklaratives Konfigurationsformat, unterscheiden sich aber in Syntax und Struktur deutlich. Psalm nutzt XML mit einem <projectFiles>-Block für den Analyse-Scope und errorLevel als Zahl von 1 (strengste Stufe) bis 8 (lockerste Stufe), eine für viele Neueinsteiger zunächst gegenläufige Zählweise im Vergleich zu PHPStans Leveln.


<?xml version="1.0"?>
<psalm
    errorLevel="2"
    resolveFromConfigFile="true"
    findUnusedBaselineEntry="true"
    findUnusedCode="false"
>
    <projectFiles>
        <directory name="src" />
        <ignoreFiles>
            <directory name="src/Legacy" />
        </ignoreFiles>
    </projectFiles>

    <taintAnalysis>
        <directory name="src/Http" />
    </taintAnalysis>
</psalm>

PHPStan nutzt stattdessen NEON, ein YAML-ähnliches Format, mit level als Zahl von 0 (lockerste Stufe) bis 9 oder dem Alias max, hier steigt die Strenge intuitiv mit der Zahl, was in Diskussionen über Psalm vs. PHPStan häufig als kleiner, aber spürbarer Vorteil für die Onboarding-Erfahrung neuer Teammitglieder genannt wird.


# phpstan.neon
parameters:
    level: 8
    paths:
        - src
    excludePaths:
        - src/Legacy/*
    checkMissingIterableValueType: true

Funktional sind beide Konfigurationsansätze gleichwertig mächtig, beide unterstützen Pfad-Ausschlüsse, Baseline-Includes und projektspezifische Strictness-Parameter. Der Unterschied liegt eher in der Vertrautheit des Formats für das jeweilige Team: Wer bereits phpunit.xml oder ähnliche XML-Konfigurationen im Projekt hat, findet sich in psalm.xml schneller zurecht, wer mit YAML-basierten CI-Configs arbeitet, empfindet NEON als natürlicher.

7. Migration und Parallelbetrieb: Beide Tools gleichzeitig?

Technisch spricht nichts dagegen, Psalm und PHPStan im selben Projekt parallel laufen zu lassen, beide lesen denselben Quellcode, ohne sich gegenseitig zu beeinflussen, und beide lassen sich über separate Composer-Skripte und CI-Jobs unabhängig voneinander ausführen. In der Praxis lohnt sich das vor allem, wenn ein Projekt gezielt Psalms Taint Analysis für sicherheitskritische Bereiche nutzen will, während der Rest der Codebasis unter PHPStan mit seinem größeren Framework-Ökosystem läuft.

Der Kostenpunkt eines Parallelbetriebs liegt in der doppelten Pflege von Ignore-Listen und Annotationen: Manche PHPDoc-Muster werden von beiden Tools unterschiedlich interpretiert, was zu Situationen führt, in denen eine Annotation, die PHPStan zufriedenstellt, bei Psalm einen neuen Fehler auslöst, und umgekehrt. Realistisch bewährt sich Parallelbetrieb eher in kleineren, klar abgegrenzten Sicherheitsmodulen als über eine gesamte, große Codebasis hinweg.

Eine vollständige Migration von einem Tool zum anderen ist aufwendiger als der Wechsel eines PHPStan-Levels, weil Annotationen wie @psalm-immutable oder @psalm-assert keine direkte Entsprechung bei PHPStan haben und im Zweifel entfernt oder durch PHPStan-eigene Alternativen wie readonly-Klassen ersetzt werden müssen. Bei Psalm vs. PHPStan lohnt sich die Entscheidung deshalb möglichst früh im Projekt, ein später Wechsel ist machbar, aber mit spürbarem manuellen Aufwand verbunden.

8. Ein Entscheidungsrahmen für Teams

Für Teams, die primär öffentlich erreichbare Endpunkte, Formulare oder APIs mit direktem Nutzer-Input betreiben und Sicherheitsanalyse als harte Anforderung sehen, spricht die Taint Analysis klar für Psalm, ein Feature, das sich mit PHPStan im Kern nicht nachbilden lässt. Für Teams, die primär auf einem verbreiteten Framework wie Symfony oder Laravel aufsetzen und von einem breiten, gut gepflegten Extension-Ökosystem profitieren wollen, ist PHPStan meist die pragmatischere Wahl.

Für Bibliotheksautoren mit komplexen generischen Typen zahlt sich PHPStans ausgereiftere Generics-Implementierung aus, während funktional orientierte Codebasen mit striktem Fokus auf Immutability von Psalms @psalm-immutable- und @psalm-pure-Annotationen profitieren. Bei gemischten Teams mit unterschiedlichen Präferenzen entscheidet oft die Erfahrung: Ein Team, das bereits mit einem der beiden Tools vertraut ist, sollte diesen Vorsprung nicht ohne triftigen Grund aufgeben, denn die Lernkurve für ein neues Static-Analysis-Tool ist real, auch wenn beide Tools ähnliche PHPDoc-Konventionen teilen.

Ein pragmatischer Zwischenweg für unentschlossene Teams: Beide Tools zunächst mit niedrigem Level bzw. niedrigem errorLevel parallel in einem Proof of Concept über einen begrenzten Teil der Codebasis laufen lassen, die Anzahl und Art der gefundenen Fehler vergleichen, und danach eine Entscheidung auf Basis konkreter, projekteigener Daten statt reiner Theorie treffen.

9. Psalm vs. PHPStan im direkten Vergleich

Die folgende Tabelle fasst die wichtigsten Unterschiede zwischen Psalm vs. PHPStan entlang der Dimensionen zusammen, die in der Praxis am häufigsten den Ausschlag geben.

Dimension Psalm PHPStan
Taint Analysis Eingebaut, SQL-Injection und XSS-Muster Nicht im Kern vorhanden
Immutability-Annotationen @psalm-immutable, @psalm-pure Nur über readonly-Properties
Generics-Reife Solide, Edge Cases gelegentlich ungenau Ausgereifter bei komplexen Vererbungsketten
Plugin-Ökosystem Kleiner, wechselnde Pflege Groß, viele Framework-Extensions
Baseline-Tooling --set-baseline, funktional gleichwertig --generate-baseline, funktional gleichwertig
Community-Größe Kleiner, aber aktiv Deutlich größer, De-facto-Standard

Kein Tool gewinnt in jeder Zeile dieser Tabelle, und genau das macht Psalm vs. PHPStan zu einer projektspezifischen Entscheidung statt zu einer allgemeingültigen Empfehlung. Sicherheitskritische Anwendungen profitieren stärker von Psalms Taint Analysis, während Framework-lastige Projekte mit vielen Abhängigkeiten meist besser mit PHPStans Ökosystem fahren.

10. Zusammenfassung

Psalm vs. PHPStan ist kein Duell mit einem klaren Sieger, sondern zwei unterschiedliche Schwerpunktsetzungen innerhalb derselben Werkzeugkategorie. Psalm bringt mit eingebauter Taint Analysis, @psalm-immutable und aggressiverem Type Narrowing Stärken mit, die besonders in sicherheitskritischen und funktional orientierten Codebasen zum Tragen kommen. PHPStan punktet mit einem deutlich größeren Plugin-Ökosystem, ausgereifteren Generics in komplexen Fällen und einer in der Community stärker etablierten Level-Konvention.

Für die praktische Entscheidung zählt weniger eine abstrakte Feature-Liste als die konkrete Projektsituation: Sicherheitsanforderungen, verwendetes Framework, vorhandene Team-Erfahrung und die Bereitschaft, sich auf ein Typsystem-Dialekt einzulassen. Ein kurzer Proof of Concept mit beiden Tools auf einem begrenzten Codeausschnitt liefert oft belastbarere Argumente als jeder theoretische Vergleich, auch dieser Artikel eingeschlossen.

Psalm vs. PHPStan - Das Wichtigste auf einen Blick

Psalms Alleinstellung

Taint Analysis für SQL-Injection und XSS, @psalm-immutable und @psalm-pure für funktionale Garantien.

PHPStans Alleinstellung

Großes Framework-Ökosystem, ausgereiftere Generics in komplexen Fällen, etablierte Level-Konvention.

Konfiguration

psalm.xml mit errorLevel 1-8 gegen phpstan.neon mit level 0-9 bzw. max, funktional gleichwertig mächtig.

Entscheidungshilfe

Sicherheitskritisch und funktional: Psalm. Framework-lastig mit vielen Abhängigkeiten: PHPStan. Proof of Concept vor der Entscheidung.

11. FAQ: Psalm vs. PHPStan

1Was ist der wichtigste Unterschied bei Psalm vs. PHPStan?
Psalm bringt eingebaute Taint Analysis für Sicherheitsprüfungen mit, PHPStan punktet mit einem größeren Plugin-Ökosystem für Frameworks.
2Was ist Taint Analysis bei Psalm?
Verfolgt unsichere Nutzereingaben durch den Code bis zu einer gefährlichen Stelle wie SQL-Query oder ungeschützter Ausgabe.
3Was macht @psalm-immutable?
Markiert eine Klasse als unveränderlich, Psalm prüft statisch, dass keine Methode Property-Werte nach der Konstruktion verändert.
4Ist PHPStans Generics-Support besser als bei Psalm?
Bei komplexen Vererbungsketten und Union Types gilt PHPStan als tendenziell ausgereifter, in einfachen Fällen sind beide gleichwertig.
5Kann ich beide Tools gleichzeitig nutzen?
Technisch ja, praktisch eher für klar abgegrenzte Sicherheitsmodule sinnvoll, wegen doppelter Pflege von Ignore-Listen.
6Welches Tool passt besser zu Symfony oder Laravel?
PHPStan, wegen gepflegter Framework-Extensions wie phpstan-symfony und phpstan-doctrine.
7Wie unterscheiden sich die Konfigurationsformate?
psalm.xml mit errorLevel 1-8, phpstan.neon mit level 0-9 oder max, funktional gleichwertig mächtig.
8Wie aufwendig ist eine Migration von Psalm zu PHPStan?
Aufwendiger als ein Level-Wechsel, da Annotationen wie @psalm-immutable durch Alternativen ersetzt werden müssen.
9Für welche Projekte ist Psalm die bessere Wahl?
Für Projekte mit direktem Nutzer-Input und hohem Sicherheitsanspruch sowie funktional orientierten Code mit Fokus auf Immutability.
10Wie finde ich heraus, welches Tool zu meinem Team passt?
Ein kurzer Proof of Concept mit beiden Tools auf einem begrenzten Codeausschnitt liefert belastbarere Argumente als Theorie.

Mironsoft

Static Analysis, Code-Qualität und Sicherheitsanalyse für PHP-Projekte

Unsicher, welches Static-Analysis-Tool zu eurem Projekt passt?

Wir bewerten eure Codebasis, Sicherheitsanforderungen und euer Framework, führen einen Proof of Concept mit Psalm und PHPStan durch und richten das passende Tool sauber in eurer CI-Pipeline ein.

Tool-Auswahl

Proof of Concept mit Psalm und PHPStan auf realem Codeausschnitt eures Projekts

Sicherheitsanalyse

Taint-Analysis-Setup für sicherheitskritische Endpunkte und Formulare

CI-Integration

Baseline-Setup und Build-Regeln, egal ob mit Psalm oder PHPStan umgesetzt