PHP: Deployment Pipeline

Inhaltsverzeichnis

Einführung

Versionskontrolle und Unit-Testing

Automatisierte Unit-Tests mit GitLab-CI

Running PHPStan (Static Analysis Tool)

Messung der Testabdeckung 5

Erweitertes Reporting, Code-Coverage und Code-Qualität

Das Hinzufügen von Security Checks

Zusammenfassung

Links zu den im Text erwähnten Tools

Einführung
Während im Falle der altbekannten Hochsprachen Code schon immer mindestens die Anforderung erfüllen musste, kompilierbar zu sein, konnte im Bereich der Script-Sprachen ein regelrechter Wildfuchs an Lösungen entstehen, da hier, zugespitzt formuliert, die einzige Anforderung war: „muss irgendwie funktionieren“. Hiervon waren vor allem typische Web Script-Sprachen wie PHP oder JavaScript betroffen. Mit den fortschreitenden Anforderungen an Webanwendungen entstanden ebenfalls höhere Anforderungen an Script-Sprachen. Insbesondere die Anforderung, ein automatisiertes Deployment auf Live-Systemen durchzuführen, hat dazu geführt, dass Paradigmen aus den Hochsprachen, wie etwa objektorientierte Programmierung oder Test Driven Development, auch in den Script-Sprachen Anwendung fanden. Die Entwicklung der sogenannten Continuous Integration Pipelines, also dem automatisierten Deployment auf Live-Systemen, hat es erforderlich gemacht, umfangreiche Tests schon vor dem eigentlichen Deployment durchzuführen. Rund um PHP etwa ist ein ganzes Biotop aus sogenannten Build- und Deployment-Tools entstanden.
In diesem Artikel stellen wir eine Auswahl von Open Source basierten Tools vor, die vor allem folgende Anforderungen an eine CI-Pipeline erfüllen sollen:
1. Modularer Aufbau der Pipeline unter zur Hilfenahme von kleinen effektiven Tools
2. Leichte Erweiterbarkeit der CI-Pipeline durch weitere Tools, beispielsweise um Integrationstests mit Datenbanken durchzuführen
3. Verwendung des grundlegenden Build-Services GitLab, um eine leichte Anbindung an die Versionskontrolle zu gewährleisten
4. Verwendung von Docker, um Entwicklungsumgebung und zugehörige Pipeline automatisiert und dezentral an die beteiligten Entwickler ausliefern zu können
5. Verwendung von Composer, um Abhängigkeiten auflösen sowie Spezifikationen und Reihenfolge der Script-Ausführung festlegen zu können

Versionskontrolle und Unit-Testing
Zur Versionskontrolle gibt es kaum ein besseres Tool als GitLab. Durch die Konfiguration von sogenannten Workern, am besten für die Verwendung mit Docker, kann eigentlich schon von Haus aus kontinuierlich integriert werden. Hierzu starten die konfigurierten Worker bei jedem Commit einen eignen Docker-Container, der aus der Versionskontrolle die jeweils aktuellen Quellen klont. In diesem geklonten Source-Container können dann erste Tests und Analysen gefahren werden.
Das erste Beispiel ist sehr einfach: Bei jedem Commit werden automatisierte Unit-Tests gefahren. Hierzu müssen zuerst die Komponententests konfiguriert werden. Zunächst muss, via Composer, „phpunit“ zu den Projektabhängigkeiten hinzugefügt werden.

Wurde das von Sebastian Bergman entwickelte PHPUnit-Tool hinzugefügt, muss der eigentliche Unit-Test konfiguriert werden. PHPUnit erwartet XML-Dateien als Konfigurationsdateien. Folgend nun ein einfaches generisches Beispiel für die Konfiguration der PHPUnit-Testsuite:

Anschließend kann der Test abgefeuert werden. Wie in der Konfigurationsdatei angegeben, befindet sich der eigentliche Testcode in diesem Beispiel im Verzeichnis „/tests/“. Zunächst werden die Tests lokal abgefeuert, um zu sehen, ob sie funktionieren.

Composer hat die ausführbaren PHPUnit-Befehle im Verzeichnis „vendor“ untergebracht. Dies ist bei praktisch allen PHP-Entwicklungen das Standardverzeichnis für zusätzliche ausführbare Abhängigkeiten, denn schließlich bedeutet „vendor“ Zulieferer, bzw. Lieferant.

So weit so gut, nun zur Automatisierung der Uni-Tests.

Automatisierte Unit-Tests mit GitLab-CI
Zunächst wird eine YAML-Datei benötigt, denn GitLab-CI wird im YAML-Format konfiguriert. Die Konfigurationsdatei beschreibt wo GitLab-CI den Docker-Container findet und wie genau die Tests im gebooteten Docker-Container ablaufen sollen. In der Regel wird die Konfigurationsdatei als .gitlab-ci.yml bezeichnet.

Der erste Bezeichner ist „image“. Dieser bezeichnet das eigentliche Docker-Image. Immer wenn es um kontinuierliche Integration geht, sollte ein möglichst vollständiges Image verwendet werden. Ein hervorragendes Beispiel aus der PHP-CI-Welt sind speziell für die Verwendung mit Docker zugeschnittene PHP-Builds. Insbesondere TetraWeb-Builds zeichnen sich durch sehr gute Integrationen in Docker aus. Die TetraWeb-Builds bringen bereits Composer und viele weitere CI-Tools mit. Ganz unabhängig vom eigenen Projekt kann so sichergestellt werden, dass alles Nötige für die kontinuierliche Integration bereits vorhanden ist. Zu beziehen sind die PHP-Builds selbstverständlich über GitHub.
Abschließend wird nur noch das Bash-Script benötigt, welches die Docker-Instanz mit allen Abhängigkeiten hochfährt. Ein derartiges Shell-Script wird bei jedem Docker-basierten Projekt benötigt. Es sollte im Projekt-Verzeichnis unter /ci/docker untergebracht sein. Ein guter Name dafür wäre „docker_install“.

Nicht vergessen das Shell-Script ausführbar zu machen:

Nun können die ersten Commits durchgeführt werden. Dazu muss nun der Quell-Code verändert, committet und gepuscht werden. Dies erzeugt in Git-Lab einen Merge Request. Danach sollte folgende Ausgabe sichtbar sein:

Soweit so gut, es existiert nun eine erste vollautomatisierte CI-Pipeline für Komponententests. Nur wenn PHPUnit-Tests fehlerfrei durchlaufen, wird aus dem Merge Request ein tatsächlicher Merge und damit ein Pull-Request.

Doch damit nicht genug. Nun werden weitere Schritte in die CI-Pipeline eingebaut.

Running PHPStan (Static Analysis Tool)
PHPStan ist ein hervorragender Code-Analyser, der weit mehr zu bieten hat als einfache Syntax-Checks. Beispielsweise ist PHPStan in der Lage, Brüche im PHP-Flaw zu erkennen, etwa wenn eine Variable benutzt wird, bevor sie deklariert wurde. Sicher ist PHPStan nicht das ultimative Tool, um sämtliche Bugs zu erkennen. Es bringt aber den großen Vorteil mit, dass es relativ einfach eingerichtet werden kann. So ist es nicht erforderlich, weitere Unit-Tests zu schreiben. Indem Stan einfach den Code analysiert, finden sich schon reichlich potenzielle Flaw-Brüche.

Zunächst wird PHPStan nach bereits beschriebenen Composer-Befehl integriert:

Danach muss eine Neon-Datei erzeugt werden, um PHPStan zu konfigurieren. Beispielsweise nach diesem einfachen Schema:

PHPStan neigt leider zu sogenannten „False Positivs“. In der Ignore-Errors-Section der Konfigurationsdatei können, wie in dem obigen Beispiel, reguläre Ausdrücke angegeben werden, die False Positivs unterdrücken.

Zum Schluss wird noch ein kleines Composer-Script benötigt, um PHPStan zu triggern:

Ausgeführt wird PHPStan dann folgender Maßen:

Es werden in diesem Beispiel also alle Dateien unterhalb der src-Hierarchie getestet. Weiterhin wird der sehr restriktive Level 4 gewählt, dies ist schon der restriktivste Level bei PHPStan. Damit die PHPStan-Tests in die Pipeline integriert werden, muss die .gitlab-ci.yml Konfigurationsdatei weiter angepasst werden:

Messung der Testabdeckung
Eine der wichtigsten Informationen im Zusammenhang mit CI-Prozessen ist sicherlich die Messung der Testabdeckung, denn schließlich sollte eine hohe Testabdeckung zur möglichst weitgehenden Bug-Freiheit auf dem Live-System führen. Zur Messung der Testabdeckung eignet sich besonders gut der in PHP integrierte Debugger PHPdbg. Für die Integration von PHPdbg wird die Datei .gitlab-ci.yml um folgende Konfigurationseinstellung erweitert:

Hier ist wichtig, dass PHPdbg vor PHPUnit läuft!

Erweitertes Reporting, Code-Coverage und Code-Qualität
Der vorhergehende Schritt erlaubte es, die Test-Code-Coverage zu tracken. Wenn aber nun ein Check durchgeführt werden soll, müsste der generierte HTML-Report manuell begutachtet werden. Das ist sicher etwas, was nicht jeden Tag gemacht werden kann und außerdem kann ein solcher Schritt nicht in den Merge Request integriert werden. Daher wäre es sehr interessant, eine Zusammenfassung des Reports in den Merge Request zu integrieren. Dann könnten alle Entwickler einfach feststellen, dass die eigene Code-Coverage ausreichend ist, um keine Störungen im Live-System zu verursachen.

Dies ist etwas was GitLab nicht nativ kann. Doch es gibt die sogenannte „PHP-Washing Machine“. Die Washing Machine ist ein einfaches Tool, welches die Informationen aus der clover.xml sammelt und damit einen Prozentwert der Code-Coverage errechnet. Zusätzlich kann Washing Machine einen Branch-Vergleich bezüglich der Code-Coverage erstellen. So kann sichergestellt werden, dass die Variation der Code-Coverage zwischen zwei Branches nicht zu groß ist. Das bedeutet, es kann eine Aussage getroffen werden, ob Veränderungen am Code zu einer Steigerung oder Abnahme der Code-Coverage führen. Hier wird auch vom sogenannten C.R.A.P.-Antipattern oder C.R.A.P-Score gesprochen. C.R.A.P. steht für „Change Risk Antipatterns“. Der C.R.A.P. errechnet sich unter anderem aus zyklometrischer Komplexität („Spaghetticode“) und aus der Testabdeckung der einzelnen Code-Units. Je höher die wiederkehrende Komplexität, desto höher ist der Score. Je besser die Testabdeckung ist, umso geringer ist der Score. Das heißt, sehr komplexer Code, der gut getestet ist, kann durchaus einen geringen C.R.A.P.-Score ergeben. In der Regel lässt sich allzu komplexer Code jedoch nur schwer sinnvoll testen. Daher findet sich oft die unglückliche Kombination aus komplexem Code mit geringer Testabdeckung, was einen sehr schlechten C.R.A.P. Score ergibt.

Das Hinzufügen von Security Checks
Um Security Checks durchzuführen, empfiehlt sich eine Integration von Sensiolab’s Security-Checker Tool. Dieser Security Checker analysiert die composer.lock und schlägt an, wenn Bibliotheken benutzt werden, die bekannter Maßen verletzbar sind. Wichtig ist hier zusehen, dass Security Checker nicht auf Code-Ebene nach Verletzbarkeiten sucht. Dafür müssten beispielsweise Tools wie Scrutinizer oder RATS zum Einsatz gebracht werden.

Zur Installation von Security Checker muss die docker_install.sh erweitert werden, um den Security Checker Befehl nutzen zu können:

Danach muss nur noch der Aufruf vom Security Checker in die Konfigurationsdatei .gitlab-ci.yml aufgenommen werden:

Zusammenfassung
Nun haben wir uns mit GitLab ein Continuos Integration Setup gebaut, welches bei jedem Merge Request durchläuft und den Merge nur dann durchführt, wenn die Tests erfolgreich sind. Die CI-Pipeline führt automatisierte Uni-Tests durch, checkt Abhängigkeiten und Bibliotheken auf Security Aspekte und schließlich erhalten wir automatisiertes Feedback zu unserer Code-Qualität direkt im Merge Request.

Links zu den im Text erwähnten Tools

https://github.com/sebastianbergmann/phpunit
https://github.com/thecodingmachine/washingmachine
https://github.com/TetraWeb/docker
https://github.com/phpstan/phpstan
https://github.com/sensiolabs/security-checker
https://www.php.net/manual/de/book.phpdbg.php
https://www.docker.com/
https://sebastian-bergmann.de/
https://de.wikipedia.org/wiki/Crapware