Archiv der Kategorie: clean code

CRUD – Theorie und Praxis

CRUD, dieses Vorgehen sollte eigentlich jeder kennen … falls nicht, CRUD ist die Kurzform für „Create, Read, Update and Delete“ und ist vereinfacht gesagt als Checkliste zu sehen und gilt nicht nur für PHP.

Du hast in deiner Anwendung zum Beispiel eine Liste aller User, die soll editierbar sein. Du kannst nun CRUD als Checkliste benutzen und einzeln abhaken:

  • Create: Kann ich neue User erzeugen? Check!
  • Read: Kann ich eine Liste aller User ausgeben UND kann ich im Editfall einen User darstellen? Check!
  • Update: Kann ich die Daten eines Users ändern? Check!
  • Delete: Kann ich einen User löschen? Check!

Hast du bei allen ein „Check!“, dann bist du technisch auf dem Mindestlevel angelangt. Herzlich Willkommen 😉

Soviel zu Theorie. In der Praxis sehe ich es leider oft, dass CRUD viel zu wörtlich genommen wird. Besonders das C und das U sehe ich oft als zwei Dinge in der Anwendung.

Warum nun ist das schlimm? Nun, ich arbeite mit Legacy-Anwendungen. Diese sind teilweise vor vielen Jahren begonnen und verrichten bis heute ihren Dienst. Innerhalb dieser Anwendungen gibt es immer wieder den Fall, dass eine Datensatzart die CRUD Bedingung erfüllen muss, d.h. ich habe eine Liste von (Beispiel) Usern oder Kunden, die kann man ändern, die kann man neu erzeugen, die kann man löschen usw.

Nun mein Appell an alle: Bitte, fasst C und U zusammen. Bitte!

Es gibt nichts schlimmeres, als dass man ein „create“ Script und ein „update“ Script hat, beide machen im Prinzip das gleiche (Prüfung der Daten, Normalisieren der Daten usw.), aber im Endeffekt hast du 2 Scripte. Du hast also „duplicate code“. Und das ist nicht nur „bad smell„, dies ist einfach nur „bad style“. Eine Änderung an der Logik der User (Stichwort neues Feld) und du musst beide Scripte aktualisieren. Falls deine Anwendung mehrere solcher Listen hat und das neue Feld mehrere betrifft darfst du die „2“ gern entsprechend faktorisieren.

Also, bitte, bau die ein „edit“ Script, prüfe die Variablen, normalisiere diese und mache alles fertig. Dann nur noch die Unterscheidung „neu“ oder „ändern“ und gut ist. Das sind aus meiner Erfahrung nur ein if-else (oder switch) und das entsprechende SQL-Kommando. Die Ausführung des Kommandos ist dann ja wieder das selbe und kann außerhalb der Verzweigung stehen.

Dieses eine Script läßt sich jetzt viel leichter skalieren, verstößt nicht mehr gegen den „copy paste detector“ und läßt dich einfach ruhiger schlafen.

Just my 2 cent ….

XDebug Logo

XDebug und Zend Server CE für Unit Tests und Code Coverage Reports

Wer Unit Testing betreibt, der will auch Code Coverage, ganz klare Sache. Für Code Coverage benötigt man in Verbindung mit phpUnit XDebug. In der Regel kein Problem.

Wer allerdings den ZendServer benutzt der war bisher angeschmiert, den XDebug im ZendServer zu konfigurieren kann nervig sein und eine falsch eingetragene Zeile kann den Betrieb des ZendServers sogar komplett lahmlegen.

Aber natürlich gibt es einen Weg, XDebug, ZendServer inkl. ZendDebugger laufen zu lassen, du bekommst also den ZendDebugger zum debuggen und XDebug zum Unit Testen deiner Anwendung, das beste aus beiden Welten – unter Windows 😉

Bereit? Los geht’s:

  1. ZendServer stoppen, entweder per Dienstkonsole oder mit „Apache Service Monitor“
  2. Lade dir die fehlende XDebug dll bei XDebug.org runter.
    Welche ist die passende?
    Zunächst erstmal die PHP Version deines ZendServers, dann den Link OHNE „TS“ und den mit 32-Bit, der sollte passen – war bisher bei allen meinen ZendServer-Installationen so.
    Beispiel mit PHP 5.4: „PHP 5.4 VC9 (32 bit)“
  3. Die .dll legst du nun im Verzeichnis
    /ZendServer/lib/phpext

    deines ZendServers ab.

  4. Nun ab in die php.ini
    Dort trägst du ziemlich weit unten aber ÜBER diesem Eintrag:

    zend_extension="C:\zend\ZendServer\lib\ZendExtensionManager.dll"

    (Pfad ist bei dir zumindest ähnlich)
    das hier ein:

    zend_extension="C:\zend\ZendServer\lib\phpext\php_xdebug-2.2.2-5.4-vc9-nts.dll"

    Natürlich ersetzt du den konkreten Namen durch deinen, bei mir ist das der ZendServer mit PHP 5.4 in der zum Veröffentlichungszeitpunkt aktuellsten Version von XDebug.

  5. Spaß mit Code Coverage!
    Ja, genau, schon fertig.

Ab nun kannst du Code Coverage Reports bekommen.

Was nicht geht: Du kannst mit XDebug (höchstwahrscheinlich) nicht wirklich debuggen – aber du hast ja den Zend Debugger, der zumindest mit dem Zend Studio wesentlich besser funktioniert.

Ich selbst habe bisher keine verlässlich funktionierende Konfiguration gefunden, mit dem ich via XDebug im Zend Server debuggen und profilieren konnte; mir reicht XDebug zum erzeugen der Code Coverages.

Vielen Dank geht an „Limespacer“ für diesen Beitrag, der mir dabei sehr geholfen hat:
http://www.limespace.de/2009/07/15/zend-server-ce-mit-xdebug/

P.S. Bei mir geht es auch mit aktiviertem Zend Debugger, falls das bei dir nicht der Fall sein sollte, dann schalte diesen wie im verlinkten Beitrag gezeigt einfach ab.

Windows Developer System – Der Webserver (02)

Kommen wir in der Reihe des Windows Developer Systems nun zum Kern eures Entwickler-Systems, dem Webserver. Es gibt verschiedene Möglichkeiten, welchen Webserver man als Entwickler benutzt. Die Haupt Zielplattform wird sicherlich immer noch der gute, alte Apache Webserver sein. Da wir als Windows-ianer aber etwas verwöhnt sind, was Installation und Konfiguration von Software angeht benutzen wir für diesen Teil den Zend Server. Der hat den Vorteil, dass er komplett fertig als „One-Click-Installer“ reinkommt und nichts kostet außer ein paar Minuten Zeit.

Zuerst begibt man sich auf die aktuelle ZendServer Downloadseite und wählt die für sich passende Installation aus. Aktuell gibt es dort den ZS für PHP 5.3 und PHP 5.4 zur Auswahl.

Die Downloadseite des Zend Servers

Es wird zum Download AFAIK kein Account benötigt, falls doch ist dieser kostenfrei und auf jeden Fall die Mühe wert.

Nachdem ihr den Server runtergeladen habt, könnt ihr die Installation wie gewohnt starten. Der Server ist wirklich einfach zu installieren. Beim Zielverzeichnis können wir der Einfachheit halber Den Pfad “C:\” wählen, der Server wird dann unter “C:\zend” installiert. Die kurzen Dateipfade werden uns später die Arbeit etwas leichter machen, wenn es darum geht, Virtuelle Hosts aufzusetzen – dazu später mehr.

Das wirklich schöne am Setup des Zend Servers ist, dass im Schritt “Custom Setup” ganz unten die Option “MySQL Server” angewählt werden kann und damit nicht nur ein MySQL Server runtergeladen wird, sondern das Ganze auch fix und fertig serviert und konfiguriert wird. Wirklich praktisch!

Custom Setup des Zend Servers

Die restlichen Optionen müsst ihr nach euren Anforderungen setzen, ich belasse meist alles so wie es ist und füge nur die Option des MySQL Server hinzu, dann habe ich schon ein funktionierendes System.

Die Installation dauert je nach Optionen, Downloadgeschwindigkeit und CPU ein paar Minuten. Zeit für einen Kaffee 😉

Nach der Installation _kann_ es sein, dass manche Rechner neu starten müssen, andere jedoch nicht. Tut wie euch geheißen und ihr bekommt anschließend den kompletten Zend Server als Trial-Verison für 30 Tage. Keine Angst, nach Ablauf der 30-Tage verwandelt sich die “echte” Zend Server in die “Free”-Version, die genauso als Entwickler-Basis verwendbar ist – nur ein paar Profi-Features fehlen, das wirklich wichtige bleibt aber erhalten.

Je nach Präferenz könnt ihr in den Diensten von Windows nun auch den automatischen Start von “Apache” und “MySQL Server” auf “Automatisch” belassen oder auf “Manuell” setzen. Ich setze den Apache meist auf Manuell und starte diesen bei Bedarf, der MySQL Server läuft dagegen ständig, da ich öfters mal Datenbankabfragen aus meiner IDE starte, während ich programmiere. Aber auch hier: Eure Bier, macht’s, wie’s euch beliebt.

In eurer System-Tray (neben der Uhr) habt ihr nun auch ein schickes neues Icon, ein Klick drauf und ihr könnt den Server starten, stoppen oder restarten wie es euch gefällt. Ein Doppelklick führt zu einer netten Übersicht, die in der Praxis aber kaum Bedeutung hat.

Ob ihr nun glaubt oder nicht, euer Webserver mitsamt MySQL-Server ist nun installiert und einsatzbereit. Yes, it’s that easy!

Wenn der Apache gestartet ist erreicht ihr die Konfigurationsseite des ZendServers unter der URL

http://localhost:10081/ZendServer/

Beim ersten Aufruf (oder nach einem Update) müsst ihr den Üblichen EULA’s zustimmen und angeben, ob ihr den Server als “Development” Server benutzt oder als “Production”, dann ein Password für den Admin-Zugang eintragen und fertig ist der Server!

Zend Server Dashboard direkt nach Installation

Im Punkt “Configurations” könnt ihr einzelne Komponenten (de-)aktivieren und auch alle Teile der php.ini bearbeiten, inkl. Erklärungen. Grade für Neulinge im Bereich Webserver sehr praktisch. Der Server kann nach allen Änderungen die Ihr braucht per Klick neu gestartet werden.

Der größte Teil unter dem Mneüpunkt “Applications” fällt übrigens nach dem Ende der Trial-Phase weg und betrifft eh nur Cloud-“Zeug”, was erstmal nicht von belang sein soll. Wir wollen ja unser lokales System aufbauen und die Grundlage dafür ist nun vorhanden.

Schaut euch also nun in Ruhe euren neuen lokalen Webserver an. Im nächsten Teil geht es weiter mit der Konfiguration der Virtuellen Hosts und was man damit schönes machen kann.

php Codesniffer und CI , Zeilenlänge und Windows Zeilenumbruch setzen

Windows, Continous Integration, 16:10 Bildschirme und phpcs.

Dies alles passt im Standard nicht zusammen, zumindest nicht mit unseren internen Regeln, daher hier die notwendigen Anpassungen, um zumindest die Zeilenlänge auf ein erträgliches Maß von 120 zu 160 zu bekommen (nicht mit dem Blutdruck verwechseln) und den Codesniffer an Windows-like Zeilenumbrüche von \r\n zu gewöhnen.

Meine phpcs.xml sieht dann in etwa so aus:

<?xml version="1.0"?>
<ruleset name="PHPCS Rules">
 <description>Using Zend Rules</description>
 <rule ref="Zend">
  <exclude name="Generic.WhiteSpace.DisallowTabIndent" />
  <exclude name="Generic.Functions.OpeningFunctionBraceBsdAllman" />
  <exclude name="Generic.PHP.DisallowShortOpenTag" />
  <exclude name="Generic.Files.LineEndings" />
 </rule>
 <rule ref="Generic.Functions.OpeningFunctionBraceKernighanRitchie" />
 <rule ref="Generic.Files.LineLength">
  <properties>
   <property name="lineLimit" value="120"/>
   <property name="absoluteLineLimit" value="160"/>
  </properties>
 </rule>
</ruleset>

Windows Developer System – Die Grundlagen (01)

Ich stelle euch hier vor, wie ihr Stück für Stück eine professionelle PHP-Entwicklungsumgebung unter Windows erstellt und das ganze ohne besonders viel Aufwand. Die meisten Tutorials in dieser Richtung – zumindest die, die ich kenne – gehen immer davon aus, dass ihr Linux benutzt – und wenn doch mal Windows erwähnt wird, dann meist mit dem Seitenhieb, doch mal endlich ein „richtiges“ Betriebssystem zu benutzen.

Ich selbst habe 2 Maschinen, beide mit Windows7, und dieses Tutorial basiert daher natürlich auf dem Betriebssystem Windows. Ihr könnt aber davon ausgehen, dass ihr, wenn ihr unter Windows entwickeln wollt, keine Einschränkungen gegenüber den Linux-Leuten habt, ihr habt nur andere Kommandos – auch wenn die Pinguine euch manchmal was anderes erzählen wollen. Am Ende ist doch eh nur der Code wichtig und der läuft sowohl unter Windows wie unter Linux.

Anfangen möchte ich mit einem Überblick und nötigen Grundlagen, alles weitere kommt dann Stück für Stück. In jedem Teil findet sich ein Inhaltsverzeichnis mit den jeweils anderen Folgen dieser Serie.

Auf jeden Fall wünsch ich euch viel Spaß beim lesen und viel Erfolg beim Umsetzen.

Inhaltsverzeichnis:

Windows Developer System – Die Grundlagen

Zuallererst brauchen wir natürlich ein Windows, welches auch immer. Es sollte im Prinzip auf jedem Windows ab XP funktionieren, testen kann ich – wie oben schon erwähnt – nur auf Win7; Kommentare zu anderen Windows-Varianten wie z.B. Vista, XP oder sogar Win2000 wären nett 😉

Linuxianer oder Mac-User dürfen natürlich gern mitlesen. Bei den Anleitungen zur Installation werde ich aber gnadenlos Windows-lastig sein, dafür könnte sich das Mitlesen bei der Benutzung von PEAR und den damit verbundenen Code-Analyse-Tools aber lohnen. Mein Tipp: Dabeilesen ist alles 😉

Eure Festplatte sollte noch ein paar MB oder besser GB Platz haben, natürlich vor allem für die „große“ Software wie den Webserver und die lokale Datenbank, als auch für eine professionelle IDE … die Puristen unter euch können natürlich auch weiterhin im Notepad2, Notepad++ oder dem Programmers Notepad arbeiten; viele Features einer IDE können einem Entwickler in der täglichen Arbeit allerdings sehr viel lästige Arbeit abnehmen (siehe die 10 Regeln für Entwickler – Punkt 3).

Die Frage an dieser Stelle lautet: „Warum sollte ich das machen?“. Die Antwort ist trivial: Weil es deine Arbeit verbessert und erleichtert, weil es dir viele Möglichkeiten gibt, dich selbst und deine Fähigkeiten zu verbessern. Und bestimmt noch mehrere Dutzend Antworten, die dir aber auch alle im Laufe dieses Tutorials klar werden. Darum!

Von hier aus geht es nun also nahtlos weiter zum nächsten Punkt, dem downloaden und installieren des lokalen Webservers.

Method-Chaining in eigenen PHP-Projekten einsetzen

Klassen und Objekte kennt ja nun mittlerweile fast jeder, ein PHP-Framework wie ZendFramework oder CodeIgniter haben sich sicherlich auch schon viele zumindest mal angesehen … will ich hoffen. Nun, auch wenn man die Frameworks nicht direkt einsetzt, so kann man doch viel von ihnen lernen. Viele der Techniken, die in bekannten PHP-Frameworks eingesetzt werden, entstanden entweder aus dem großen Ästhetischen Faible, den nun mar jeder Entwickler hat oder schlicht aus Faulheit der Programmierer 😉

Egal, eines der – wie ich finde – tollen Features in vielen Frameworks ist die Möglichkeit, mehrere Funktionen eines Objektes hintereinander aufrufen zu können, ja, es sieht schon fast wie ein normaler Satz aus, was dann im Editor steht und seinen Dienst verrichtet. Diese Möglichkeit der sog. “Fluent Interfaces” nennt man “Method-Chaining” (und wieder im Bullshot-Bingo gewonnen, *strike*).

Kleines Beispiel, kennen wir alle, unsere Basisklasse:

class Base1 {
function macheEins() {
// some magic here
}
function macheZwei($parameter) {
// more magic here
}
}

Das ganze rufe ich nun auf:

$myBaseClass = new Base1();
$result1 = $myBaseClass->macheEins();
$result2 = $myBaseClass->macheZwei($result1);

Was passiert? Das Ergebnis des Aufrufs von “macheEins” ist der Parameter für “macheZwei”. Das ganze sieht strukturiert aus und ist lesbar, was also soll man besser machen können? Nein! Bitte jetzt nicht sagen, man könne doch sowas machen:

$myBaseClass = new Base1();
$result2 = $myBaseClass->macheZwei($myBaseClass->macheEins());

Sicher, es funktioniert, aber: NEIN! Macht das nicht! Warum? Ganz einfach: Debugge das mal, viel Spaß. Denn wenn man erst einmal mit so einem falschen Verhalten anfängt, dann verschachteln sich schnell auch mal 5 oder 8 Funktionen ineinander und finde dann mal den Fehler, viel Spaß! Also: Ganz klares “So nicht!”.

Aber wie dann?

Zunächst müssen uns im klaren sein, was die Klasse macht. Offenbar braucht “macheZwei” ein Ergebnis einer Berechnung einer anderen Funktion der Klasse. Dieses Ergebnis könnte man doch genauso innerhalb der Klasse speichern und dann benutzen.

class Base2 {
private $valueHolder;
function macheEins() {
$this->valueHolder = someMagic;
return $this->valueHolder;
}
function macheZwei() {
return $this->valueHolder * someMoreMagic;
}
}

Schon erfüllt die Klasse auch die Anforderungen, aber … so richtig “fluent” will das ganze nicht werden, obwohl ja nun im Aufruf von “macheZwei” der Übergabeparameter fehlt. Was nun? Und wie sieht denn so ein “Fluent Interface” mit “Method-Chaining” nun aus? Der Aufruf sähe in unserem Beispiel in etwa so aus:

$myBaseClass = new BaseFluent();
$result2 = $myBaseClass->macheEins()->macheZwei();

Aber unsere derzeitige Klasse unterstützt das nicht! Wie bekommen wir unsere Klasse nun “Fluent”?

Nun, dazu müssen wir diese massiv umbauen. Der größte Umbau ist, dass die einzelnen Methoden nicht mehr direkt die Ergebnisse liefern, sondern “nur” das Objekt selbst zurückgeben … und darin liegt auch schon der ganze Trick. Den Methodenaufruf kann ich immer nur auf einem Objekt machen. Eine Methode, die mir einen Basisdatentypen zurück liefert, kann ich dafür nicht gebrauchen, da ich auf diesem Basistypen (int, String, array, …) keine weiteren Methoden meiner Klasse aufrufen kann.

Nehmen wir mal an, im letzten Code stünde statt “new BaseFluent” ein “new Base2”. Dann würde der Aufruf:

$result2 = $myBaseClass->macheEins()->macheZwei();

folgendes bedeuten: Rufe die Methode “macheEins” auf dem Objekt “myBaseClass” auf, diese gibt den Datentyp von valueHolder zurück (nehmen wir mal an, es wäre ein integer mit dem rein zufälligen Wert 42), rufe dann auf dem Objekt 42 die Methode “macheZwei” auf … *meep* Fehlermeldung, “42” ist kein Objekt, hat daher generell keine Methoden und erst recht keine spezielle Methode mit dem Namen “macheZwei” also Fehler und Script Abbruch.

Der Trick besteht nun darin, dass die Methoden des Objektes nicht mehr die eigentlichen Ergebnisse zurückgeben, sondern das Objekt der Klasse selbst; darauf darf man ja dann auch wieder Methoden derselben Klasse aufrufen, also bauen wir flugs die Klasse um:

class BaseFluent {
private $valueHolder;
function macheEins() {
$this->valueHolder = someMagic;
return $this;
}
function macheZwei() {
$this->valueHolder *= someMoreMagic;
return $this;
}
function getValueHolder() {
return $this->valueHolder;
}
}

Man erkennt nun, wohin die Reise geht. Getter und Setter werden implementiert, um die Daten zu holen, die Methoden geben uns $this zurück, worauf wir weiterhin Klassenmethoden aufrufen können und wir haben nun unser “Fluent Interface” für “Method-Chaining” in PHP realisiert; war doch gar nicht schwer und hat auch gar nicht weh getan, oder?

In der freien Wildbahn trefft ihr auf diese Art des Codens übrigens ganz stark beim ZendFramework an, bei CodeIgniter geht es wohl auch, denke ich (ich mache mich da erst seit kurzem fit und bitte alle CI-Fans, meine Unwissenheit zu entschuldigen). Es macht aber auch Spaß, dass bei eigenen Klassen umzusetzen, die sowieso umgearbeitet werden sollen. Sieht einfach viel übersichtlicher aus. Und noch ein Tipp: Mehr als zwei Verkettungen sollten untereinander stehen, also so:


$myBaseClass->macheEins()
->macheZwei()
->macheDrei()
->undNochMehr();

Viel Erfolg damit …

KISS – oder wie umständlich muss es noch werden?

Passend zum DRY-Artikel nun der zum Thema KISS. Nein, nicht die Rock-Band-KISS, sondern ein Prinzip der Softwareentwicklung, das besagt: „Mache es so einfach wie möglich!“.

Immer wieder sehe ich Code, der akademisch bestimmt mit Note 1 ausgezeichnet wird. In Java, C++, Delphi oder anderen compilierenden Sprachen hat das ganze auch durch seinen Sinn und seine Daseinsberechtigung. Aber in PHP sieht die Sache anders aus.

PHP ist eine Interpretersprache und wird es auf absehbare Zeit auch erstmal bleiben. Interpretersprachen haben im Grundprinzip eines gemeinsam: Sie durchlaufen den Code während der Ausführung Zeile für Zeile, Schritt für Schritt. Und genau da liegt der Schwachpunkt: Je mehr Zeilen ein Code hat, desto länger braucht der Interpreter, den Code zu durchlaufen. Je länger der Interpreter braucht, umso größer wird die „Time-to-Response„, also diejenige Zeit, die zwischen Anforderung an den Server (Aufruf der Seite) und Antwort des Servers (Auslieferung der Seite, also des generierten Quelltextes) liegt. Und diese Zeit sollte „as small as possible“ sein (wer wartet schon gern auf seine Seite).

Dieses Problem potenziert sich meist, da der normale User erstmal eine (allgemeine) Startseite ansurft und sich dann durch die Seite bewegt; dabei ruft er meist spezialisierte Seiten mit mehr Aufgaben für den Server auf. Mehr Aufgaben, mehr Inhalt, mehr Code, länger warten, nicht gut!

Warum nun das ganze hier im Zusammenhang mit PHP?
In PHP lassen sich selbstverständlich schöne Objektorientierte Konstrukte bilden, die auch von vielen Seiten als „richtig“ proklamiert werden. Ein Beipiel sei dies (in Pseudocode, nicht wundern):



holeBenutzerdaten();
// mach was damit


// ab hier nun die funktionen, diese liegen alle
// in unterschiedlichen klassen, dateien usw.

function holeBenutzerdaten()
{
prüfeEingabe();
ladeBenutzerdaten();
return;
}

function prüfeEingabe()
{
prüfeSpezifischeEingabe;
ladeIrgendwasUnwichtiges;
prüfeNochmal;
}

function prüfeSpezifischeEingabe()
{
prüfeObEingabeValideIst
ja: prüfeDieEingabeNochmalMitWasAnderem
nein: ladeFehlerbehandlung; gibFehlerAnFehlersystem; zeigeFehler;
}

// So, und nun noch 20 Dateien mehr und ca. 60 Funktionsaufrufe

Nicht schön, denn hier wird, anstatt das Problem zu erledigen, von einer Funktion zur nächsten gesprungen. Unschön dabei: Jede Zeile kostet Zeit und die gilt es in einem guten Projekt so gering wie nur möglich zu halten.

Es gilt das Prinzip: So viel wie nötig, so wenig wie möglich! Nicht mehr, nicht weniger. Dazu aber noch später mehr.

Und nun Beispiel 2: Die reine Lehre sagt, dass veränderliche Werte in Variablen abgelegt werden sollen und diese Variablen dann an einen einheitlichen Ort. Nennen wir das mal „Config-Dateien“, dann kann sich jeder was drunter vorstellen. Diese Config-Files kennen wir alle, meist stehen da mindestens die MySQL Zugangsdaten drin, manchmal auch noch was anderes, zumeist belangloses. Das ist soweit auch gut und richtig und sollte – in einem gewissen Rahmen – auch gemacht werden … allerdings sollte man es dabei nicht übertreiben.

In diesem Beispiel möchte ich zeigen, wie man – unter dem Aspekt des Konfigurierens und Auslagerns – eine ganz simple Sache sehr kompliziert machen kann … und dafür in gewissen Kreisen sicherlich noch großen Beifall ernten wird.

Urspung des ganzen ist ein Aufruf, ähnlich wie dieser:


mysql_query("SELECT * FROM user");

Warum schreibt nun in die Config-Dateien nicht rein, wie die Tabellen heißen und benutzt dann nicht den statischen Wert, sondern die Variable? Und warum nehmen wir dann nicht eine Funktion? Die kann man dann immerhin noch mit Unit Tests auf ihre korrekte Funktion testen!? Das sieht dann so aus:


public function getTbluser() { return 'meineUserTabelleInMysql'; }

und die kann man dann so nutzen:


mysql_query("SELECT * FROM ".getTbluser());

Toll, oder? Ändert sich nun der Tabellenname, dann muss man das nur an einer Stelle ändern und gut ist. Kommt doch dem DRY-Prinzip zugute.
Noch besser wäre es doch, wenn wir nun auch alle Feldnamen in festlegen … weil … die können sich ja auch ändern. Also los, Feldnamen auch. *tipptipptipp* Toll. Ach ja, die Variablennamen könnten sich ja mal ändern. Also auch die rein und per $$ eingebungen, damit es auch geht.

Und der Code sieht dann doch gleich viel … übersichtlicher aus:


mysql_query("SELECT ".getTblUserField1().",".getTblUserField2()." FROM ".getTblUser()." WHERE ".getTblUserFieldSort()." = $$configFileTblUserFeldSort");

Super, oder? Kommt man nun noch dazu, diese ganzen, sich ständig verändernden SQL Kommandos noch zu ersetzen, dann hat man doch ein völlig einfach konfigurierbares System vor sich, oder etwa nicht?

Ihr merkt es schon, ich drifte ins Zynische ab – aber nur gaaanz leicht, das mag daran liegen, dass ich von diesem Code in letzter Zeit zu viel gesehen habe – , nochmal also zur Klarstellung:
Nein, so macht man es nicht!

Warum nicht? Dazu gibt es gleich mehrere Punkte.
Zum einen kann man es nicht mehr lesen. Das ist ein sehr wichtiger Punkt, unterschätzt das nicht, denn zum einen sitzt ihr nicht bis in alle Ewigkeit an einem Code – nichts ist schlimmer, als nach Monaten wieder alten Code lesen zu müssen und nicht zu verstehen – und zum anderen werdet ihr an der Qualität eures Codes auch innerhalb der Entwicklungsabteilung gemessen! Schwer verständlichen Code zu schreiben mag einen Marketing-Menschen noch leicht beeindrucken können, ein guter Programmierer dagegen wird euch – wenn ihr Glück habt nur leise – verfluchen.

Zum anderen muss der Interpreter jedesmal wieder in die entsprechende Funktion springen und das braucht Zeit. Nein, viel ist das nicht, wenn das nur eine Tabelle ist, nur ganz, ganz wenig Zeit, wirklich. Allerdings: Hat man erst so eine schöne Funktion, die einem die entsprechenden Tabellen und Felder referenziert, dann benutzt man das doch nicht nur für die Abfrage selbst, nein, auch im Code kommen dann anstelle der Feldnamen die Funktionsnamen vor.


$res = mysql_query("SELECT ".getTblUserField1().",".getTblUserField2()." FROM ".getTblUser()." WHERE ".getTblUserFieldSort()." = $$configFileTblUserFeldSort");
$name = $res[getTblUserField1()];
// anstelle von
// $name = $res['name'];

(ja, ich weiß, dass da ein mysql_fetch_irgendwas fehlt)

Und das für jede Tabellenabfrage im Code … und jedes Feld … in jeder Abfrage … und dazu noch in jedem Kontrollkonstukt im Code, also jeder for-Schleife, jeder Ausgabe, in jedem if, in jedem switch und und und … Leute, das summiert sich. Da kommen schnell dutzende Funktionssprünge vor und das sind Zeiten, die nicht sein müssen!

Warum sollte meine Anwendung langsamer sein, als es sein müsste. Die wird von ganz allein langsamer, sobald nämlich immer mehr Benutzer gleichzeitig was wollen, dann summieren sich die Zeiten nicht nur, nein, die multiplizieren sich mit jedem Request! Also, jeder Flaschenhals ist es wert, dass er refaktorisiert wird. Warum nicht gleich von Anfang an alles richtig machen?

Und mal ehrlich: Wie oft ändern sich die Tabellennamen und man ihr als Entwickler müsst wirklich nur die Configs ändern? Na, ehrlich, komm *inDieSeiteStubs* Na also, noch nie gesehen, jede Umstellung der Tabellennamen hatte schon immer eine mehrstündige Umstellungsphase begründet … zusammen mit dem Verlust einiger wichtiger Gramme Körpergewicht 😉

Und hatten wir nicht eben erst erwähnt, wie wichtig die Lesbarkeit des Codes ist? Sicher, ihr habt das Projekt grade erst begonnen und wisst noch alles aus dem Kopf. Aber, wie gesagt, ihr seit meist nicht allein an einem Projekt und – selbst wenn – auch nicht immer „am Stück“ dabei. Euer Kollege würde gern wissen, warum ein bestimmter Request langsam ist oder gar nicht funktioniert und schaut im Code noch und trifft auf so einen Query. Nun muss erstmal mühsam der eigentliche, der „echte“ Query zusammengebaut werden, denn der Kollege hat keine Ahnung, welche Felder ihr ansprecht.

Aber der Tabellenname könnte sich doch mal ändern!
Warum sollte sich der Tabellenname ändern? Ja, auf Shared-Hosting-System kommt es schon mal vor, dass man in einen Webspace viele Webanwendungen packt und da kann es auch vorkommen, dass mehrere Anwendungen ihre Benutzertabelle eben ‚user‘ nennen. Warum auch nicht, es ist der passendste Name dafür! In solchen Fällen – wenn ich also weiß, dass so eine Situation eintreten kann – benutze ich die Option eines Praefix, der jedem Tabellennamen vorgestellt wird und an zentraler Stelle definiert ist.


// Im Config-File
define('DB_PRAEFIX','myPraefix_');

// Im Code
mysql_query("SELECT * FROM ".DB_PRAEFIX."user");

Das hat den Vorteil, dass man den SQL immer noch lesen kann und auch die Tabelle auf dem DB-Server wiederfindet, auch wenn man keine Kenntnis über den Inhalt von DB_PRAEFIX hat. Der Code bleibt lesbar und so ziemlich jeder Entwickler weiß auch, warum ihr das macht.

DAS ist es zum Beispiel, was dass KISS Prinzip sagt: Macht es einfach! Nicht nur euch, sondern auch anderen. Nein, ihr sollt nun dabei nicht vergessen, Sicherheitsmechanismen einzubauen oder Usereingaben zu filtern, wir wollen auch nicht zurück ins PHP3 Monolithen-Zeitlalter, aber dazu bedarf es keiner Funktion, die eine Funktion aufruft, die eine Funktion aufruft, die wiederum … ihr ahnt es schon.

Meine KISS Anforderungen sind ungefähr diese

  • – Die Gesamttiefe der Funktionsaufrufe darf 3 Ebenen nicht überschreiten (die aufgerufene Seite nicht eingeschlossen). Je weniger, desto besser.
  • – Funktionen sollten so wenig wie nötig aufgerufen werden
  • – Funktionen sollten etwas machen, nicht nur Namen liefern (return ‚tbluser‘; )
  • – Funktionen sollten das machen, wofür sie da sind. Nicht mehr!

„Klar“, sagt nun jeder, „aber wie bekomme ich dies oder das dann hin, das geht dann gar nicht mehr“.
Bei vielen dieser Fragen kann man sagen, es ist die berühmte Ausnahme von der Regel und sicherlich ist hier und da eine Ausnahme sicherlich ratsam. Man soll schließlich das ganze gerede und die ganzen schönen Buzzwords nicht als „In-Stein-gemeisselt“ verstehen. Leider zeigen viele dieser Ausrufe aber auch, wie groß die Abhängigkeit von bestimmten Frameworks oder Programmierparadigma ist.

Ich will hier keine Diskussion auslösen über Sinn und Unsinn von Frameworks oder Paradigmen, ich zeige – wie so oft in der Softwarearchitektur – das Optimum. Der Rest liegt bei euch, der Weg ist das Ziel 😉

Über eure konstruktive Meinung dazu würde ich sehr freuen – obwohl ich mehr mit empörten Kommentaren rechne *schnellUnterDenTischDuck*!

DRY – redundante Funktionen in PHP vermeiden

DRY – Don’t repeat yourself.
Das kennt jeder, das _sollte_ jeder wissen. Und so gut wie jeder, der etwas Ahnung hat, versucht es so oft es geht umzusetzen.

Allerdings: Das gilt nicht nur für Funktionsnamen, das DRY-Prinzip geht weiter. Es meint IMHO auch, dass man für viele ähnliche Abfragen sich am besten Wrapper baut, die dann die Anfrage an eine Funktion leiten, die die eigentliche Antwort produziert. Die Wrapper „füttern“ die eigentliche Funktion nur mit den richtigen Fragen.

Ein Beispiel: Siggi Scriptkid hat ein Klasse zur Benutzerverwaltung geschrieben, die braucht er in seinem aktuellen Projekt. Er speichert seine Benutzerdaten in einer MySQL Datenbank und muss nun seiner Klasse folgende Fragen stellen und erwartet folgende Antworten:

  • Hole mir den Benutzer mit der ID 5
  • Hole mir den Benutzer mit der Mailadresse xyz
  • (noch viele weitere, ähnliche Fragen mehr)

Siggi schreibt also ganz fleißig Funktionen in seine Klasse:
(Stark vereinfacht, es fehlen Filtern der Usereingaben, maskieren der SQL String und vieles mehr; der Code dient nur als Beispiel!)


class User
{
public function getUserByID($userid)
{
$ressource = mysql_query('SELECT vorname, name
FROM user
WHERE userid = '.$userid);
$user = mysql_fetch_assoc($ressource);
return $user;
}

public function getUserByMailadress($email)
{
$ressource = mysql_query('SELECT ID, mail, userlevel
FROM user
WHERE mail = "'.$email.'"');
$user = mysql_fetch_assoc($ressource);
return $user;
}
}

Nun trifft man auf folgendes Problem: Die Felder der jeweiligen Rückgabe sind unterschiedlich. Auch wenn Siggi nun hingeht, und in beiden Funktionen die Felder gleich setzt, ergeben sich Wartungsschwierigkeiten:

  • Was ist, wenn noch mehr, ähnliche Funktionen hinzukommen? Dann bekommt Siggi wirklich Arbeit!
  • Was ist, wenn Siggi der „user“ Tabelle noch mehr Felder hinzufügen muss und es 6 Funktionen gibt?
  • Was ist, wenn Siggi auch nur eine der Funktionen vergisst?

Um es kurz zu machen: Hier greift das DRY-Prinzip NICHT! Der o.a. Code sollte einem Entwickler, der nicht Siggi Scriptkid heißen möchte, nicht passieren.

Aber wie wende ich das DRY-Prinzip nun an?
Okay, ich schreibe mal meinen Vorschlag dazu:


class User
{
public function getUserByID($userid)
{
return $this->getUser('ID='.$userid);
}

public function getUserByMailadress($email)
{
return $this->getUser('mail='.$email);
}

private function getUser($where)
{
$ressource = mysql_query('SELECT ID, vorname, name, mail, userlevel
FROM user
WHERE '.$where.'
LIMIT 1);
$user = mysql_fetch_assoc($ressource);
return $user;
}
}

Ich schaffe eine private Methode, die die Daten aus der Datenbank holt. Diese ist die einzige Methode, die das macht, somit habe ich nur eine Stelle, in der ich zukünftig Felder ändern muss, wenn sich welche ändern.

Die „alten“ Funktionen verbleiben als Wrapper-Funktionen und übergeben der privaten Funktion nur die richtigen Parameter, um ihre eigene Aufgabe erfüllen zu können. Dann leiten Sie Antwort einfach als eigene wieder zurück.

Auf diesem Wege verinfache ich die Pflege, bin in der Lage, meine alten Funktionen weiterhin nutzen zu können – mit dem Vorteil, dass nun alle Antworten einheitlich sind – und bin darüberhinaus in der Lage, sehr schnell verschiedene neue Funktionen zu schreiben, die mit neuen Fragestellungen klarkommen, z.B. Benutzer mit E-Mail x und Passwort y.

Was haltet ihr von dieser Vorgehensweise. Ich selbst halte sie für praktiabel und gut, was meint ihr? Gut? Schlecht? Gibt es bessere Vorgehensweisen?