Vor Jahren habe ich eine Entscheidung getroffen, die mir insgeheim peinlich war: Ich habe unseren Psalm-Job aus dem Pre-Commit-Hook genommen und nur noch in der CI laufen lassen, weil niemand im Team jedes Mal 50 Sekunden warten wollte, nur weil er einen Tippfehler korrigiert hatte. Es fühlte sich an, als würde ich die Latte tiefer legen. Dann habe ich gesehen, wie genau dasselbe in drei anderen Teams passiert ist, und aus dem schlechten Gewissen wurde die Erkenntnis, dass Tool und Workflow einfach nicht zusammenpassten. Genau dieses Jucken wollte Jorg Sowa nach eigener Aussage loswerden, als er im April 2026 mit mir anfing, und die Zahlen, die er veröffentlicht hat, kann man kaum ignorieren.
Kurz die Fakten, denn sie sind wichtig. mir ist in Rust geschrieben, steht unter MIT-Lizenz und ist aktuell bei Version 0.88. Auf einem einzigen Laptop analysiert es Symfony und Laravel in 0,7 bis 2 Sekunden; Psalm brauchte 37 bis 54 Sekunden und kam über Hauptprozess und neun Worker auf Spitzenwerte von 3 bis 4 GB Speicher. Sowa nennt eine Spanne von 19- bis 82-mal schneller bei höchstens einem Fünftel des Speichers. Der Symfony-Lauf nutzte Commit b47221ac mit Symfonys eigener, unveränderter psalm.xml auf Error-Level 5 samt Baseline; der Laravel-Lauf umfasste 1.742 Dateien von v13.36.0. Er sagt offen, dass Psalms --diff-Modus, der nur geänderte Dateien neu prüft, nicht Teil des Vergleichs war, und ich würde diese Spalte gern ausgefüllt sehen, bevor irgendwer die 82x für bare Münze nimmt.
Aber worauf ich immer wieder zurückkomme, ist nicht die Stoppuhr. mir bringt keine eigene Typsprache mit. Es liest psalm.xml, wenn keine mir.xml existiert, findet psalm-baseline.xml von selbst, respektiert errorLevel 1 bis 8 mit Psalms Zuordnung pro Issue und versteht @psalm-pure, @psalm-immutable, @psalm-suppress und den Rest, dazu PHPStan-Syntax und @phpstan-ignore. Issue-Namen wie InvalidArgument und TaintedSql bleiben gleich, wo beide Tools den Check haben. Anders gesagt: Was bei Vimeo als Docblock-Dialekt eines einzelnen Projekts begann, ist zu einer Schnittstelle geworden, die eine komplett unabhängige Engine implementieren kann. Das ist ein riesiges Kompliment an die PHP-Community. Wir haben in Kommentaren ein reicheres Typsystem gebaut, als die Sprache selbst hergab, und es hat sich als robust genug erwiesen, jede einzelne Implementierung zu überdauern.
Und genau deshalb finde ich, dass der nächste Schritt langweilig und überfällig ist: Wir brauchen eine schriftliche Spezifikation für die Docblock-Typsprache. Was list<T>, non-empty-string oder ein Array-Shape mit Literal-Typen im Grenzfall bedeutet, ist derzeit dadurch definiert, was Psalm macht, weitgehend bestätigt von PHPStan und seit PhpStorm 2020.3 ungefähr auch von deiner IDE verstanden. Mit einem dritten ernstzunehmenden Interpreter im Spiel reicht „was Psalm halt macht“ nicht mehr. Sowa warnt selbst, dass dein erster mir-Lauf eine andere Issue-Liste ausspucken wird, dass noch nicht jede Regel existiert und manche Namen abweichen. Ein Teil dieser Lücken ist schlicht unfertige Arbeit. Ein anderer Teil werden echte Meinungsverschiedenheiten darüber sein, was ein Typ bedeutet, und ohne Spezifikation kann niemand sagen, wer recht hat.
Den stärksten Einwand gegen all die Begeisterung nehme ich ernst. Psalm ist in PHP geschrieben, die Leute, die es benutzen, können es also lesen, debuggen und an einem Sonntagnachmittag einen Patch schicken. Ein Analyzer in Rust rückt die Engine für die meisten Nutzer außer Reichweite. Bei Plugins sieht man den Preis deutlich: mir hat eine native Plugin-API in Rust nach dem Vorbild von Psalms Event-Handlern, und bestehende PHP-Plugins wie das für PHPUnit laufen nur über eine Brücke, die einen PHP-Subprozess startet, wofür weiterhin PHP und Psalm installiert sein müssen. Wenn das Analyse-Tooling der Community zu einem kleineren Kreis von Maintainern in einer anderen Sprache abwandert, ist das ein echter Verlust an Widerstandsfähigkeit, und das will ich nicht schönreden.
Trotzdem lande ich auf der Seite, die es willkommen heißt, und zwar aus einem praktischen Grund. Deine Investition lag nie in Psalms Quellcode. Sie steckt in den Annotationen, die dein Team über Jahre geschrieben hat, in der Baseline, die ihr Stück für Stück abgetragen habt, in den Suppressions mit Kommentaren, die erklären, warum. Weil mir all das als Vertrag behandelt, kostet dich ein Versuch ein composer require und einen Befehl, und du kannst beide Analyzer in der CI parallel laufen lassen, bis du der Ausgabe traust. Nichts wird umgeschrieben. Wenn mir für immer bei 0.9 hängen bleibt, deinstallierst du es und hast einen Nachmittag verloren. Go-Entwickler haben ihre schnellen Linter bekommen, indem Tools neu geschrieben wurden und alle neue Konventionen übernehmen sollten; wir dürfen unsere behalten und nur die Engine darunter austauschen, und das ist ehrlich gesagt der bessere Deal.
Was sich ändert, wenn eine Analyse zwei Sekunden dauert, ist der Moment, in dem du sie laufen lässt. Bei 50 Sekunden ist sie eine Schranke, an die du einmal vor dem Push stößt. Bei unter zwei kann sie bei jedem Speichern laufen, und die Typfehler tauchen auf, solange du noch weißt, warum du die Zeile geschrieben hast. Diese veränderte Gewohnheit ist mehr wert als jedes Benchmark-Diagramm, und genau das konnte Psalms Design uns nie ganz geben, egal wie gut seine Analyse ist. Dafür nehme ich ein paar fehlende Regeln gern in Kauf, zumindest auf einem Feature-Branch.
Deshalb würde mich interessieren, wo du stehst, vor allem wenn du Libraries pflegst, die präzise Docblock-Typen mitliefern. Wenn zwei Engines sich bei einer deiner @template-Signaturen widersprechen, welche hältst du dann für falsch, und würdest du deinen Namen unter eine von der Community gepflegte Spezifikation für diese Typsprache setzen, oder hältst du es für den gesünderen Weg, die Tools sich von selbst annähern zu lassen?




Kommentare
Noch keine Kommentare — schreib den ersten.
Starte die Diskussion
Kein Konto, kein Passwort nötig — gib einfach deine E-Mail-Adresse ein, wir senden dir einen einmaligen Anmelde-Link. Beim ersten Mal bist du damit automatisch angemeldet.
Deine Bewertung wird nach der Anmeldung automatisch übernommen.
Schau in dein Postfach
Wir haben einen Anmelde-Link an … gesendet. Öffne ihn auf diesem Gerät — dieser Tab meldet dich automatisch an.
Nichts angekommen? Wirf einen Blick in den Spam-Ordner — und markiere die Mail dort als „Kein Spam“, dann landet sie künftig direkt im Postfach.