Exakt ein Jahr ist vergangen – Zeit für einen neuen Eintrag hier im Web-Developer-Blog 🙂 Heute gebe ich einen kleinen Einblick in die wunderbare Welt der „Statische Code Analyse“. Und vor allem wie uns diese bei der Pflege unserer Projekte unterstützen kann! Vielleicht wird aus diesem Posting ja eine kleine Serie zum Thema?

Ich möchte zunächst grob über den Sinn und Zweck von solchen Statischen Code Analysen sprechen und euch auch auch direkt ein Tool vorstellen! Bühne frei für Psalm!

Unsere Situation

Es ist nun knapp 1 ½ Jahre her das PHP 8 veröffentlicht wurde und ein halbes Jahr seit der Veröffentlichung von PHP 8.1. Höchste Zeit das wir unsere Projekte kompatibel machen und auf die neuen PHP Versionen ziehen! Das kommt einigen von euch bestimmt bekannt vor, oder? Vielleicht erinnert ihr euch ja an den Beitrag: „Welche PHP Version für neue Projekte?“ – Heute geht es um die andere Seite: Bestehende Projekte für neue PHP Versionen fit machen!

Früher oder später kommt nämlich der Zeitpunkt an dem wir sicherstellen müssen das unsere alte Applikation überhaupt auf einer PHP 8 oder sogar PHP 8.1 Umgebung läuft. Das ist häufig nicht selbstverständlich! Und genau zu diesem Zweck (und natürlich noch mehr) gibt es Tools die uns dabei helfen können „inkompatiblen“ Code ausfindig zu machen. Das machen wir mit Hilfe einer „Statischen Code Analyse“. Die Tools dahinter können natürlich noch viel viel mehr als nur das – aber darüber berichte ich vielleicht ein anderes mal 🙂

Psalm hilft uns weiter!

Vor einiger Zeit habe ich mir das Tool Psalm angeschaut um damit eine Statische Code Analyse meines Codes zu machen. Ich hatte vorher schon Erfahrung mit anderen Tools wie zum Beispiel dem CodeSniffer oder dem PHP-CS-Fixer. Diese beziehen sich allerdings eher auf den Codestyle oder „coding standards“ als auf die technischen Details.

Nicht falsch verstehen: die anderen Tools sind ebenso nützlich und sorgen für „sauberen“ Code, doch das hilft uns bei der Migration von alten Projekten nicht unbedingt weiter.

Also – was tut Psalm? Diese Applikation aus dem Hause vimeo prüft die „Typensicherheit“ unseres Codes. Dieses Thema ist seit PH 7 stark in den Vordergrund gerückt – mit PHP 8 umso mehr. Unter anderem heißt es auf der Homepage „On its strictest setting it can help you prevent almost all type-related runtime errors […]“. Perfekt für unseren Anwendungsfall!

Installation und Konfiguration

In erster Linie müssen wir natürlich Psalm installieren – mein Tool der Wahl: Composer. Hier gibt es zwei Möglichkeiten Psalm zu installieren:

$ composer req vimeo/psalm --dev

Oder wir können das „phar“ Paket nutzen um das Tool ohne weitere Abhängigkeiten zu nutzen:

$ composer req psalm/phar --dev

Nach diesem Schritt kann Psalm ausgeführt werden! Zunächst wird aber darauf hingewiesen das eine Konfigurationsdatei erstellt werden muss. Das erledigt Psalm dank folgendem Befehl von selbst:

$ ./vendor/bin/psalm --init

In diesem Schritt wird der Code auf Basis der eingestellten (oder verfügbaren) PHP Version geprüft. Anschließend wird die psalm.xml Datei geschrieben, mit vorgeschlagenem „Error Level“. Diese Datei wird unsere Konfiguration beinhalten und kann etwa so aussehen:

<?xml version="1.0"?>
<psalm
    errorLevel="2"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src" />
        <ignoreFiles>
            <directory name="vendor" />
        </ignoreFiles>
    </projectFiles>
</psalm>

Je nach IDE wird auf Basis des Schemas übrigens die Autovervollständigung aktiv 🙂

Führen wir Psalm erneut aus geht es tatsächlich los und durchsucht unseren Code nach Problemen – je nach Größe und Status des Codes kann es zu ziemlich überwältigenden Ergebnissen kommen – ich selbst hatte hier schon stellenweise Ergebnisse mit mehreren zehntausend (!!) Fehler-Meldungen.

Psalm zähmen

Da Psalm in der Standard-Konfiguration ziemlich „aggressiv“ ist macht es durchaus Sinn die Konfiguration ein wenig zu entschärfen. Genau zu diesem Zweck ist es möglich die „issueHandlers“ zu definieren – zum Beispiel ob diese einen „Fehler“, eine „Info“ oder gar nichts melden sollen.

Das könnte so aussehen:

<?xml version="1.0"?>
<psalm
    errorLevel="2"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src" />
        <ignoreFiles>
            <directory name="vendor" />
        </ignoreFiles>
    </projectFiles>

    <!-- Configure some issue type behaviour -->
    <issueHandlers>
        <!-- Suppress some types, because they can not be checked reliably -->
        <UndefinedConstant errorLevel="suppress" />
        <UndefinedFunction errorLevel="suppress" />
        <UndefinedDocblockClass errorLevel="suppress" />

        <!-- Suppress some types because of old PHPDocs -->
        <InvalidReturnType errorLevel="suppress" />
    </issueHandlers>
</psalm>

Nicht vergessen – es ist auch in XML möglich Kommentare zu schreiben 😉 Das sollte man definitiv tun um anderen Entwicklern das Leben leichter zu machen. Wir erklären hier zum Beispiel wieso bestimmte issueHandlers ignoriert werden.

In meinem Beispiel ignoriere ich „undefinierte Konstanten“, „undefinierte Funktionen“ und „undefinierte DocBlock Klassen“. Das hat damit zu tun das diese Prüfung nicht immer 100% zuverlässig funktionieren – das hat aber weniger mit Psalm als mit dem Code selbst zu tun.

Diese Probleme treten bei mir auf da eine Reihe von Konstanten dynamisch erstellt werden. Zum Beispiel wenn Werte zur Laufzeit aus einer Datenbank kommen. So etwas kann Psalm nicht erkennen und es regnet Fehler. Gleiches gilt für die Funktionen, wenn deren Dateien dynamisch eingebunden werden.

Als nächstes ignorieren wir „inkorrekte Rückgabewerte“. Das kann aufgrund veralteter PHPDoc Kommentare passieren oder wenn man den Typen nicht genau genug definiert:

<?php

/**
 * @return int
 */
function getNumericValue($number) {
    return (float)$number;
}

Oder alternativ:

<?php

function getNumericValue($number): int {
    return (float)$number;
}

Das lösen dieser Fehler hilft dabei den Code robuster zu machen, da wir uns ab jetzt auf die Rückgabewerte verlassen können. Und im Fall von PHP 8 gibt es keine Fehlermeldungen, wenn mal ein Rückgabewert nicht stimmt 😉

PHP Version anpassen

Psalm wird den Prozess auf Basis der verfügbaren PHP Version ausführen. Die „verfügbare“ PHP Version wird folgendermaßen ermittelt:

  • Mittels Parameter --php-version=8.0
  • Als Konfiguration in der XML
  • Aus der PHP Bedingung package.json 
  • Aus PHP selbst (siehe auch php -v)

Es kann durchaus Sinn machen den Code für verschiedene PHP Versionen zu prüfen – dazu kommt später noch ein kleiner Tipp von mir.

Die Fehlerflut reduzieren

Sobald wir die Konfiguration abgeschlossen haben geht es an die Fehler. Und hier kann es eine regelrechte „Fehlerflut“ geben mit mehreren tausend Fehlern! Damit uns diese Ergebnisse nicht jedes mal um Ohren fliegen hat Psalm ein Ass im Ärmel: die „Baseline“.

Es handelt sich dabei um eine Art „Bestandsaufnahme“ aller Fehler. Der Sinn dahinter ist einfach: Diese bekannten Fehler sollen uns nicht jedes mal aufgelistet werden, wenn wir eine Komponente refakturieren. Viel mehr wollen wir den Fehler-Berg Zeile für Zeile abarbeiten.

Die Baseline lässt sich sehr einfach erzeugen:

$ ./vendor/bin/psalm --set-baseline=psalm-baseline.xml

Dieser Befehl wird die Baseline auf Basis des aktuellen Codes erzeugen und auch direkt innerhalb unserer psalm.xml referenzieren. Zukünftige Scans werden die bekannten Fehler dann nicht erneut auflisten.

Sollten wir in der Zwischenzeit Fehler repariert haben kann die Baseline mit Hilfe des Parameters --update-baseline aktualisiert werden. Dies wird gelöste Probleme aus der Baseline entfernen – nicht neue Fehler aufnehmen. Dafür müsste erneut --set-baseline genutzt werden.

Natürlich lässt sich der Scan auch ohne Berücksichtigung der Baseline aufrufen. Dafür benutzen wir den Parameter --ignore-baseline.

Mein Tipp an euch

Stellen wir uns ein Projekt vor das mit PHP 8 kompatibel gemacht werden soll. Für einen solchen Fall schlage ich den folgenden Workflow vor:

  1. Auf Basis der aktuellen PHP (z.B. 7.4) Version eine Baseline erzeugen
  2. Einen GIT Branch für die PHP 8 Kompatibilität vorbereiten und diesen auschecken
  3. Psalm mit --php-version=8.0 ausführen unter Berücksichtigung der Baseline
  4. Gefundene Fehler reparieren
  5. (Optional) Baseline aktualisieren

Mit Hilfe dieses kleinen Workflows sollte es möglich sein spezifische Fehler aufgrund der PHP Version ausfindig zu machen.

Plugins, Plugins, Plugins

Um Psalm mit bestimmten Funktionalitäten zu erweitern ist es möglich mit Plugins zu arbeiten. Das ermöglicht uns die Statische Code Analyse für Bibliotheken und Frameworks zu optimieren – die Liste ist lang und beinhaltet viele bekannte Namen wie zum Beispiel PHPUnit, Symfony, Doctrine, Laravel, CakePHP und viele weitere…

Für uns dürfte das Plugin insane-comparison interessant sein, da es sich konkret um das geänderte Verhalten zwischen PHP 7 und PHP 8 Vergleichen dreht. In der README findet man unter anderem solche Beispiele wie dieses hier:

<?php

$a = 'banana';
$b = 0;

if ($a == $b) {
    echo 'PHP 7 will display this';
} else {
    echo 'PHP 8 will display this instead';
}

Zu Guter letzt…

Psalm hat noch eine weitere sehr coole Funktion: gefundene Fehler automatisch lösen. Das funktioniert mit psalter oder psalm --alter. Bevor man dies aber auf den eigenen Code loslässt sollte dieser Prozess mit --dry-run simuliert und geprüft werden. Der komplette Command schaut so aus:

$ ./vendor/bin/psalm --alter --issues=all --dry-run

$ ./vendor/bin/psalter --issues=all --dry-run

Es gibt auf der Homepage noch verschiedenen Optionen zum steuern der Anpassungen so wie viele Beispiele.

Ich möchte aber vorerst nicht viel weiter auf diesen Punkt eingehen, weil ich es selbst noch nicht so ausgiebig getestet habe.

Andere Statische Code Analysen?

Wie eingangs erwähnt gibt es natürlich noch andere Tools die uns Sinnvoll unterstützen können und ich könnte mir vorstellen das ich diese zukünftig ebenfalls hier vorstellen werde. Vor allem dürfte es interessant sein die verschiedenen Schwerpunkte zu beleuchten, da jedes Tool seine eigenen Stärken hat.

Für Heute soll dieser Eintrag aber ausreichen. Ich wünsche euch einen angenehmen Freitag und ein schönes Wochenende. Und bis zum nächsten mal natürlich Happy Coding!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.