Kryptografie – mehr als nur Zufall

Im täglichen Sprachgebrauch benutzen wir das Wort Zufall recht unreflektiert. Sätze wie, „Ich bin zufällig hier vorbeigekommen.“ oder „Was für ein Zufall, dich hier zu treffen.“ kennt jeder von uns. Aber was möchten wir damit zum Ausdruck bringen? Eigentlich möchten wir damit sagen, dass wir die aktuelle Situation nicht erwartet haben.

Zufall ist eigentlich ein mathematischer Ausdruck, den wir in den täglichen Sprachgebrauch übernommen haben. Zufall meint etwas nicht Vorhersagbares. Also Dinge wie, an welcher Stelle sich zu einem exakten Moment ein beliebiges Elektron eines Atoms befindet. Welchen Weg ich zu einem bestimmten Ziel nehme, kann zwar beliebig sein, dennoch lassen sich über Wahrscheinlichkeiten Präferenzen ableiten, welche die Wahl dann durchaus vorhersagbar machen.

Umstände für ein solches Szenario können Entfernung, persönliche Befinden (Zeitdruck, Unwohlsein oder Langeweile) oder äußere Umstände (Wetter: Sonnenschein, Regen) sein. Habe ich Langeweile UND es scheint die Sonne, wähle ich für etwas Zerstreuung und Neugier eine unbekannte Strecke. Habe ich wenig Zeit UND es regnet, entscheide ich mich für den mir kürzesten bekannten Weg, oder eine Strecke, die möglichst überdacht ist. Daraus folgt: Je besser man die Gewohnheiten einer Person kennt, umso vorhersagbarer sind ihre Entscheidungen. Vorhersagbarkeit aber widerspricht dem Konzept Zufall.

Dass mathematische Begriffe, die sehr streng definiert sind, zeitweilig als Modeerscheinung in unseren täglichen Sprachgebrauch übernommen werden, ist keine neue Sache. Ein sehr populäres Beispiel, das bereits Joseph Weizenbaum angeführt hat, möchte ich hier kurz aufgreifen. Der Begriff Chaos. Eigentlich beschreibt Chaos im mathematischen, den Umstand, dass eine sehr kleine Änderung bei sehr langen Strecken das Ergebnis erheblich verfälscht, sodass es nicht einmal als Schätzung oder Näherung verwendet werden kann. Eine typische Anwendung ist die Astronomie. Richte ich einen Laserstrahl von der Erde auf den Mond, so verursacht bereits eine Abweichung im Winkel von wenigen Millimetern, dass der Laserstrahl kilometerweit am Mond vorbeigeht. Um solche Gegebenheiten populärwissenschaftlich einer breiten Masse zu erklären, verwendete man eine Assoziation, dass, wenn ein Schmetterling in Tokio mit den Flügeln schlägt, dies zu einem Sturm in Berlin führen kann. Leider gibt es nicht wenige Pseudowissenschaftler, die dieses Bild aufgreifen und ihrer Umwelt als Tatsache verkaufen. Das ist natürlich Unfug. Das Flügelschlagen eines Schmetterlings kann auf der anderen Seite des Globus keinen Sturm erzeugen. Denken wir nur daran, welche Auswirkungen das auf unsere Welt hätte, alleine die ganzen Vögel, die sich jeden Tag in die Luft schwingen.

„Warum ist die Ehe des Mathematikers gescheitert? Seine Frau war nicht berechenbar.“

Warum ist Zufall in der Mathematik aber eine so wichtige Sache? Im Konkreten geht es um das breite Thema Kryptografie. Wenn wir für die Verschlüsselung Kombinationen wählen, die man leicht erraten kann, ist der Schutz schnell dahin. Dazu ein kleines Beispiel.

Die Seiten des Internets sind statuslos. Das bedeutet, dass, nachdem eine Webseite aufgerufen wird und man auf einen Link klickt, um zur nächsten Seite zu gelangen, alle Informationen aus der vorangegangenen Seite verloren gegangen sind. Um dennoch Dinge wie in einem Onlineshop, einen Warenkorb und all die sonst noch notwendigen Funktionen zum Einkaufen bereitstellen zu können, gibt es die Möglichkeit, Daten auf dem Server in sogenannten Sessions zu speichern. Zu diesen Daten gehört oft auch das Login des Nutzers. Um die Sessions zu unterscheiden, haben diese eine Identifikation (ID). Der Programmierer legt nun fest, wie diese ID generiert wird. Eine Eigenschaft dieser IDs ist, dass sie eindeutig sein müssen, es darf also keine ID zweimal vorkommen.

Nun könnte man auf die Idee kommen, den Zeitstempel inklusive der Millisekunden zu nutzen, um daraus einen Hash zu generieren. Der Hash verhindert, dass man auf den ersten Blick erkennt, dass die ID aus einem Zeitstempel erstellt wird. Ein geduldiger Hacker hat dieses Geheimnis mit ein wenig Fleiß vergleichsweise schnell gelüftet. Hinzukommt noch die Wahrscheinlichkeit, dass zwei Nutzer zur gleichen Zeit eine Session erzeugen können, was zu einem Fehler führen würde.

Nun könnte man auf die Idee kommen, die SessionID aus verschiedenen Segmenten wie Zeitstempel + Benutzernamen und anderen Details zusammenzubauen. Obwohl steigende Komplexität einen gewissen Schutz bietet, ist dies keine wirkliche Sicherheit. Denn Profis haben Methoden mit überschaubarem Aufwand, diese ‚vermeidlichen‘ Geheimnisse zu erraten. Der einzig wirkliche Schutz ist die Verwendung von kryptografisch sicherem Zufall. Als ein Segment, das sich mit noch so viel Aufwand nicht erraten lässt.

Bevor ich aber verrate, wie wir dem Problem begegnen können, möchte ich den typischen Angriffsvektor und den damit erzeugten Schaden auf SessionIDs kurz besprechen. Wenn die SessionID durch einen Angreifer erraten wurde und diese Session noch aktiv ist, dann kann der Hacker diese Session in seinem Browser übernehmen. Das Ganze nennt sich Session Hijacking oder auch Session Riding. Der Angreifer, der eine aktive Session übernehmen konnte, ist als fremder Nutzer mit einem Profil, das ihm nicht gehört, bei einem Onlinedienst angemeldet. Damit kann er alle Aktionen durchführen, die ein legitimer Nutzer auch tun kann. Es wäre also möglich, in einem Onlineshop eine Bestellung auszulösen und die Ware an eine andere Adresse zu schicken. Ein Umstand, den es mit allen Mitteln zu verhindern gilt.

Nun gibt es verschiedene Strategien, die eingesetzt werden, um das Stehlen einer aktiven Session zu unterbinden. Jede einzelne dieser Strategien bietet schon einen ‚gewissen‘ Schutz, aber die volle Stärke wird erst durch die Kombination der verschiedenen Optionen erreicht, denn die Hacker rüsten ja auch stetig nach und suchen nach Möglichkeiten. Im Rahmen dieses kleinen Artikels betrachten wir ausschließlich den Aspekt, wie man eine kryptografisch sichere Session ID erzeugen kann.

So ziemlich alle gängigen Programmiersprachen haben eine Funktion random(), die eine zufällige Zahl erzeugt. Die Implementierung dieser Zufallszahl variiert. Leider sind diese generierten Zahlen für Angreifer gar nicht so zufällig, wie sie sein sollten. Deswegen gilt für Entwickler immer der Grundsatz, diese einfache Zufallsfunktion zu meiden. Stattdessen gibt es für Backendsprachen wie PHP und JAVA kryptografisch sichere Implementierungen für Zufallszahlen.

Für Java Programme kann man auf die Klasse java.security.SecureRandom zurückgreifen. Eine wichtige Funktion dieser Klasse ist die Möglichkeit, aus verschiedenen Kryptografie-Algorithmen [1] zu wählen. Zusätzlich lässt sich der Startwert über den sogenannten Seed. Um die Verwendung ein wenig zu demonstrieren, hier ein kleiner Codeausschnitt:

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.

Wir sehen, die Verwendung ist recht einfach und kann leicht angepasst werden. Für PHP ist es sogar noch einfacher, Zufall zu erzeugen. Dazu ruft man lediglich die Funktion random_int ( $min, $max ); [2] auf. Das Intervall kann optional angegeben werden.

Wir sehen also, dass die Annahme vieler Menschen, unsere Welt wäre in großen Teilen berechenbar, nicht ganz. Es gibt in vielen Bereichen der Naturwissenschaften Prozesse, die wir nicht berechnen können, Diese bilden dann wiederum die Grundlage, um ‚echten‘ Zufall zu erzeugen. Für Anwendungen, die einen sehr starken Schutz benötigen, greift man oft auf Hardware zurück. Das können etwa Geräte sein, die den radioaktiven Zerfall eines gering strahlenden Isotops messen.

Das Feld der Kryptografie und auch der Web-Application-Security sind natürlich noch viel umfangreicher. Dieser Artikel sollte mit einem recht einfachen Beispiel auf die Notwendigkeit dieser Thematik lenken. Dabei habe ich es vermieden, mit komplizierter Mathematik mögliche Interessenten zu verwirren und sie schlussendlich auch zu vergraulen.

Ressourcen

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.


PHP Elegant Testing in Laravel

Die Programmiersprache PHP ist seit Jahrzehnten im Bereich der Webanwendungen für viele Entwickler die erste Wahl. Seit der Einführung objektorientierter Sprachfeatures mit der Version 5 wurde PHP erwachsen. Große Projekte lassen sich nun in eine saubere und vor allem wartbare Architektur bringen. Ein markanter Unterschied zwischen kommerzieller Softwareentwicklung und einem Hobby-Programmierer, der die Vereinshomepage zusammengebaut hat und betreut, ist der automatisierte Nachweis, dass die Anwendung festgelegte Vorgaben einhält. Hiermit betreten wir also das Terrain der automatisierten Softwaretests.

Ein wichtiger Grundsatz von automatisierten Softwaretests ist, dass diese ohne zusätzliche Interaktion nachweisen, dass die Anwendung ein zuvor festgesetztes Verhalten an den Tag legt. Softwaretests können nicht sicherstellen, dass eine Anwendung fehlerfrei ist, dennoch erhöhen sie die Qualität und reduzieren die Menge der möglichen noch enthaltenen Fehler. Der wichtigste Aspekt in automatisierten Softwaretests ist, dass ein bereits in Tests formuliertes Verhalten jederzeit in kurzer Zeit überprüft werden kann. Das stellt sicher, dass, wenn Entwickler eine bestehende Funktion erweitern oder in ihrer Ausführungsgeschwindigkeit optimieren, die vorhandene Funktionalität nicht beeinflusst wird. Kurz gesagt, haben wir ein leistungsfähiges Mittel, um sicherzustellen, dass wir bei unserer Arbeit nichts kaputtprogrammiert haben, ohne mühselig alle Möglichkeiten von Hand aus jedes Mal aufs Neue durchzuklicken.

Fairerweise muss man auch erwähnen, dass die automatisierten Tests entwickelt werden müssen, was wiederum im ersten Moment Zeit kostet. Dieser ‚vermeintliche‘ Mehraufwand kompensiert sich aber zügig, sobald die Testfälle mehrfach ausgeführt werden, um sicherzustellen, dass der Status Quo sich nicht verändert hat. Natürlich gehört es auch dazu, dass die erstellten Testfälle ebenfalls gepflegt werden müssen.

Wird etwa ein Fehler erkannt, schreibt man zuerst für diesen Fehler einen Testfall, der diesen Fehler nachstellt. Die Reparatur ist dann erfolgreich abgeschlossen, wenn der beziehungsweise die Testfälle erfolgreich sind. Aber auch Änderungen im Verhalten vorhandener Funktionalität erfordern immer ein entsprechendes Anpassen der zugehörigen Tests. Dieses Konzept, zur Implementierung der Funktion parallel Tests zu schreiben, ist in vielen Programmiersprachen umsetzbar und wird testgetriebene Entwicklung genannt. Aus eigener Erfahrung empfehle ich, auch bei vergleichsweise kleinen Projekten bereits testgetrieben vorzugehen. Kleine Projekte haben oft nicht die Komplexität großer Anwendungen, für die auch im Bereich des Testens einige Tricks benötigt werden. In kleinen Projekten hat man hingegen die Möglichkeit, im überschaubaren Rahmen seine Fertigkeiten auszubauen.

Testgetriebene Softwareentwicklung ist auch in PHP keine neue Sache. Das Unit-Test Framework PHPUnit von Sebastian Bergmann gibt es bereits seit 2001. Das um 2021 erschiene Test-Framework PEST setzt auf PHPUnit auf und erweitert dies um eine Vielzahl an neuen Möglichkeiten. PEST steht für PHP elegant Testing und definiert sich selbst als ein Werkzeug der neuen Generation. Da viele, vor allem kleinere Agenturen, die in PHP ihre Software entwickeln, in aller Regel auf manuelles Testen beschränken, möchte ich mit diesem kleinen Artikel eine Lanze brechen und aufzeigen, wie leicht es ist, PEST zu nutzen. Natürlich gibt es zu dem Thema testgetriebene Softwareentwicklung ein Füllhorn an Literatur, die auf die Art und Weise, wie man Tests in einem Projekt möglichst optimal organisiert. Dieses Wissen ist ideal für Entwickler, die bereits erste Gehversuche mit Test-Frameworks gemacht haben. Denn in diesen Büchern kann man lernen, wie man mit möglichst wenig Aufwand unabhängige, wartungsarme und performante Tests entwickelt. Um aber an diesen Punkt zu gelangen, muss man zuerst einmal die Einstiegshürde, die Installation der gesamten Umgebung, bewerkstelligen.

Eine typische Umgebung für eigene entwickelte Web-Projekte ist das Laravel-Framework. Beim Anlegen eines neuen Laravel-Webprojektes besteht die Möglichkeit, sich zwischen PHPUnit und PEST zu entscheiden. Laravel kümmert sich um alle notwendigen Details. Als notwendige Voraussetzung wird eine funktionierende PHP Umgebung benötigt. Dies kann zum einen ein Docker Container sein, eine native Installation oder die Serverumgebung XAMPP von Apache Friends. Für unser kurzes Beispiel verwende ich die PHP CLI in einem Debian Linux.

sudo apt-get install php-cli php-mbstring php-xml php-pcov

Nach Ausführen des Kommandos in der Konsole kann über den Befehl php -v der Erfolg der Installation getestet werden. Im nächsten Schritt benötigen wir einen Paketmanager, mit dem wir andere PHP Bibliotheken für unsere Anwendung bereitstellen können. Composer ist ein solcher Paketmanager. Dieser ist mit wenigen Anweisungen ebenfalls schnell auf dem System bereitgestellt.

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'ed0feb545ba87161262f2d45a633e34f591ebb3381f2e0063c345ebea4d228dd0043083717770234ec00c5a9f9593792') { echo 'Installer verified'.PHP_EOL; } else { echo 'Installer corrupt'.PHP_EOL; unlink('composer-setup.php'); exit(1); }"
php composer-setup.php
php -r "unlink('composer-setup.php');"

Damit wird die aktuelle Version der Datei composer.phar in das aktuelle Verzeichnis heruntergeladen, in dem der Befehl ausgeführt wird. Zudem wird auch automatisch der korrekte Hash überprüft. Damit Composer auch global über die Kommandozeile verfügbar ist, kann entweder der Pfad in die Pfadvariable aufgenommen werden oder man setzt eine Link der composer.phar in ein Verzeichnis, dessen Pfad bereits in der Bash integriert ist. Ich bevorzuge letztere Variante und erreiche dies über:

ln -d composer.phar $HOME/.local/bin/composer

Wenn alles korrekt ausgeführt wurde sollte nun mit composer list die Version inklusive der verfügbaren Kommandos ausgegeben werden. Ist dies der Fall, können wir global in das Composer Repository den Lavarel Installer installieren.

php composer global require laravel/installer

Damit wir nun auch über die Bash Lavarel installieren können, muss die Path-Variable COMPOSER_HOME gesetzt werden. Um herauszufinden, wo Composer das Repository angelegt hat, genügt der Befehl composer config -g home. Den hierüber ermittelten Pfad, der in meinem Fall /home/ed/.config/composer lautet, bindet man dann in die Variable COMPOSER_HOME. Nun können wir in einem leeren Verzeichnis

php $COMPOSER_HOME/vendor/bin/laravel new MyApp

ausführen, um ein neues Laravel-Projekt anzulegen. Die zugehörige Ausgabe auf der Konsole schaut wie folgt aus:

ed@P14s:~/Downloads/test$ php $COMPOSER_HOME/vendor/bin/laravel new MyApp

   _                               _
  | |                             | |
  | |     __ _ _ __ __ ___   _____| |
  | |    / _` |  __/ _` \ \ / / _ \ |
  | |___| (_| | | | (_| |\ V /  __/ |
  |______\__,_|_|  \__,_| \_/ \___|_|


  Which starter kit would you like to install? ────────────────┐
  None                                                         
 └──────────────────────────────────────────────────────────────┘

  Which testing framework do you prefer? ──────────────────────┐
  Pest                                                         
 └──────────────────────────────────────────────────────────────┘

Creating a "laravel/laravel" project at "./MyApp"
Installing laravel/laravel (v12.4.0)
  - Installing laravel/laravel (v12.4.0): Extracting archive
Created project in /home/ed/Downloads/test/MyApp
Loading composer repositories with package information

Die so erzeugte Verzeichnisstruktur enthält den Ordner tests, in dem die Testfälle abgelegt sind, sowie die Datei phpunit.xml, welche die Konfiguration der Tests vorhält. Laravel definiert zwei Test Suiten: Unit und Feature, die bereits jeweils einen Demo Test enthalten. Um die beiden Demotestfälle auszuführen, nutzen wir das von Laravel mitgelieferte Kommandozeilenwerkzeug artisan [1]. Um die Tests auszuführen, genügt im Root-Verzeichnis einzig der Befehl php artisan test.

Damit wir die Qualität der Testfälle beurteilen können, müssen wir die zugehörige Testabdeckung ermitteln. Die Coverage erhalten wir ebenfalls durch artisan mit der Anweisung test, die durch den Parameter –coverage ergänzt wird.

php artisan test --coverage

Für die von Lavarel mitgelieferten Demo Testfälle ist die Ausgabe wie folgt:

Leider sind die Möglichkeiten von artisan zum Ausführen der Testfälle sehr eingeschränkt. Um den vollen Funktionsumfang von PEST nutzen zu können, sollte von Beginn an gleich der PEST Exekutor verwendet werden.

php ./vendor/bin/pest -h

Den PEST Exekutor findet man im Verzeichnis vendor/bin/pest und mit dem Parameter -h wird die Hilfe ausgegeben. Neben diesem Detail beschäftigt uns der Ordner tests, den wir bereits erwähnt haben. Im initialen Schritt sind über die Datei phpunit.xml zwei Testsuiten vorkonfiguriert. Die Testdateien selbst sollten mit dem Suffix Test enden, wie im Beispiel ExampleTest.php.

Im Vergleich zu anderen Test Suiten, versucht PEST möglichst viele Konzepte der automatisierten Testausführung zu unterstützen. Um dabei den Durchblick nicht zu verlieren, sollte jede Teststufe in einer eigenen Test Suite abgelegt werden. Neben den klassischen UnitTests werden Browsertests, Stresstests, Architekturtests und sogar das neu aufgekommene Mutation Testing ermöglicht. Natürlich kann dieser Artikel nicht alle Aspekte von PEST behandeln, zudem sind mittlerweile auch viele hochwertige Tutorials für das Schreiben klassischer Komponententests in PEST verfügbar. Deswegen beschränke ich mich auf einen Überblick und ein paar weniger verbreitete Konzepte.

Architektur-Test

Der Sinn von Architekturtests ist es auf einfache Weise zu überprüfen, ob die Vorgaben durch die Entwickler auch eingehalten werden. Dazu zählt, dass unter anderem Klassen, die Datenmodelle repräsentieren, in einem festgelegten Verzeichnis liegen und nur über spezialisierte Klassen aufgerufen werden dürfen.

test('models')
->expect('App\Models')
->toOnlyBeUsedOn('App\Repositories')
->toOnlyUse('Illuminate\Database');

Mutation-Test

Diese Form des Testens ist etwas Neues. Zweck der Übung ist es durch Veränderungen, z. B. in Bedingungen der originalen Implementierungen, sogenannte Mutanten zu erzeugen. Wenn die zu den Mutanten zugeordneten Tests weiterhin korrekt durchlaufen werden, anstatt fehl zuschlagen, kann das ein starker Hinweis darauf sein, dass die Testfälle möglicherweise fehlerhaft sind und keine Aussagekraft haben.

Original: if(TRUE) → Mutant: if(FALSE)

Stress-Test

Eine andere Bezeichnung für Stresstests sind Penetrationstests, die besonders auf die Performance einer Anwendung abgesehen haben. Damit kann man also sicherstellen, dass die Web-App beispielsweise mit einer definierten Anzahl an Zugriffen zurechtkommt.

Natürlich sind noch viele andere hilfreiche Funktionen vorhanden. So kann man Tests beispielsweise gruppieren und die Gruppen können dann einzeln aufgerufen werden.

// definition
pest()->extend(TestCase::class)
->group('feature')
->in('Feature');

// calling
php ./vendor/bin/pest --group=feature

Für alle diejenigen, die nicht mit dem Lavarel Framework arbeiten und dennoch nicht auf das Testen in PHP mit PEST verzichten möchten, können das PEST Framework auch so in ihre Anwendung einbauen. Dazu muss lediglich in der Composer Projektkonfiguration PEST als entsprechende Entwicklungsabhängigkeit definiert werden. Anschließend kann im Wurzelverzeichnis des Projektes das Initial Test Setup angestoßen werden.

php ./vendor/bin/pest --init

Wie wir sehen konnten, sind allein die hier kurz vorgestellten Optionen sehr mächtig. Die offizielle Dokumentation von PEST ist auch sehr ausführlich und sollte grundsätzlich die erste Anlaufstelle sein. In diesem Artikel ging es mir vor allem darum, die Einstiegshürden für testgetriebene Entwicklung in PHP zu minimieren. Denn auch PHP bietet mittlerweile ein gutes Füllhorn an Möglichkeiten, sehr effizient und zuverlässig kommerzielle Softwareprojekte umzusetzen.

Ressourcen

Erfolgreiches Validieren von ISBN Nummern

Regelmäßig stehen Entwickler vor der Aufgabe, Nutzereingaben auf Korrektheit zu prüfen. Mittlerweile existiert eine erhebliche Anzahl an standardisierten Datenformaten, mit denen solche Validierungsaufgaben leicht zu meistern sind. Die International Standard Book Number oder kurz ISBN ist ein solches Datenformat. ISBN gibt es in zwei Ausführungen: in einer zehnstelligen und in einer 13-stelligen Variante. Von 1970 bis 2006 wurde die zehnstellige Version der ISBN verwendet (ISBN-10), die im Januar 2007 von der 13-stelligen Fassung abgelöst wurde (ISBN-13). Heutzutage ist es in vielen Verlagen verbreitete Praxis, für Titel beide Versionen der ISBN bereitzustellen. Dass sich anhand dieser Nummer Bücher eindeutig identifizieren lassen, ist allgemein bekannt. Das bedeutet natürlich auch, dass diese Nummern eindeutig sind. Es gibt also keine zwei unterschiedlichen Bücher mit gleicher ISBN (Bild 1).

Der theoretische Hintergrund, um festzustellen, ob eine Zahlenfolge korrekt ist stammt aus der Codierungstheorie. Wer sich also etwas ausführlicher mit dem mathematischen Hintergrund Fehler-erkennender und Fehler-korrigierender Codes beschäftigen möchte, dem Sei das Buch „Codierungstheorie“ von Ralph Hardo Schulz empfohlen [1]. Darin lernt man beispielsweise, wie die Fehlerkorrektur bei Comact Disks (CD) funktioniert. Aber keine Sorge, wir reduzieren in diesem kleinen Workshop die notwendige Mathematik auf ein Minimum.

Bei der ISBN handelt es sich um einen Fehler erkennenden Code. Wir können also den erkannten Fehler nicht automatisch wieder beheben. Wir wissen nur, dass etwas falsch ist, kennen aber nicht den konkreten Fehler. Gehen wir der Sache daher ein wenig auf den Grund.

Warum man sich bei ISBN-13 genau auf 13 Stellen geeinigt hat, bleibt Spekulation. Zumindest haben sich die Entwickler nicht von irgendwelchem Aberglauben beeindrucken lassen. Das große Geheimnis hinter der Validierung ist die Bestimmung der Restklassen [2]. Die Algorithmen für ISBN-10 und ISBN-13 sind recht ähnlich. Beginnen wir also mit dem älteren Standard, ISBN-10, der sich wie folgt errechnet:

1x1 + 2x2 + 3x3 + 4x4 + 5x5 + 6x6 + 7x7 + 8x8 + 9x9 + 10x10 = k modulo 11

Keine Sorge, um die oben stehende Formel zu verstehen, müssen Sie kein Raketeningenieur bei SpaceX sein. Wir heben den Schleier der Verwirrung anschaulich mit einem kleinen Beispiel für die ISBN 3836278340. Daraus ergibt sich folgende Rechnung:

(1*3) + (2*8) + (3*3) + (4*6) + (5*2) + (6*7) + (7*8) + (8*3) + (9*4) + (10*0) = 220
220 modulo 11 = 0

Die letzte Ziffer der ISBN ist die sogenannte Prüfziffer. In dem aufgeführten Beispiel lautet diese 0. Um diese Prüfziffer zu erhalten, multiplizieren wir jede Stelle mit ihrem Wert. Das heißt, an vierter Position steht eine 6, also rechnen wir 4 * 6. Das wiederholen wir mit allen Positionen und die einzelnen Ergebnisse addieren wir zusammen. So erhalten wir den Betrag 220. Die 220 wird mit der sogenannten Restwertoperation Modulo durch 11 geteilt. Da die 11 genau 20 mal in die 220 hineinpasst, bleibt ein Rest null. Das Ergebnis von 220 modulo 11 ist 0 und stimmt mit der Prüfziffer überein, was uns sagt das eine gültige ISBN-10 vorliegt.

Eine Besonderheit gibt es aber noch zu beachten. Bisweilen kommt es vor, dass die letzte Ziffer der ISBN mit X endet. In diesem Fall ist das X gegen 10 auszutauschen.

Wir sehen, der Algorithmus ist sehr einfach gehalten und kann leicht über eine einfache for-Schleife umgesetzt werden.

boolean success = false;
int[] isbn;
int sum = 0;

for(i=0; i<10; i++) {
    sum += i*isbn[i];
}

if(sum%11 == 0) {
    success = true;
}

Um den Algorithmus so einfach wie möglich zu halten, wird jede Stelle der ISBN-10-Nummer in einem Integer-Array gespeichert. Ausgehend von dieser Vorbereitung ist es nur noch nötig, das Array zu durchlaufen. Wenn dann die Überprüfung der Summe durch das Modulo 11 das Ergebnis 0 liefert, ist alles bestens.

Um die Funktion richtig zu testen, werden zwei Testfälle benötigt. Einerseits gilt es zu überprüfen ob eine ISBN korrekt erkannt wird. Der zweite Test überprüft die sogenannten false positives. Es wird also ein erwarteter Fehler mit einer falschen ISBN provoziert. Das lässt sich zügig bewerkstelligen, indem man von einer gültigen ISBN eine beliebige Stelle ändert.

Unser ISBN-10 Validator hat noch einen kleinen Schönheitsfehler. Ziffernfolgen, die kürzer oder länger als 10 sind, also dem erwarteten Format nicht entsprechen, könnten bereits vorher abgewiesen werden. Der Grund hierfür lässt sich in dem Beispiel erkennen: Die letzte Stelle der ISBN-10 ist eine 0 – somit ist das Zeichenergebnis auch 0. Wird die letzte Stelle also vergessen und eine Prüfung auf das korrekte Format fehlt, wird der Fehler nicht erkannt. Etwas das keine Auswirkung auf den Algorithmus hat, aber sehr hilfreich als Feedback bei Nutzereingaben ist, ist das Eingabefeld so lange auszugrauen und den Absenden-Button zu deaktivieren, bis das korrekte Format der ISBN eingegeben wurde.

Der Algorithmus für ISBN-13 ist ähnlich einfach aufgebaut.

x1 + 3x2 + x3 + 3x4 + x5 + 3x6 + x7 + 3x8 + x9 + 3x10 + x11 + 3x12 + x13 = k modulo 10

Analog wie bei ISBN-10 steht xn für den Zahlenwert an der entsprechenden Position in er ISBN-13. Auch hier werden die Teilergebnisse aufsummiert und durch ein Modulo geteilt. Der große Unterschied ist, dass hier nur die geraden Positionen, also die Stellen 2, 4, 6, 8, 10 und 12, mit 3 multipliziert werden und das Ergebnis dann mit Modulo 10 dividiert wird. Als Beispiel berechnen wir die ISBN-13: 9783836278348.

9 + (3*7) + 8 + (3*3) + 8 + (3*3) + 6 + (3*2) + 7 + (3*8) + 3 + (3*4) + 8 = 130
130 modulo 10 = 0

Auch für die ISBN-13 lässt sich der Algorithmus in einer einfachen for-Schleife umsetzen.

boolean success = false;
int[] isbn;
int sum = 0;

for(i=0; i<13; i++) {
    if(i%2 == 0) {
        sum += 3*isbn[i];
    } else {
        sum += isbn[i];
    }
}

if(sum%10 == 0) {
    success = true;
}

Die beiden Codebeispiele zu ISBN-10 und ISBN-13 unterscheiden sich vor allem in der if-Bedingung. Der Ausdruck i % 2 berechnet den Modulo-Wert 2 zur jeweiligen Iteration. Wenn an dieser Stelle der Wert 0 herauskommt, bedeutet das, dass es sich um eine gerade Zahl handelt. Der dazugehörige Wert muss dann mit 3 multipliziert werden.

Hier zeigt sich wie praktisch die Modulo-Operation % für das Programmieren sein kann. Um die Implementierung möglichst kompakt zu halten, kann anstatt der if-else-Bedingung auch der sogenannte Dreifach-Operator verwendet werden. Der Ausdruck sum += (i%2) ? isbn[i] : 3 * isbn[3] ist wesentlich kompakter, dafür aber auch schwerer zu verstehen.

Nachfolgend finden Sie eine vollständig implementierte Klasse zur Prüfung der ISBN in den Programmiersprachen: Java, PHP und C#.

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.

Die in den Beispielen vorgestellten Lösungen haben zwar alle denselben Kernansatz, unterscheiden sich aber nicht nur in syntaktischen Details. So bietet die Java-Version eine allumfassende Variante, die etwas generischer zwischen ISBN-10 und ISBN-13 unterscheidet. Das demonstriert zum einen, dass viele Wege nach Rom führen. Soll aber auch gerade weniger erfahrenen Entwicklern verschiedene Lösungsansätze zeigen und sie motivieren, eigene Anpassungen vorzunehmen. Um das Verständnis zu vereinfachen, wurde der Quelltext mit Kommentaren angereichert. Bei PHP, als untypisierte Sprache, entfällt insbesondere das Konvertieren des Strings in Nummern. Dafür wird eine RegEx genutzt, um sicherzustellen, dass die eingegebenen Zeichen typsicher sind.

Lessons Learned

Wie Sie sehen, handelt es sich bei der Überprüfung, ob eine ISBN korrekt ist, um keine Hexerei. Das Thema der Validierung von Benutzereingaben ist natürlich viel umfangreicher. Andere Beispiele sind Kreditkartennummern. Aber auch reguläre Ausdrücke leisten in diesem Zusammenhang wertvolle Dienste.

Ressourcen

  • [1] Ralph-Hardo Schulz, Codierungstheorie: Eine Einführung, 2003, ISBN 978-3-528-16419-5
  • [2] Begriff der Restklasse bei Wikipedia, https://de.wikipedia.org/wiki/Restklasse

PHP meets Maven – Teil 4

[Teil 1] [Teil 2] [Teil 3] [Teil 4]

Für die Integration in IDEs ist es unerheblich, bei welcher IDE Ihre persönlichen Präferenzen angesiedelt sind, der Funktionsumfang der Integration ist in beiden IDEs weitgehend identisch und unterscheidet sich nur in Details. Während NetBeans von Haus aus Maven-Projekte unterstützt, ist für die meisten Eclipse-Distributionen die zusätzliche Installation des Eclipse-Maven-Plug-ins m2e notwendig.

Der Vorteil, die Funktionalitäten von Maven innerhalb einer IDE nutzen zu können, ist enorm. Ein Aspekt ist beispielsweise der Import bestehender Maven-Projekte in die Entwicklungsumgebung. Anhand der POM werden die notwendigen Konfigurationen des gesamten Projekts wie zum Beispiel Verzeichnisse für Sourcen, Test und Dependencies aus der POM gelesen. Ein mühseliges Adaptieren der Projekteigenschaften nach einem Import entfällt ebenso wie das Verteilen der IDE-Konfiguration über das Konfigurationsmanagement. Dadurch hat der Entwickler mehr Freiheit bei der Wahl seiner Entwicklungsumgebung. Eine Grundvoraussetzung für den erfolgreichen Import eines Projekts in eine IDE ist, dass die verwendeten Dependencies lokal oder remote verfügbar sind. Hin und wieder kommt es vor, dass einzelne Artefakte manuell in das lokale Repository installiert werden müssen. Diese Aufgabe lässt sich in beiden IDEs sehr komfortabel mit wenigen Mausklicks bewerkstelligen und ein optisches Feedback des Erfolgs kann über die Views der Repository-Browser eingeholt werden.

Ältere Projekte, die nicht im Maven-Format vorliegen und damit nicht die notwendige Verzeichnisstruktur und POM aufweisen, lassen sich in den meisten Fällen über die Konsole automatisiert migrieren. Der schnellere Weg ist allerdings eine manuelle Migration, da die automatisch generierte POM in aller Regel im Nachhinein weiter von Hand angepasst werden muss. Über Archetypes werden die Verzeichnisstruktur und die POM erzeugt. Im zweiten Schritt sind die Sourcen et cetera in die entsprechenden Verzeichnisse zu kopieren, um abschließend die Dependencies zu konfigurieren. In späteren Arbeitsschritten kann die POM den Projektanforderungen weiter angepasst werden.

Eine wichtige Eigenschaft ist unter anderem auch die Möglichkeit, das vorhandene Maven Build-in der IDEs durch eine eigene Maven-Installation auszutauschen. Der Vorteil einer externen Installation ergibt sich aus dem größtmöglichen Einfluss auf den Entwicklungsprozess, da beispielsweise festgelegt wird, welche Version von Maven verwendet wird. Vor allem, wenn stets auf die neueste Version zurückgegriffen werden soll, ist diese Option von unschätzbarem Wert, das es meist einige Zeit dauert, bis das entsprechende Plug-in aktualisiert wird.

Die größten Unterschiede zwischen NetBeans und Eclipse finden sich bei der Bearbeitung der POM. Während NetBeans auf eine Code-Vervollständigung setzt, bietet Eclipse einen grafischen POM-Editor. Für den korrekten Betrieb von Maven ist die Auszeichnung der Schemadefinition der POM nicht notwendig. Das Weglassen der XML-Schemadefinition quittiert Eclipse mit einer Fehlerausgabe, die folgende Auszeichnung des <project> Tags beendet die Belästigung umgehend:

<project 
	xmlns="http://maven.apache.org/POM/4.0.0" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	                    http://maven.apache.org/maven-v4_0_0.xsd">
XML

Das Anstoßen der einzelnen Build-Lifecycles aus Eclipse oder NetBeans heraus ist mittlerweile recht intuitiv. Der Übersichtlichkeit wegen werden nur die wichtigsten Phasen der Lifecycles zum direkten Ausführen über die Toolbar beziehungsweise das Kontextmenü angeboten. Dazu zählen vor allem Build, Clean, Clean Build und Test. Wenn dennoch einmal ein spezielles Goal gestartet werden muss, bieten beide IDEs die Möglichkeit, über einen Wizard die entsprechende Phase mit dem gewünschten Goal zu konfigurieren und auszuführen. Im Screenshot ist beispielhaft die Run-Konfiguration von Eclipse abgebildet. Für php-maven Projekte existiert ein Eclipse-Plug-in, das von Martin Eisengart entwickelt wurde. Aktuell ist dazu eine neue Version für Eclipse Indigo erschienen. Eine wichtige Eigenschaft dieses Plug-ins ist die Konvertierungsfunktion für Maven-Projekte nach php-maven. Nach erfolgreicher Konvertierung zeigt Eclipse in der View Problems den Fehler, dass das maven-plugin-Plug-in nicht ausgeführt werden kann. Diese Meldung ist kein wirklicher Fehler, sondern ergibt sich aus den Restriktionen des m2e-Plug-ins, das für alle unbekannten Plug-ins Fehler ausgibt.

Sehr komfortabel ist das Generieren der Site über das Plug-in. Dazu hält der Menüeintrag die Punkte generate, view und deploy bereit. Besonders angenehm ist die Option, die generierte Seite im Browser auszugeben, ohne umständlich über das Target-Verzeichnis navigieren zu müssen.

Wenn die Testfälle über das Kontextmenü in das Projekt eingebunden wurden, kann die View PHPUnit die wichtigsten Informationen der durchlaufenen Testfälle visualisieren. Neben den Testergebnissen wird auch eine Coverage ausgegeben (Bild 6).

Berichtswesen

Neben dem Build- und Clean-Lifecycle existiert als Dritter im Bunde der Site-Lifecycle, mit dem Reports und sogar komplette Webseiten automatisch generiert werden können. Ein gutes Beispiel der Site-Generierung ist die Homepage des php-maven Projekts, die mit Maven erzeugt wurde.

Innerhalb der POM können verschiedene Angaben zu wichtigen Projektinformationen gemacht werden, die über eine Projektseite publiziert werden können. Typische Informationen sind unter anderem der Projektname mit einer Kurzbeschreibung, dem Gründungsjahr und der Lizenz des Artefakts. Neben diesen allgemeinen Informationen können auch die URLs zu CI-Servern, Sourcecode-Repositories, Mailing-Listen und beteiligten Personen angegeben werden. Die notwendigen Einträge der POM zeigt Listing 1.

<licenses>
	<license>
		<name>BSD 3-Clause</name>
		<url>http://www.opensource.org/licenses/BSD-3-Clause/</url>
	</license>
</licenses>

<name>CMS</name>
<description>
	A Collection of diffrent Modules for a CMS.
</description>
<url>https://elmar-dott.com</url>
<inceptionYear>2012</inceptionYear>

<scm>
	<url>https://git.elmar-dott.com</url>
	<connection>https://git.elmar-dott.com</connection>
	<developerConnection>https://git.elmar-dott.com</developerConnection>
</scm>

<issueManagement>
	<system>Redmine</system>
	<url>https://issues.elmar-dott.com/</url>
</issueManagement>

<ciManagement>
	<system>Jenkins</system>
	<url>http://localhost/jenkins</url>
</ciManagement>

<developers>
	<developer>
		<name>Elmar Dott</name>
		<id>ed</id>
		<email>ed@elmar-dott.com</email>
		<roles>
			<role>Release-Management</role>
		</roles>
		<organization>Elmar Dott Consulting</organization>
		<organizationUrl>https://elmar-dott.com</organizationUrl>
		<timezone>+1</timezone>
	</developer>
</developers>

Um der Seite statische Inhalte zuzufügen, stehen unterschiedliche Mechanismen zur Auswahl. Grundlegend ist das Verzeichnis site unterhalb von src im Projektverzeichnis anzulegen, in dem unter anderem auch der Site-Deskriptor hinterlegt wird. Über den Site-Deskriptor site.xml werden unter anderem die Navigation zusammengebaut und zusätzliche Inhalte hinzugefügt. Es können drei unterschiedliche Content-Typen erzeugt werden: APT (Almost Plain Text) ist ein an Wiki Style angelehntes Format, während fml eine FAQ-Struktur erzeugt und überwiegend in Maven-1-Projekten zum Einsatz kam. Am verbreitetesten ist xDoc, ein XML-basiertes Format, um Inhalte zu erstellen.

Um der Seite verschiedenste Reports hinzuzufügen, ist das Site-Plug-in entsprechend zu konfigurieren. Der übliche Weg über den Abschnitt <reports> ist mittlerweile als deprecated gekennzeichnet und sollte nicht weiter verwendet werden. Um nicht benötigte Reports auszusparen, werden diese in der Konfiguration des <reportSets> weggelassen. Auf der Maven-Plugin-Seite finden sich noch weitere Plug-ins zu Reports, beispielsweise das Checkstyle-Plug-in, um den Code auf die Einhaltung festgelegter Style-Guides zu prüfen.


PHP meets Maven – Teil 3

[Teil 1] [Teil 2] [Teil 3] [Teil 4]

Wer bereits einmal in die Verlegenheit gekommen ist eine im Produktivzustand arbeitende PHP-Webapplikation zu aktualisieren, wird mir sicherlich beipflichten, das diese Arbeit äußerst ungern gemacht wird. Eine andere Unschönheit ergibt sich daraus, wenn ein solches System für die Entwicklung eines neuen Webauftritts beispielsweise lokal installiert wird. Nach getaner Arbeit sind dann verschiedene Hürden zu meistern, um die Anwendung über ein QS-System auf dem Live-Server lauffähig zu bekommen. Viele Probleme lassen sich bereits während der Entwicklungsphase durch etwas Planung und eine saubere Architektur vermeiden. Gerade bei Webanwendungen kann durch eine effiziente Modularisierung in Kombination mit Maven ein erheblicher Mehrwert erzielt werden.

Ziel dieses Teils der Artikelserie ist es nicht, Migrationswege für bereits bewährte Webapplikationen wie beispielsweise Magento, Media-Wiki und Jomoola nach Maven aufzuzeigen. Ein solches Vorhaben sollte aus verschiedenen Gründen reiflich überlegt werden und ist eher etwas für erfahrene Entwicklungsteams. Für eine erfolgreiche Migration ist tiefgreifendes Systemwissen unbedingt notwendig.

Gezeigt wird, wie mit PHP und Maven moderne und zukunftssichere Webanwendungen erstellt werden können. Die Basis dazu bilden die bereits vorgestellten Library-Artefakte, die nun zu einer gesamten Anwendung orchestriert werden. Etablierte Applikationen wie Magento, um nur einen willkürlich gewählten Vertreter zu nennen, sind weitaus älter als die vorgestellten OOP-Eigenschaften, die durch PHP 5.3 eingeführt wurden. Deswegen ist auch kein direkter Architekturvergleich möglich.

Die Segel in Richtung Zukunft

Die Vision in der Software-Entwicklung besteht vor allem darin, einmal entwickelte Module wiederverwenden zu können. Im PHP-Maven-Projekt ist das erklärte Ziel, ein umfangreiches Repository an freien und kommerziellen Artefakten im Lauf der Zeit anzusammeln und zur Verfügung zu stellen. Um Namenskonflikten aus dem Weg zu gehen, ist die Verwendung von Namespaces in Library-Projekten unumgänglich. Wichtige Designregeln sollten zwingend eingehalten werden, wofür die folgende Checkliste herangezogen werden kann:

  • echo und print sind innerhalb des Produktivcodes absolut tabu.
  • Die Entwicklung erfolgt rein objektorientiert (OOP).
  • Namespaces sind zu verwenden.
  • Eine Klasse pro Datei, wobei Klasse und Dateinamen identisch sind (korrespondieren).
  • Kein Modul darf direkt auf eine Datenbanktabelle eines anderen Artefakts zugreifen; es sind nur API-Aufrufe gestattet.
  • Content wird über Datenbanktabellen persistiert.
  • Die Konfiguration erfolgt über XML- oder INI-Dateien.

Diese Liste der aufgezählten Punkte stellt eine Mindestanforderung für Artefakte dar, die darauf abzielen, ihre Funktionalität möglichst vielen Projekten über einen langen Release-Zeitraum zur Verfügung zu stellen. Die Reihenfolge ist keine Priorisierung. Ein klarer Stil der Codierung sollet stringent eingehalten werden. Beachtet man diese Punkte nicht, kann sich das negativ auf den Entwicklungsprozess auswirken.

Die Problematik der Namespaces wurde bereits erläutert. Die Forderung, dem OOP-Paradigma zu folgen, begründet ihren Ursprung vor allem in der Kapselung der Funktionalitäten und der guten Strukturierung des Codes. Dass der Dateiname mit der Klasse zu korrespondieren hat, dient ebenfalls der besseren Übersicht und ermöglicht das Verwenden von Auto-Class-Loadern. In aller Regel werden fertige Artefakte durch eine übergeordnete Anwendung aufgerufen. Erzeugt ein Artefakt eigenständig sichtbare Systemausgaben in der Anwendung, ist dies ein ernstes Problem. Fehler oder Debug-Ausgaben sind aus diesem Grund ausschließlich über ein Logging-Verfahren zu behandeln. Ein sehr wichtiger Punkt im Hinblick auf die Wartbarkeit einer Applikation ist die Forderung nach Zugriffen auf Datenbanktabellen. Sicherlich mag im ersten Moment ein SQL-Statement attraktiver wirken als ein API-Aufruf. Immerhin attestiert es dem Entwickler einen tiefen Einblick in das vorhandene System. Dummerweise offenbart ein solches Vorgehen nicht die Brillanz des Akteurs, sondern dessen mangelnde Teamfähigkeit.

Ein weiterer Aspekt ist das Persistieren von Daten. Die Faustregel zur Entscheidung, wie Daten langfristig zu speichern sind, ist, dass alle Einstellungen, die das Verhalten eines Systems beeinflussen, in Textdateien abgelegt werden sollten. Durch Nutzer erzeugte Inhalte wie Texte gehören in eine Datenbank. Typische Konfigurationseinstellungen sind Datenbankparameter, da sie sich je nach System unterscheiden. Solche Dinge in einer Datenbank abzulegen erschwert den Aufwand des Deployments erheblich. Content hingegen hat keinen direkten Einfluss auf die Applikation und muss daher nicht in die Entwicklungssysteme synchronisiert werden. Im Gegenteil, dieser Zustand wäre ein erhebliches Sicherheitsrisiko. Ein Beispiel wären Accountdaten mit Adresse und Bankverbindung der Nutzer eines Webshops. Diese Information ist nur dem Betreiber zugedacht und nicht der Entwicklungsabteilung der Applikation.

Strukturarbeiten

Nachdem nun die Voraussetzungen für optimales Artefakt-Design bekannt sind, ist es an der Zeit, diese durch eine Webanwendung zu einem Gesamtwerk zu vereinen. Auch wenn es auf den ersten Blick trivial erscheint: Ein geschickt gewähltes Verzeichnislayout ist bereits die halbe Miete. Bild 1 enthält eine empfohlene Verzeichnisstruktur für Webprojekte mit den wohlbekannten Elementen. Einzige Ausnahme bildet hier der Ordner PHP-INF mit sämtlichen geschützten Inhalten, die für Außenstehende nicht einsehbar sein dürfen. Das Vorbild dieses Verzeichnisses ist Java-Webprojekten entnommen. Um das PHP-INF Verzeichnis vor unerwünschten Blicken verborgen zu halten, bietet sich eine .htaccess Datei in Kombination mit einer robots.txt an, die sämtliche Suchmaschinen aussperrt, um nur einige Schutzmechanismen aufzuzeigen.

Von besonderem Interesse sind die Dateien des Unterverzeichnisses libs. Wie diese PHP-Archive erzeugt werden, wurde im vorangegangenen Teil dieser Serie beschrieben. Im Kontext der Webanwendung sind diese Artefakte einfache Dependencies, die durch Maven verwaltet werden und über die Bootstrap-Datei index.php eingebunden sind. Auf diese Weise entsteht im Lauf der Zeit ein Baukastenprinzip, ähnlich einem Komponenten-Framework.

Im Gegensatz zum vorgestellten Library-Projekt befinden sich die Sourcen nun im Ordner resources. Der Grund dafür ist sehr schnell aufgezeigt. Maven kopiert aus diesem Verzeichnis die Dateien in der gleichen Hierarchie in das target Verzeichnis. Ein besonders hilfreiches Feature ist, dass Maven im resources Verzeichnis Filter anwenden kann, die es ermöglichen, Texte zu ersetzen. Dazu ist lediglich über den Build-Lifecycle das Filtering in der POM zu aktivieren:

<build>
	<resources>
		<resource>
			<directory>src/main/resources/</directory>
			<filtering>true</filtering>
		</resource>
	</resources>
</build>
XML

Diese Eigenschaft ist besonders wertvoll für das Deployment. In den Konfigurationsdateien der Anwendung können so Systemeigenschaften in Platzhalter ausgelagert werden. So erklärt sich auch die strikte Forderung, Systeminformationen in Textdateien vorzuhalten. Es besteht natürlich auch die Option, in SQL-Dateien eine Textersetzung vorzunehmen, um Konfigurationen vorzuhalten. Man sollte sich aber bewusst sein, dass Datenbanktabellen, die im schlimmsten Fall in einer Spalte Konfigurationen vorhalten, kaum zum Verständnis des Systems beitragen. Besonders aus Sicht der Wartbarkeit bietet eine Konfigurationsdatei mehr Flexibilität als SQL-Statements. Die Eigenheit, alles möglichst über Datenbanktabellen abzuspeichern, hat eine eher einfache Ursache. Eine Konfigurationsdatei im Filesystem muss durch verschiedene Mechanismen vor unbefugtem Zugriff geschützt werden. Datenbanktabellen bieten von Hause aus mehr Sicherheit. Ähnlich verhält es sich bei den bekannten config.php Files.

Um Texte ersetzen zu können, werden sogenannte Profile benötigt, die in dem vorgestellten Beispiel über die POM vorgehalten werden. Die verschiedenen Profile werden über eine ID unterschieden.

<profiles>
	<profile>
		<id>local</id>
		<activation>
		    <activeByDefault>true</activeByDefault>
		</activation>
		<properties>
			<dbms>mysql</dbms>
			<db.server>localhost</db.server>
			<db.name>test</db.name>
			<db.prefix>test_</db.prefix>
			<db.user>User</db.user>
			<db.pwd>login</db.pwd>
		</properties>
	</profile>
	<profile>
		<id>qs-stage</id>
		<properties>
			<dbms>mysql</dbms>
			<db.server>localhost</db.server>
			<db.name>test</db.name>
			<db.prefix>test_</db.prefix>
			<db.user>User</db.user>
			<db.pwd>login</db.pwd>
		</properties>
	</profile>
</profiles>
XML

Ein möglicher Weg, um ein Profil zu aktivieren, ist das <activeByDefault> -Tag. Es gibt natürlich auch noch viele andere Wege, die auf der Manual-Page beschrieben werden.

PHP-CLI

Damit Maven seine volle Kraft ausschöpfen kann, ist es notwendig, das Command Line Interface (CLI) für PHP in der Konsole zu aktivieren.

Mit dem CLI ist es möglich, PHP-Skripts ohne Webbrowser direkt auf der Kommandozeile auszuführen. Diese Funktion wird beispielsweise benötigt, um aus Maven heraus die Sourcecode-Dokumentation über den php-Documentor anzustoßen. Sobald der Pfad zum Verzeichnis der php.exe in die PATH-Variable aufgenommen wurde, können PHP-Skripts über die Konsole ausgeführt werden. Der Erfolg einer Installation lässt sich durch die Anweisung php –v rasch überprüfen. Im Erfolgsfall wird sie mit der Ausgabe der installierten PHP-Version quittiert.

Packungsinhalte

Nachdem die Projektstruktur von Webapplikationen mit ihren Besonderheiten vorgestellt worden ist, ist es nun an der Zeit, einige Details über die POM zu erwähnen. Um die Vielseitigkeit von Maven zu demonstrieren, wird der Packagetyp rar gewählt. Durch etwas Zauberei wird allerdings keine RAR-Datei, sondern eine ZIP-Datei ausgeliefert. Der Grund für diese Entscheidung: Diese Webanwendung ist ein individuelles Projekt und soll nicht innerhalb anderer Projekte verwendet werden. Daher ist es nicht notwendig, das Artefakt in einem Repository vorzuhalten. Aus dieser Tatsache ergibt sich auch das verify.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <phase>verify</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <target>
          <delete>
            <fileset dir="${project.build.directory}/${package.dir}" includes="*.phar"/>
            <fileset dir="${project.build.directory}/${package.dir}" includes="**/*placeholder"/>
            <fileset dir="${project.build.directory}" includes="*.rar"/>
          </delete>
          
          <zip destfile="${project.build.directory}/${package.dir}.zip"
        basedir="${project.build.directory}/${package.dir}"
        update="true" />
        </target>
      </configuration>
    </execution>
  </executions>
</plugin>

Der Auszug der POM zeigt unter anderem auch, wie das Library-Projekt als Dependency eingebunden wird. Der Scope weist das Artefakt für die Verwendung zur Laufzeit aus. Damit die entsprechenden Dateien im target-Verzeichnis vollständig zu einer ZIP gepackt werden können, sind innerhalb des <build> Tags noch einige Plug-ins zu konfigurieren.

Eine zentrale Rolle spielt das antrun Plug-in. Um in Maven in Archiven zusätzlichen Inhalt einzufügen, sind Assemblies vorgesehen. Wesentlich einfacher ist der Weg über ANT. Das antrun Plug-in ermöglicht das Ausführen von ANT-Tasks.

Die Konfiguration des Plug-ins ist weitgehend selbsterklärend. Innerhalb von <configuration> können verschiedene Task definiert werden. Eine ausführliche Übersicht bietet das User-Manual von ANT.

Elternteile

In den POMs der Library-Artefakte ist der Eintrag zu finden, der auf eine parent-pom für PHP-Maven-Projekte verweist. Dieses Konstrukt bedeutet, dass dem aktuellen Projekt noch ein Projekt übergeordnet ist. Grundsätzlich können Projekte verschiedenster Art beliebig tief verschachtelt werden. Damit das gesamte Konstrukt aber auch überschaubar bleibt, sollte vorher reiflich überlegt werden, wie feingranular ein Projekt aufgebaut werden muss. Um ein Multiprojekt zu erzeugen, muss lediglich die -POM angegeben werden, und über den Eintrag kann auf die untergeord-
neten Module verwiesen werden:

<parent>
	<groupId>org.phpmaven</groupId>
	<artifactId>php-parent-pom</artifactId>
	<version>2.0-SNAPSHOT</version>
</parent>

<groupId>de.banaalo</groupId>
<artifactId>modules</artifactId>
<version>1.0</version>
<packaging>pom</packaging>

<modules>
	<module>validator</module>
</modules>

Der Vorteil des Multiprojekts modules ist, dass die gesamte Konfiguration für die Unterprojekte in der übergeordneten POM erfolgt. Es werden nur noch die individuellen Konfigurationen in den Unterprojekten ergänzt:

<project>
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>de.banaalo</groupId>
		<artifactId>modules</artifactId>
		<version>1.0</version>
	</parent>

	<groupId>de.banaalo.modules</groupId>
	<artifactId>validator</artifactId>
	<version>1.0</version>
	<packaging>php</packaging>
</project>

Wie an der POM des Validators zu sehen ist, ist die Konfiguration erfreulich kurz. Die Effizienz ergibt sich, sobald mehr als ein Modul im gleichen Kontext erzeugt wird. Die Parent-POM stellt sicher, dass für alle Teilprojekte dieselben Dependencies verfügbar sind. So kann verhindert werden, dass beispielsweise Modul A für die XML-Verarbeitung ein anderes Artefakt verwendet als Modul B. Dies ist ein wichtiger Aspekt für die Qualität von Software.

Ausblick

Nachdem Sie nun viele Details zu den Möglichkeiten von Maven kennengelernt haben, stellt der nächste und abschließende Teil dieser Serie das Eclipse-Plug-in für Maven for PHP vor und zeigt unter anderem, wie Webseiten und Reports über Maven generiert werden.


PHP meets Maven – Teil 2

[Teil 1[Teil 2] [Teil 3] [Teil 4]

Der erste Teil der Serie hat gezeigt, dass Maven den Paradigmen DRY und COC folgt. Aus diesem Grund sollte beim Anlegen eines Projekts die vorgegebene Verzeichnisstruktur eingehalten werden. Es ist durchaus möglich, von dieser Empfehlung abzuweichen, was aber zur Folge hat, dass der Konfigurationsaufwand in der pom.xml erheblich anwächst und sich schnell Fehler einschleichen können. Bild 1 stellt eine einfache Ordnerstruktur für ein typisches Maven-Projekt dar.

Die beschriebene Struktur ist bis auf wenige Abweichungen für sämtliche Maven-Projekte identisch. Der wichtigste Teil ist die pom.xml im Wurzelverzeichnis des Projekts. Das optionale Verzeichnis site enthält alle notwendigen Dateien, um eine Projekt-Homepage durch Maven generieren zu lassen. In src vermuten Sie zu Recht die verschiedensten Quelltextdateien. Im Ordner test befinden sich sämtliche Testdateien. Die resources-Verzeichnisse sind optional und besitzen eine besondere Funktion: Dort werden vor allem Konfigurationsdateien abgelegt, in denen Platzhalter zur Textersetzung eingebunden werden können. Um die Zusammenhänge schneller zu erkennen, sollten Sie einen Blick in die Konfigurationsdatei pom.xml werfen.

Project Object Model

Das Project Object Model (POM) bildet die zentrale Steuereinheit für Maven und enthält alle nötigen Informationen. Eine vollständige Übersicht der Konfigurationsmöglichkeiten kann auf der Maven-Projektwebseite nachgeschlagen werden. Beginnen wir zuerst mit den wichtigsten Einträgen:

<groupId>org.phpmaven</groupID>
<artifactId>php-libary</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>php</packaging>

Diese vier Zeilen sind der essenzielle Bestandteil einer jeden POM. Nun wird auch ersichtlich, was es mit den Platzhaltern $groupID und $artifactID in der Projektstruktur auf sich hat. Die artifactId ist der Projektname, dem eine Version zugeordnet wird. Bei der groupId handelt es sich um die Domain des Projekts.

Diese Informationen benötigt Maven, um die Artefakte im Dependency-Management organisieren zu können. Daraus ergibt sich beim Anlegen eigener Projekte die Forderung, dass die Kombination aus Domäne, Projektnamen und Version nicht mehrfach vergeben werden darf, da sonst vorhandene Artefakte im lokalen Repository ohne Rückfrage sofort überschrieben werden. Da Maven für Java-Projekte konzipiert wurde, nutzte es auch den in Java vorhandenen Mechanismus der Packages. Das bedeutet für den Wert org.phpmaven in groupId, dass Maven den Punkt als Trennzeichen interpretiert und erst org und darunter phpmaven als Verzeichnis erzeugt. Auf diese Weise können Sie Ihre Artefakte im lokalen Repository jederzeit aufspüren.

Für Java-Projekte ist es notwendig, anhand des Package-Namens, der sich aus groupId und artifactId zusammensetzt, eine Ordnerhierarchie zu erzeugen. In PHP-Projekten ist das Anlegen einer solchen Struktur nicht zwingend notwendig. Mit Blick auf künftige Mehrfachverwendung von Artefakten in den verschiedenen eigenen Projekten ist die Verwendung von Namespaces unumgänglich. Das Risiko von Namenskonflikten zwischen den Artefakten steigt mit der Anzahl der verfügbareren Artefakte.

Der Eintrag php weist Maven an, aus den Source-Files ein Phar-Archiv zu erzeugen. Es gibt neben php noch weitere Package-Typen, etwa zip und pom. Der Package-Typ pom erlaubt die Verwendung von Multi-Projekten, auf die später noch eingegangen wird. Listing 1 zeigt eine vollständige POM für ein einfaches PHP-Library-Projekt, das Sie als Abhängigkeit in anderen Projekten nutzen können. Dependencies werden in der POM im Bereich eingetragen. Im Beispiel ist das Test-Framework PHPUnit eingebunden. Die Angabe teilt Maven mit, dass es sich bei diesem Artefakt um ein PHP-Archiv handelt. Ohne diese Konfiguration würde Maven ein Java-Archiv (JAR) erwarten. Der Scope einer Dependency konfiguriert die Sichtbarkeit des Artefakts im Build Lifecycle. Es gibt vier unterschiedliche Scopes:

  • compile: Das Artefakt ist in allen Phasen verfügbar (Default).
  • provide: Das Artefakt ist nur bis zur compile-Phase sichtbar und wird nicht mit deployed. Dieser Scope ist vor allem für Java-Projekte wichtig, um die Ressourcen korrekt zu verlinken.
  • runtime: Diesen Scope verwenden Dependencies, die nur zur Laufzeit benötigt werden, was für PHP-Projekte die Regel darstellt. Artefakte, die mit runtime gekennzeichnet sind, werden mit der Anwendung zusammen deployed.
  • test: Abhängigkeiten, die mit diesem Scope konfiguriert sind, werden nicht mit deployed und sind nur für die test-Phasen des Build-Lifecycles sichtbar.
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.phpmaven</groupId></plugin>
    <artifactId>php-parent-pom</artifactId><plugin>
    <version>2.0-SNAPSHOT</version>
  </parent>
  
  <groupId>com.elmar.dott</groupId>
  <artifactId>validator</artifactId>
  <version>1.0-SNAPSHOT</version>
  <version>2.10</version>
  <packaging>php</packaging>
  
  <build>
		<plugins>
			<plugin>
				<groupId>org.phpmaven</groupId>
				<artifactId>maven-php-plugin</artifactId>
				<version>${phpmaven.plugin.version}</version>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-site-plugin</artifactId>
				<version>3.0</version>
				<inherited>true</inherited>
				<configuration>
					<reportPlugins>
					<plugin>
						<groupId>org.phpmaven</groupId>
						<artifactId>maven-php-plugin</artifactId>
						<version>${phpmaven.plugin.version}</version>
						<reportSets>
							<reportSet>
								<reports>phpdocumentor</reports>
							</reportSet>
						</reportSets>
					</plugin>

					<plugin>
						<groupId>org.phpmaven</groupId>
						<artifactId>maven-surefire-report-plugin</artifactId>
						<version>${phpmaven.plugin.version}</version>
						<reportSets>
							<reportSet>
								<reports>report-only</reports>
							</reportSet>
						</reportSets>
					</plugin>
					</reportPlugins>
				</configuration>
			</plugin>
		</plugins>
	</build>
	
	<dependencies>
		<dependency>
			<groupId>de.phpunit</groupId>
			<artifactId>PHPUnit</artifactId>
			<version>3.6.10</version>
			<type>phar</type>
			<scope>test</scope>
		</dependency>
	</dependencies>  
</project>

Nachdem Sie nun eine Projektstruktur und die dazugehörige POM haben, wird es Zeit, Maven in Aktion zu erleben. Die Idee ist, eine Bibliothek zu erzeugen, die Benutzereingaben validiert. Dieses Artefakt kann dann später von Ihnen dahingehend erweitert werden, in Ihren Projekten an verschiedensten Stellen Benutzereingaben auf Gültigkeit zu überprüfen.

Mögliche Erweiterungen könnten etwa Validierungen von ISBN- und IBAN-Nummern sein. Um ohne Verzögerung zu beginnen, können Sie das fertige Projekt von der Website herunterladen und in einer aktuellen IDE Ihrer Wahl öffnen.

Das Artefakt besteht aus der Klasse Validator mit den beiden Methoden makeSecure() und validate(). Die Methode makeSecure() soll HTML-Tags escapen und Slashes sowie Backslashes aus dem übergebenen String entfernen. validate() prüft eine Eingabe gegen einen regulären Ausdruck auf Gültigkeit. In der Klasse sind bereits einige RegEx als Konstanten definiert. Damit haben wir schon alles, um erste Resultate zu sehen.

Validierungen

Öffnen Sie eine beliebige Konsole und navigieren Sie in das Projektverzeichnis zur POM. Nun müssen sie lediglich mvn package eingeben. Diese Anweisung bewirkt, dass Maven den Build Lifecycle bis zur Phase package abarbeitet. Die nachfolgenden Phasen install und deploy werden nicht aufgerufen. Die Phase install kopiert das erzeugte Artefakt direkt in das lokale Repository und deploy würde zusätzlich das Artefakt in einem Remote-Repository ablegen. Für den Moment genügt es uns, das Phar-Archiv erzeugt zu haben.

Wegen des POM-Eintrags install unterhalb von <build> genügt es, lediglich mvn install in der Konsole zu schreiben, um das erzeugte Artefakt ins lokale Repository zu kopieren. Nachdem mvn package ausgeführt wurde, hat Maven im Projektverzeichnis einen temporären Ordner namens target erzeugt und quittiert die Ausgabe mit BUILD SUCCESS. In dem neu angelegten Verzeichnis sind sämtliche generierten Dateien abgelegt, unter anderem auch das erwünschte Phar-Archiv.

Um negative Synergien zu vermeiden, sollte das target-Verzeichnis vor einem jedem Build gelöscht werden. Das erledigt die Anweisung mvn clean.

Das erzeugte Phar-Archiv lässt sich mit den folgenden Zeilen in eine Applikation einbinden:

require_once 'phar://validator-1.0-SNAPSHOT.phar/index.php';
use de\banaalo\validator\Validator as Validator;

$validator = new Validator();
$input = 12345;

echo $validator->validate
($validator->NUMBER, $input);

Der Trick besteht darin, sämtliche Klassen des Archivs in der Datei src/main/php/index.php mittels include bekannt zu machen. Bei dieser Herangehensweise muss lediglich die index.php wie in der ersten Zeile des Listings geladen werden. Anschließend ist noch der Namespace aufzulösen und dann können die Klassen des Artefakts wie gewohnt verwendet werden.

Testgetriebene Entwicklung

Dank des Extreme Programming (XP) hat das Testen von Sourcecode mittlerweile einen sehr zentralen Stellenwert im Software-Entwicklungsprozess erhalten. Implementierte Funktionalität wird nicht erst nach Beendigung der Entwicklung auf Korrektheit hin überprüft, sondern schon während der Entwicklungsphase. Eine etablierte Form des Testens sind Unit-Tests, wofür das Framework PHPUnit von Sebastian Bergmann als Dependency in den Maven-Build-Lifecycle eingebunden ist. Die aktuell verwendete Version von PHPUnit ist 3.6.10. Ein ausführliche Übersicht zu PHPUnit bietet das Manual des Frameworks.

Sämtliche Testfälle sind im Verzeichnis test/php abzulegen. Wenn eine Verzeichnisstruktur für die Sourcen erzeugt wurde, sollte diese ebenfalls für die Testfälle übernommen werden. Eine weitere Konvention ist, die Testklassen analog der zu testenden Klassen zu benennen und das Postfix test anzufügen.

Zur Klasse Validator existiert die korrespon-ierende Klasse ValidatorTest, die von PHPUnit_Framework_TestCase abgeleitet ist. Um später in der Test-Auswertung bei Fehlern das betroffene Fragment schneller identifizieren zu können, sollten die Methoden sprechende Namen haben. Im vorgestellten Beispielprojekt wird die Validierung von Zahlen durch den Validator getestet. Dazu existiert die Methode testNumberValidation(). Um Klassen testen zu können, sollte schon während der Implementierung darauf geachtet werden, dass nur ein Einstiegspunkt und ein definierter Ausstiegspunkt vorhanden ist. Mehr als das Erstellen der Testfälle und das Einbinden von PHPUnit als Abhängigkeit ist nicht notwendig. Beim Ausführen von Maven werden nun jedes Mal alle Testfälle abgearbeitet. Falls ein Test fehlschlägt, wird der Build mit einer Fehlermeldung abgebrochen.

API-Dokumentation auf Knopfdruck

Ein weiterer wichtiger Aspekt in der SoftwareEntwicklung ist das Erzeugen einer aussagekräftigen API-Dokumentation. Für diese Aufgabe ist in Maven der Site-Lifecycle vorgesehen. Um den phpDocumentor dem Site-Lifecycle bekannt zu machen, muss die Konfiguration des site-Plug-ins überschrieben werden. In der Beispiel-POM ist dies bereits geschehen und deswegen muss die POM nicht weiter angepasst werden. Wie auch bei PHPUnit ist es nicht notwendig, das Tool phpDocumentor auf dem System zu installieren. Auf der Homepage des php Documentors ist das Werkzeug gut dokumentiert. Auf der Webseite ist unter anderem eine Übersicht der möglichen Annotationen zu finden. Um die Dokumentation zu erzeugen, muss der Site-Lifecycle mit der Anweisung mvn site ausgeführt werden.

Wie alle anderen durch Maven erzeugten Dateien wird auch die fertige API-Dokumentation im target-Verzeichnis abgelegt.

Jedes Mal aufs Neue von Hand eine Projektstruktur anzulegen ist mühsam. Damit das Erzeugen neuer Projekte automatisiert geschehen kann, bietet Maven den Mechanismus der Archetypen. Ein Archetyp erzeugt eine vorgegebene Verzeichnisstruktur und die dazugehörige POM. Für PHP existieren derzeit drei verschiedene Archetypen: library, web und zend, die im Lauf der Zeit noch erweitert werden. Der nachfolgende Code-Ausschnitt zeigt die benötigte Anweisung, um einen Archetyp auszuführen:

mvn archetype:generate \
  -DarchetypeGroupId=org.phpmaven \
  -DarchetypeArtifactId=php5-lib-archetype \
  -DarchetypeVersion=2.0.0-beta-3\
  -DgroupId=de.banaalo \
  -DartifactId=validator \
  -Dversion=1.0-SNAPSHOT

Falls Sie aus einem bereits bestehendem Projekt einen Archetyp erzeugen wollen, können Sie mvn archetype:create-from-project ausführen. Es kann auch vorkommen, dass anschließend der generierte Archtyp von Maven nicht gefunden wird. Der Grund ist in der Datei archetypecatalog.xml zu suchen. Um den neuen Archetyp in den Katalog aufzunehmen, ist die Anweisung mvn archetype:crawl auszuführen. Ein manuelles Editieren dieser Datei ist nicht notwendig.

Fazit

Library-Projekte sind Artefakte, die Funktionalitäten kapseln und diese Funktionalitäten in anderen Projektformen als Abhängigkeit zur Verfügung stellen. Im nächsten Teil der Serie wird gezeigt, wie Homepage-Projekte mit Maven verwaltet werden können, die wiederum Artefakte einbinden. In diesem Zusammenhang wird auch auf Multi-Projekte und deren Verwendung eingegangen. Sie erfahren auch, wie Sie erzeugte Artefakte für andere Entwickler in einem Remote-Repository verfügbar machen.