Trenne Ausgabe und Logik

Die Trennung von Ausgabe und Logik ist ein Grundsatz den man beherzigen sollte. Viele CMS bieten Template-Engines an um dies zu erreichen. WordPress bildet in diesen Punkt eine Ausnahme. Es bringt keine eigene Template-Engine mit, sondern setzt auf PHP als Template-Engine. Grundsätzlich keine schlechte Idee, denn PHP ist ursprünglich eine Template-Sprache. Aber die Möglichkeiten die PHP bietet führen grundsätzlich dazu das Logik und Ausgabe munter miteinander vermischt werden, was wiederum zu arg unleserlichen und vor allem unflexiblen Code führt.
In diesen Artikel geht es nicht ausschließlich um WordPress. Das hier gezeigte lässt sich aber sehr leicht auf WordPress anpassen und viele WordPress-Themes haben es bitter nötig sich hinter der Fassade auch mal etwas aufzuhübschen.

Ich hatte in meinem letzten Artikel bereits kurz angesprochen das es eine gute Idee ist Logik und Ausgabe voneinander zu trennen. Eins der besten Argumente für die Trennung von Logik und Ausgabe ist wohl das, dass man einmal geschriebenen Code erneut verwenden kann, man spart sich so viel Arbeit. Aber auch die Übersichtlichkeit steigt, was z.B. die Fehlersuche vereinfacht. Und nicht zu Letzt ist es leichter im Team zu arbeiten. So kann sich einer um die Logik kümmern während der andere sich ausschließlich um das Design (Ausgabe) kümmert.

Das hier im Artikel beschriebene dürfte für PHP-Profis nichts neues sein. Für den einen oder anderen meiner Leser (sofern vorhanden ;) ) kann es aber durchaus interessant sein. Fangen wir also mal ganz locker an.

Ran an den Speck

Ziel soll es sein das in der einen Datei ein Template (Vorlage) steht, während in einer anderen Datei dieses Template mit Daten gefüllt wird. Will man die Trennung noch weiter voran treiben, so legt man weitere Dateien an die sich z.B. um die Beschaffung der Daten kümmern oder um die Ausgabe der Daten (HTML, RSS, Druck, usw.).
Wir wollen uns aber mal darauf beschränken ein einfaches  Template mit einem ebenso einfachem Array zu füllen. Bei dem Template soll es sich um eine Liste handeln. In WordPress zum Beispiel ist fast alles eine Liste. Ob nun die Artikel auf der Startseite, die Blogroll, eine Aufzählung der letzten Kommentare, Menüs, usw. Eigentlich besteht WordPress zu gefühlten 90% aus Listen. Listen dürften als Beispiel also ganz brauchbar sein.
Schauen wir uns mal kurz die Anatomie einer Liste in HTML an. Sie besteht im Prinzip aus zwei Teilen. Den äußeren Teil der die Liste definiert und den inneren Teil der die eigentliche Liste darstellt. In HTML gibt es drei native Listentypen: <ol> (Ordered List), <ul> (Unordered List) und <dl> (Defenition List). Da die <dl>-Liste etwas komplexer ist nehmen wir uns mal die <ol>- und <ul>-Listen vor. Als “Ersatz” für die <dl>-Liste möchte ich eine einfache <div>-Liste dazu nehmen. Die <div>-Liste besteht aus einem äußeren Div-Container und <p>-Tags für die Listenelemente.

Nachdem nun klar ist um welche Listen es geht, brauchen wir noch ein paar Daten für die Liste. Die meisten Daten werden uns als Array oder Objekt zur Verfügung gestellt. Wir können ja mal annehmen wir hätten gerade eine Datenbankabfrage gemacht und wollten nun das Ergebnis als Liste darstellen. Der Einfachheit halber begnügen wir uns mit einem eindimensionalen Array das lediglich die Zahlwörter von Eins bis Drei enthält.

Aus Daten wird eine Liste

Das Problem ein Array als Liste darzustellen ist recht einfach zu lösen. Äußeren Teil ausgeben, das Array durchlaufen und den inneren Teil erstellen, inneren Teil in den Äußern Teil einbetten und fertig ist die Liste.

<?php
require_once 'class-formatter.php';

class Lister extends Formatter
{
public function get_list( $data = array() ){

$inner = new stdClass();
$values = new stdClass();

foreach( $data as $key => $value ){

$values->key = $key;
$values->item = $value;

$inner->inner .= self::sprintf( '<li>%item%', $values );
}

return self::sprintf( '<ol>%inner%</ol>', $inner );

}

}

$data = array( 'Eins', 'Zwei', 'Drei' );

$list = new Lister();
echo $list->get_list( $data );

view raw file1.php This Gist is brought to you using Simple Gist Embed.

Um es gleich ordentlich zu machen verwende ich eine Klasse. Diese Klasse erweitert den Formatter den ich bereits vorgestellt habe und erbt dadurch die Methoden printf() und sprintf(). Zu der Verarbeitung der Key-Value-Paare in der Foreach-Schleife komme ich später noch.

Wenn wir jetzt aber anstatt der Ordered List eine Unordered List erstellen wollen, schreiben wir uns eine neue Klasse!? Das wäre ja nicht Sinn der Aktion, wir wollen ja eine flexible Lösung haben.
Es fehlt die Möglichkeit einen Listen-Typ auszuwählen. Was dafür geändert werden muss, sind die Template-Strings mit dem HTML-Code. Die einfachste Methode um die Templates für verschiedene Listen-Typen zu speichern ist ein Array. Wir benötigen also ein Array das zu jeden Listen-Typ (ol, ul und div) zwei Template-Strings speichert (den inneren Teil und den äußeren Teil).

<?php
require_once 'class-formatter.php';

class Lister extends Formatter
{
public $templates = array();

public function __construct(){

$this->templates = $this->get_templates();

}

public function get_templates(){

$this->templates = array(
'ol' => array(
'outer' => "<ol>n%inner%</ol>n",
'inner' => "<li>%item%</li>n"
),

'ul' => array(
'outer' => "<ul>n%inner%</ul>n",
'inner' => "<li>%item%</li>n"
),

'div' => array(
'outer' => "<div>n%inner%</div>n",
'inner' => "<p>%item%</p>n"
),
);

}

public function get_list( $type = '', $data = array() ){

// get templates if not already set
if( empty( $this->templates ) )
$this->get_templates();

// check if the requested list-typ is defined
if( ! in_array( $type, array_keys( $this->templates ) ) )
return FALSE;

// create list
$inner = new stdClass();
$values = new stdClass();

foreach( $data as $key => $value ){

$values->key = $key;
$values->item = $value;

$inner->inner .= self::sprintf( $this->templates[$type]['inner'], $values );
}

return self::sprintf( $this->templates[$type]['outer'], $inner );

}

}

$data = array( 'Eins', 'Zwei', 'Drei' );

$list = new Lister();
echo $list->get_list( 'ol', $data );
echo $list->get_list( 'ul', $data );
echo $list->get_list( 'div', $data );

view raw file1.php This Gist is brought to you using Simple Gist Embed.

Wunderbar. Jetzt können wir ganz einfach durch Angabe eines Listen-Types und eines Arrays eine Liste erzeugen. Das spart uns schon mal viel Arbeit, denn anstatt für jede Liste eine eigene Foreach-Schleife zu schreiben, benutzen wir einfach die Instanz unserer Klasse.
Und wenn wir jetzt der Liste noch eine CSS-Klasse mitgeben wollen, dann schreiben wir uns eine andere Klasse die das kann!? Vielleicht erweitern wir einfach unsere Klasse dahingehend das sie nicht so starr ist.

Noch mehr Flexibilität

Die fest eingebauten Templates sind zu starr für unsere Zwecke, irgendwie müssen die raus aus der Klasse. Zu Anfang habe ich ja schon geschrieben das die Templates in die eine Datei, die Klasse die aus den Templates fertiges HTML macht in eine andere Datei soll. Das die Klasse oben einen Konstruktor hat, hat seinen Sinn. Denn das ist unser nächster Ansatzpunkt.
Wir schieben einfach die Methode get_templates() in eine Template-Klasse und übergeben der Lister-Klasse ein Objekt das aus der Template-Klasse erzeugt wurde. Damit können wir innerhalb der Lister-Klasse auf die Templates zugreifen. Alles klar soweit? Nicht? Einfach das Beispiel anschauen.

<?php
require_once 'class-formatter.php';
require_once 'class-templates.php';

interface Templates
{
public function get_templates();
}

class Lister extends Formatter
{
public $templates_object = NULL;

public $templates = array();

public function __construct( Templates $templates ){

$this->templates_object = &$templates;

$this->get_templates();

}
protected function get_templates(){

if( NULL === $this->templates_object )
throw new Exception( 'No templates defined' );

$this->templates = &$this->templates_object->get_templates();

}

public function get_list( $type = '', $data = array() ){

// get templates if not already set
if( empty( $this->templates ) )
$this->get_templates();

// check if the requested list-typ is defined
if( ! in_array( $type, array_keys( $this->templates ) ) )
return FALSE;

// create list
$inner = new stdClass();
$values = new stdClass();

foreach( $data as $key => $value ){

$values->key = $key;
$values->item = $value;

$inner->inner .= self::sprintf( $this->templates[$type]['inner'], $values );
}

return self::sprintf( $this->templates[$type]['outer'], $inner );

}

}

$data = array( 'Eins', 'Zwei', 'Drei' );

$list = new Lister( new Simple_List_Templates );

echo $list->get_list( 'ol', $data );
echo $list->get_list( 'ul', $data );
echo $list->get_list( 'div', $data );

<?php
class Simple_List_Templates implements Templates
{
public function get_templates(){

return array(
'ol' => array(
'outer' => "<ol>n%inner%</ol>n",
'inner' => "<li>%item%</li>n"
),

'ul' => array(
'outer' => "<ul>n%inner%</ul>n",
'inner' => "<li>%item%</li>n"
),

'div' => array(
'outer' => "<div>n%inner%</div>n",
'inner' => "<p>%item%</p>n"
),
);

}
}

Ich möchte zur Erklärung schnell mal über die Klasse Lister fliegen. Am Anfang sehen wir ein Interface, das hat in sofern seinen Sinn, als dass man damit bestimmte Bedingungen festlegen kann. So wird im Konstruktor der Lister-Klasse festgelegt das ein Objekt übergeben werden muss das aus einer Klasse erstellt wurde die das Interface Template imlementiert. Damit gehen wir sicher, das dass übergebene Objekt über die Methode get_templates() verfügt. Würden wir versuchen ein Objekt zu übergeben das nicht über diese Methode verfügt, sprich nicht das Interface Template implementiert, würden wir beim Aufruf der Methode get_templates() eine Fehlermeldung bekommen. Damit gehen wir zwar nicht sicher das die Template-Klasse uns beim Aufruf der Methode get_templates() auch ein Array mit Templates zurück gibt, es hilft aber unter Umständen sehr bei der Fehlersuche wenn mal wieder etwas nicht funktioniert.
Im  Konstruktor sehen wir das bei der Parameterübergabe darauf gepocht wird das dass übergeben Objekt das Interface Templates implementieren muss. Das übergebene Objekt speichern wir uns in einer Klassen-Variablen damit wir in anderen Methoden ggf. darauf zugreifen können. Zum Schluss holen wir uns noch die heiß begehrten Templates von der Template-Klasse.
Als nächstes sehen wir dann eine get_templates() Methode. Die Methode sollte ja eigentlich ausgelagert werden. Hier steht sie quasi als Wrapper damit man z.B. noch weitere Kontrollen oder ähnliches einbauen kann. Natürlich hätte man auch z.B. in der Methode get_list() direkt auf die gespeicherte Instanz der Template-Klasse zugreifen können. Aber wir wollen uns ja mal angewöhnen gleich von Anfang an vernünftigen Code zu schreiben. Auch in Beispielen.
Über get_list() muss ich hoffentlich keine Worte mehr verlieren. Templates abholen, Variablen ggf. verifizieren und desinfizieren, Liste basteln und ausgeben.

Ein konkretes Beispiel

Im Prinzip sind wir schon fertig und haben alle unsere gesteckten Ziele erreicht. Die Templates (Ausgabe) stehen in der einen Datei, die Verarbeitung der Daten (Logik) in einer anderen. Beides ist sauber voneinander getrennt. Kommen wir noch mal kurz darauf zurück warum das so sinnvoll sein kann.
Nehmen wir einfach mal den Fall das wir relativ viele Templates haben in denen eine Footer definiert ist . Der könnte nach traditionellem HTML in etwa so aussehen:

<div class="footer">
	<p>Copyright © 2001-2012</p>
</div>

Nun kommt unser Kunde der gelesen hat das HTML5 der letzte Schrei ist und er möchte nun seine Webseite auf HTML5 umgestellt haben weil er befürchtet das sie ansonsten morgen nicht mehr erreichbar ist. Dazu müssten auch alle Footer-Bereiche nach HTML5-Footer konvertiert werden. Aus den Code oben müsste also in etwa folgendes werden:

<footer>
	<p>Copyright © 2001-2012</p>
</footer>

Und das dann nicht nur auf einer Seite, sondern auf allen. Und nicht nur die Footer, sondern auch die Bereiche die eine Section, Navigation, Artikel, usw. darstellen. Der richtige Zeitpunkt um eine Reihe böser Flüche los zu werden, denn das bedeutet tagelanges Wühlen in unübersichtlichen Codezeilen.
Nun werfen wir einen Blick auf unsere Template-Klasse. Dort habe ich eine “Div-Liste” definiert mit der man z.B. solche Footer-Bereiche erzeugen kann. Anstatt nun den kompletten Code neu zu schreiben, tauscht man einfach seine Template-Klasse aus. Aus Simple_List_Templates wird dann z.B. HTML5_List_Templates. Das macht die Sache doch erheblich einfacher als in jeder HTML-Datei Änderungen manuell durchzuführen.
Mit etwas Überlegung kommt man dann auch recht schnell darauf das es eine relativ blöde Idee ist die Template-Klasse beim erzeugen der Lister-Klasse fest zu verdrahten. Zu den Problem komme ich in einen anderen Artikel, dort will ich noch mal auf ein paar Feinheiten eingehen. Dann gehe ich auch noch mal auf die Key-Value-Paare in der Foreach-Schleife ein.

Aber wir sehen schon so, dass man mit der Trennung von Ausgabe und Logik für die Zukunft programmiert. Man kann sowohl die Logik als auch die Templates in verschiedenen Projekten erneut verwenden. Zudem sind Änderungen nicht mehr der Weltuntergang weil man unzählige Dateien durchsuchen und kontrollieren muss. Ich hoffe dem einen oder anderen einen kleinen Denkanstoß gegeben zu haben und wünsche viel Spaß beim selber Ausprobieren.


Strings bequem formatieren

Ursprünglich war PHP eine Template-Sprache die HTML etwas Dynamik verleihen sollte. Das PHP über dieses Stadium schon sehr (sehr, sehr) lange hinausgewachsen ist, dürfte bekannt sein. Dennoch wird PHP häufig noch genauso verwendet wie in seinen Anfangstagen. Zu meinem persönlichen Leidwesen unterstützt PHP dies aus Gründen der Rückwärtskompatibilität auch weiterhin. Bei der Arbeit an so manchem Script sollte sich keine funktionsbereite Kettensäge in Reichweite befinden, da ansonsten Gefahr besteht das am Autor des Scriptes ein Funktionstest der Kettensäge vorgenommen wird.

Vor allem bei WordPress sieht man folgende Konstrukte sehr häufig. Das hängt zum einen damit zusammen das WordPress keine Template-Engine hat und man gewissermaßen dazu gezwungen wird PHP und HTML zu vermischen. Zum anderen hängt es wohl auch damit zusammen, dass irgend ein Sepp bei WordPress mal damit angefangen hat solch grausige Konstrukte zu erstellen und jetzt einfach nicht mehr damit aufhören kann.

<?php
	$href	= 'http://example.com';
	$title	= 'Link zu Example.com';
	$class	= 'example_link';
	$id		= 'first_anchor';
	$text	= 'Dies ist ein Link zu Example.com';
?>
<a href="<?php echo $href; ?>" title="<?php echo $title; ?>" id="<?php echo $id; ?>" class="<?php echo $class; ?>"><?php echo $text; ?></a>

Übersichtlich und lesbar ist was anderes. Wirklich schlimm wird es dann, wenn anstatt der Variablen auch noch Funktionsaufrufe direkt in die Ausgabe gemischt werden. Fehler sind so quasi vorprogrammiert und diese dann zu finden, ist eine Qual.

Deutlich übersichtlicher wird es, wenn man Curly Braces verwendet. Anstatt jede Variable mit einem<?php echo ...; ?> auszugeben, verwendet man geschweifte Klammern ( { und } ) um die Variable in einem String unterzubringen.

<?php
echo "<a href=\"{$href}\" title='{$title}' id='{$id}' class='{$class}'>{$text}</a>";
?>

Das sieht doch schon mal deutlich übersichtlicher aus, hat aber auch seine Nachteile. Der String muss z.B. in doppelten Anführungszeichen (“) eingeschlossen sein, was zur Folge hat das man innerhalb des Strings nur einfache Anführungszeichen(‘) verwenden kann oder doppelte Anführungszeichen mit einem Backslash maskieren muss (\”).
Zudem funktionieren Funktionsaufrufe nicht mehr so einfach. Man müsste das Ergebnis des Funktionsaufrufes erst in einer Variablen zwischenspeichern bevor man den Wert im String verwenden kann. Dies kann etwas kniffelig werden wenn die Funktion keinen Wert zurück gibt, sondern ihn stattdessen direkt ausgibt.

Arbeitet man mit Klassen, ist es etwas leichter den Rückgabewert einer Funktion im String mit geschweiften Klammern zu integrieren.

<?php
	class Link
	{
		public $href	= 'http://example.com';
		public $class	= 'example_link';
		public $id		= 'first_anchor';
		public $text	= 'Dies ist ein Link zu Example.com';

		public function get_title(){
			return 'Link zu Example.com';
		}
	}

	$obj = new Link();

	echo  "<a href='{$obj->href}' title='{$obj->get_title()}' id='{$obj->id}' class='{$obj->class}'>{$obj->text}</a>";
?>

Genau genommen handelt es sich hierbei um einen Methodenaufruf und nicht um einen Funktionsaufruf. Der “Trick” an der ganzen Sache ist einfach der, dass PHP $obj wie eine Variable behandelt (was es ja auch ist) und deswegen der Methodenaufruf relativ problemlos möglich ist.

Der größte Nachteil an allen Varianten ist meiner Meinung nach aber der, dass man Logik und Ausgabe miteinander vermischt. Gerade wenn man komplexe Ausgaben hat, wird es schwer die Stelle wiederzufinden an der die gesuchte Ausgabe formatiert wird.
Besser wäre es, wenn man alle Strings zentral an einer Stelle sammelt und in der Logik dann nur noch eine Variable zur Ausgabe verwendet.

<?php
$format = "<a href='{$href}' title='{$title}' id='{$id}' class='{$class}'>{$text}</a>";

// some other code...

echo $format;
?>

Dies wäre ein netter Ansatz, hat jedoch auch so seine Haken. Denn $format kann erst gebildet werden, nachdem alle anderen Variablen bereits definiert sind.

<?php
$format = "<a href='{$href}' title='{$title}' id='{$id}' class='{$class}'>{$text}</a>";

// some other code...

$text = 'Ein anderer Linktext';
echo $format;
?>

Würde zum Beispiel nicht funktionieren. Denn zu dem Zeitpunkt an dem $format erstellt wird ist $text noch gar nicht oder mit einen anderen Wert definiert.

Wir bräuchten also eine Funktion die quasi einen abstrakten Formatierungs-String entgegen nimmt und an den entsprechenden Stellen die Werte der Variablen einsetzt. Grundsätzlich hat PHP eine solche Funktion: printf() bzw. sprintf().
printf()nimmt einen Formatierungs-String und eine Reihe an Variablen entgegen. Die Platzhalter im Formatierungs-String erlauben zudem eine gewisse Formatierung der Ausgabe.

<?php
$format = "<a href='%s' title='%s' id='%s' class='%s'>%s</a>";

// some other code...

printf( $format, $href, $title, $id, $class, $text );
?>

Das sieht schon ganz brauchbar aus. Der Formatierungs-String kann z.B. ganz am Anfang des Scriptes definiert werden bevor auch nur eine der verwendeten Variablen definiert wurde. Und man kann anstatt einer Variablen auch einen Funktionsaufruf verwenden.
Einige kleine Nachteile hat printf() jedoch auch. In der gezeigten Variante muss z.B. die Reihenfolge strikt eingehalten werden. printf() bietet einige Möglichkeiten wie man die Reihenfolge flexibler gestalten kann und Variablen mehrfach verwenden kann. Zur genauen Verwendung der Formatierung schaut man am besten im PHP-Manual nach.

Aber auch printf() wird bei komplexen Ausgaben recht schnell unübersichtlich. Vertauscht man zwei Variablen im Funktionsaufruf, kommt es zur fehlerhaften Ausgabe. Auch wenn man die mehrfache Verwendung von Variablen nutzt, muss man die Übersicht behalten an welcher Stelle welche Variable steht.
Im Großen und Ganzen eine etwas unbefriedigende Situation die zu unleserlichen Code und versteckten Fehlern führt. Aus diesen Grund habe ich mir eine Klasse geschrieben mit der ich den Code etwas übersichtlicher gestalten kann.
Die Anforderung lag darin, zum einen die Übersichtlichkeit der Variante mit geschweiften Klammern zu erhalten. Zum anderen die Flexibilität, Abstraktion und Formatierungsmöglichkeiten von printf() zu haben. Heraus gekommen ist die Klasse Formatter.

Formatter nimmt einen Formatierungs-String ähnlich wie printf() entgegen, jedoch kann man den Platzhaltern Namen geben, was für mehr Übersichtlichkeit sorgt. Als zweiten Parameter nimmt Formatter ein Array mit Schlüssel-Werte-Paaren oder ein Objekt entgegen. Daher spielt die Reihenfolge in der die Werte definiert werden keine Rolle mehr. Zusätzlich lässt sich die Ausgabe wie bei printf()formatieren.

<?php
$format_time = 'Es ist jetzt %stunden[02d]%:%minuten[02d]%:%sekunden[02d]% Uhr %tageszeit%';
// some other code...

$time_values = array(
		'tageszeit'	=> 'mitten in der Nacht',
		'stunden'	=> 1,
		'minuten'	=> 23,
		'sekunden'	=> 8,
	);
// more code...

Formatter::printf( $format_time, $time_values );
// Es ist jetzt 01:23:08 Uhr mitten in der Nacht.
?>

So ist es sehr leicht Formatierung, Logik und Ausgabe voneinander zu trennen und das dann auch noch lesbar zu gestalten. Das es durchaus Vorteile hat das alles voneinander zu trennen, zeige ich dann im nächsten Artikel.


Quicktipp: Checkboxen auswerten

Vor kurzem hatte ich das Problem das ich mehrere Checkboxen bzw. deren Kombination auswerten musste. Normalerweise eine Arbeit für Leute die Mutter & Vadder erschlagen haben. Dazu gleich mal die Bedingungen:
Wir haben drei Checkboxen (a, b und c) in einem Formular und müssen auswerten welche Checkbox ausgewählt wurde bzw. nicht ausgewählt wurde. Wenn das Formular abgeschickt wird, bekommen wir ein POST- bzw. GET-Array mit den Werten (ich gehe im weiteren mal von einem POST-Array aus).
Das Böse an Checkboxen ist, wenn eine Checkbox nicht ausgewählt wurde, wird auch kein Wert im POST- bzw. GET-Array gesetzt.
Nach dem Absenden des Formulars bekommen wir also in etwa so etwas:

$mPost = array(
		'action'	=> 'send',
		'user'		=> 'Horst',
		'a'		=> 'On',
		'c'		=> 'On',
		'foo'		=> 'bar',
		'baz'		=> '',
	);

Dies ist nun eine Simulation des POST-Arrays und man kann sehen das für ‘b’ kein Wert gesetzt wurde sofern die Checkbox ‘b’ nicht ausgewählt wurde.
Wie wertet man nun üblicherweise die drei Checkboxen aus? Wahrscheinlich mit vielen If-ElseIf-Else Konstrukten und isset():

if( isset( $_POST['a'] ) )
...
elseif( isset( $_POST['a'] ) && isset( $_POST['b'] ) )
...
elseif( isset( $_POST['a' ) && ! isset( $_POST['b' ) )
...

if( isset( $_POST['b'] ) && isset( $_POST['c'] ) )
...

So wühlt man sich Zeile für Zeile durch alle möglichen Kombinationen und versucht keine zu vergessen. Das. Ist. Blöd! Und nicht nur blöd, sondern auch verdammt unübersichtlich. Bei drei Checkboxen mag es noch machbar sein, aber wenn man fünf, acht oder mehr Checkboxen hat, dann wird es fast schon unmöglich alle Kombinationen von ausgewählten und nicht ausgewählten Checkboxen zu berücksichtigen.

Da eine Checkbox nur ein Ja-Nein-Wert (Wahr oder Falsch) darstellt, habe ich mich recht schnell an die Zeit erinnert als ich mit der Computerei angefangen habe. Damals hatte ich einen C16 mit 16kB RAM. Dort war jedes Bit kostbarer als Gold und durfte nicht verschwendet werden. Deswegen hatte man oft mit Bitmasken gearbeitet. In einer Bitmaske steht jedes Bit für einen Ja oder einen Nein-Wert. Das war und ist recht effizient, denn in einem Byte kann ich so 8 Werte speichern. Und natürlich auch wieder abfragen.

Nun stehen wir vor dem Problem unsere drei Checkboxen in eine Bitmaske zu bekommen. Dazu benötigen wir erst einmal eine entsprechende Maske. Mit printf() bzw. sprintf() können wir eine solche Maske leicht erstellen:

$bin = sprintf( '%b%b%b', $mPost['a'], $mPost['b'], $mPost['c'] );

Das PHP-Manual sagt zum Platzhalter %b folgendes:

das Argument wird als Integer angesehen und als Binär-Wert ausgegeben

%b wird also immer zu 1 oder 0 umgewandelt. Wandeln wir nun unsere Variablen (a, b und c) in Boolsche Werte um, so würde PHP %b durch 1 bzw. 0 ersetzen, je nachdem ob die Variable TRUE oder FALSE ist. Denn zuerst würde der boolsche Wert in einen Integer (1 bzw. 0) umgewandelt und dann in einen einstelligen Binärwert. Dies erreichen wir dadurch, dass wir mit isset() abfragen ob die Variable überhaupt existiert. Somit umschiffen wir gleichzeitig das Problem das nicht ausgewählte Checkboxen keinen Wert übertragen.

$bin = printf( '%b%b%b', isset( $mPost['a'] ), isset( $mPost['b'] ), isset( $mPost['c'] ) );

Das klappt soweit schon wunderbar und bei drei Checkboxen ist es auch noch recht übersichtlich.
Als Ergebnis erhalten wir einen String der z.B. “101″ enthält. Je nachdem welche Variable gesetzt ist und welche nicht. Diesen String können wir nun ganz einfach mit einem switch-case abfragen und darauf reagieren:

switch( $bin ){
	case '000':
		//nichts ausgewaehlt
	break;

	case '100':
		// nur checkbox 'a' wurde ausgewählt
	break;

	case '101':
		// checkbox 'a' wurde ausgewaehlt, checkbox 'b' jedoch NICHT, checkbox 'c' ist uns egal
	break;

	default:
		// alle anderen Faelle
	break;
}

Bei drei Checkboxen ist das schon eine ganz brauchbare Lösung. Aber was ist wenn wir jetzt, sagen wir mal, 20 Checkboxen haben. Zum Beispiel eine Umfrage oder ähnliches?
Hier stehen wir vor zwei Problemen. Zum einen wäre der sprintf() nicht mehr übersichtlich und recht unbrauchbar. Zum anderen müssen wir ja gewähren das jede Variable an ihren Platz ist und nicht z.B. ‘a’ und ‘g’ vertauscht sind.

Als erstes legen wir uns mal ein Muster fest welche Variablen uns in welcher Reihenfolge interessieren. Das machen wir einfach mit einem Array:

$defaults = array(
		'a'	=> '',
		'b'	=> '',
		'c'	=> '',
	);

Damit wir nicht mit uninitialisierten Variablen arbeiten müssen, mischen wir das POST-Array mit unserer Vorgabe. Dadurch gehen wir sicher das alle benötigten Variablen auch mit einem (leeren) Wert initialisiert sind.

$data = array_merge( $defaults, $mPost );

Nun müssen wir nur noch über unser Vorgabe-Array ($defaults) laufen und abfragen ob die entsprechenden Schlüssel im Daten-Array ($data) gesetzt sind.

$bin = '';
foreach( $defaults as $key => $val )
	$bin .= sprintf( '%b', !!$data[$key] );

Dies kann man auch anders lösen, z.B. mit einem Trinären Operator. Je nach Geschmack des Programmierers halt:

$bin .= ! empty( $data[$key] ) ? '1' : '0';

Wenn der entsprechende Wert im Daten-Array nicht leer ist, wird eine 1 ausgegeben, ansonsten eine 0.
Als Ergebnis erhalten wir wieder einen String mit vielen Einsen und Nullen den wir im Switch-Case abfragen können. Wandelt man den String mit bindec() in eine Dezimalzahl um, kann man die Auswahl schön platzsparend speichern. Wenn man mit Cookies arbeitet, wird man nämlich schnell wieder daran erinnert das Speicherplatz Mangelware ist. Odre man nutzt den Dezimalwert um eine statistische Auswertung durchzuführen. Oder verwendet ihn im Switch-Case. Oder mit If-Abfragen. Oder, oder, oder…

Und hier das ganze dann  noch mal als komplettes Script:

$mPost = array(
'action' => 'send',
'user' => 'Horst',
'a' => 'On',
'c' => 'On',
'foo' => 'bar',
'baz' => '',
);

$defaults = array(
'a' => '',
'b' => '',
'c' => '',
);

$data = array_merge( $defaults, $mPost );

$bin = '';

foreach( $defaults as $key => $val ){
$bin .= sprintf( '%b', !!$data[$key] );
//$bin .= ! empty( $data[$key] ) ? '1' : '0';
}

$dec = bindec( $bin );

var_dump( $bin );
var_dump( $dec );

switch( $bin ){
case '000':
//nichts ausgewaehlt
break;
case '100':
// nur checkbox 'a' wurde ausgewählt
break;
case '101':
// checkbox 'a' wurde ausgewaehlt, checkbox 'b' jedoch NICHT
break;
default:
// alle anderen Faelle
break;
}
view raw file1.php This Gist is brought to you using Simple Gist Embed.

Zum Schluss noch etwas “dreckiges” PHP. Hier möchte ich mal schnell den NOTNOT-Operator einwerfen auf den ich bei der Suche nach der Lösung gestoßen bin. !$data[$key] wird Wahr (TRUE) wenn $data[$key] leer ist. Ich möchte aber wissen ob eine Variable nicht leer ist, denn printf() soll ja aus ‘%b’ eine ’1′ machen wenn die Variable gesetzt ist. Also muss ich die Bedingung !$data[$key] noch einmal negieren. Aus !$data[$key] wird also !!$data[$key].
Dies ist also im Grunde genommen nur eine fürchterliche Kurzschreibweise für ! empty( $data[$key] ). Für das schnelle Programmieren sind solche Kurzschreibweisen ganz nützlich. Sie sparen eine Menge Tipperei wenn man viel Code schreiben muss. Für die Lesbarkeit des Codes sind sie jedoch sehr kontraproduktiv. Man muss schon genau überlegen was da gerade passiert und sicher gehen das die Variable auch initialisiert ist, ansonsten hagelt es Notices.


Quick-Tipp: Schnell-Login

Hin und wieder gibt es das Bedürfnis sich ohne Angabe von Benutzername und Passwort einzuloggen. Das könnte z.B. der Fall sein wenn man automatisierte Tests durchführen möchte die einen Login benötigen. Oder aber man muss zum Testen immer wieder den Benutzer wechseln um verschiedene Szenarien durchzuspielen.
Aber auch wenn man sich in einer eher “unsicheren Umgebung” befindet (z.B. schlecht abgesichertes öffentliches Netzwerk) möchte man vielleicht nicht so gerne seine Login-Daten eintippen. Es wäre also ganz praktisch wenn man sich (automatisiert) einloggen kann ohne ständig Login-Daten einzutippen.

WordPress lässt sich relativ einfach dazu bringen Login-Daten automatisiert anzunehmen. In erster Linie ist die Funktion wp_signon() dafür zuständig den Login durchzuführen. Dazu übergibt man ihr Benutzername und Passwort, die Funktion gibt daraufhin true zurück bzw. ein WordPress-Fehler-Objekt. Dies kann man bequem mit is_wp_error() abfragen und so mit nur wenigen Zeilen einen Login durchführen.

<?php
require( dirname(__FILE__) . '/wp-load.php' );
is_wp_error(
wp_signon(
array(
'user_login'=>'YourLoginName',
'user_password'=>'YoUrAw3s0M3P455W0rD'
)
)
) ? die('Mooo... :(') : wp_safe_redirect( admin_url() );

Speichert man den Code in einer separaten Datei ab, so muss zuerst wp-load.php eingebunden werden um die WordPress-Funktionen verfügbar zu machen. Hier bitte auf den Pfad achten, iom Gist liegt die Datei im gleichen Verzeichnis wie wp-load.php.  Danach wird direkt wp_signon() mit einem Array aus Benutzername und Passwort gefüttert, welches wiederum direkt als Parameter an is_wp_error() übergeben wird.
Durch Aufruf der Datei ist man direkt eingeloggt und wird ins Backend umgeleitet. Möchte man lieber ins Frontend umgeleitet werden, so ersetzt man einfach admin_url() durch site_url().

Ich habe mir für meine Entwicklungsarbeit ein kleines Plugin geschrieben mit dem ich recht schnell zwischen verschiedenen Benutzern hin- und her wechseln kann. Dazu listet mir das Plugin auf der Login-Seite die Test-User auf, welche ich zuvor angelegt habe. Durch einen Klick auf einen entsprechenden User-Namen kann ich mich dann ohne Eingabe von Benutzername und Passwort anmelden. Auf Optik habe ich verzichtet, da es ein Werkzeug bei der Entwicklung ist. Wer mag, kann dem ganzen ja noch ein bisschen optischen Feinschliff verpassen.
Das Plugin WP-Quicklogin ist auf Github zu finden.


WordPress Version testen

Momentan habe ich es mir zur Aufgabe gemacht ein veraltetes Plugin auf eine neue Codebasis zu setzen (Refactoring). Um welches Plugin es sich dabei handelt, werde ich verraten wenn es in einem vorzeigbaren Zustand ist. Derzeit braucht es noch recht viel Arbeit.
Dabei fallen aber immer wieder ein paar Sachen ab die ich für erwähnenswert halte. Mein letzter Artikel war übrigens auch das Ergebnis dieser Arbeit. Diesmal ist es eine Klasse mit der man die minimale WordPress- und PHP-Version testen kann (MySQL könnte man auch testen, ich denke aber das wird kaum jemand machen da es eher selten der Fall ist das man eine bestimmte MySQL-Version voraussetzt). Minimale Version bedeutet hierbei, man testet ob WordPress bzw. PHP mindestens Version X hat.
Bei dem Tempo das Automattic bei der Entwickelung von WordPress an den Tag legt (alleine für dieses Jahr sind 3 Versionen geplant), wird es immer wichtiger zumindest zu  prüfen unter welcher WP-Version ein Plugin oder Theme aktiviert wird. Kann man natürlich auch sein lassen, wenn man den Anwender lieber mit Fehlermeldungen bzw. nicht funktionierenden Funktionen beglücken möchte. Ich persönlich halte es aber für empfehlenswert zu prüfen und ggf. auf eine zu niedrige Version hinzuweisen.

Die Klasse lässt sich vielfältig konfigurieren und flexibel einsetzen. Mit der statischen Methode is_WP() lässt sich z.B. prüfen ob WordPress bereits gestartet wurde. Ist dies nicht der Fall, werden 403-Header (forbidden) gesendet und das Script beendet. Dies ist z.B. nützlich um ein Script gegen direktes Aufrufen zu schützen.
Bsp.:

// beendet das Script falls WordPress nicht zuvor gestartet wurde
WP_Environment_Check::is_WP();

Kern der Klasse sind allerdings die drei Methoden check_wp(), check_php() und run_all_tests() (die Methode check_mysql() ist, wie erwähnt, nur der Vollständigkeit halber dabei und dürfte eher selten Anwendung finden). Konfiguriert man ein Array oder ein Objekt mit den entsprechenden Werten und übergibt das Array bzw. Object der Methode run_all_tests(), so werden die entsprechenden Versionen geprüft und ggf. das Script beendet. Natürlich kann man auch nur einzelne Komponenten testen. Im Gist ist noch eine Datei mit einigen Beispielen, ich denke dadurch wird klar wie man die Klasse verwenden kann.
Im Normalfall ruft man die Klasse beim Aktivieren des Plugins auf, so dass das Plugin erst gar nicht aktiviert wird.

Nun möchte man vielleicht nicht gleich mit dem Hammer zuschlagen und das Script beenden weil es sich um ein Theme und nicht um ein Plugin handelt. Dafür ist die Methode set_die_on_fail() nützlich. Übergibt man ihr den Wert TRUE, so wird das Script bei einen fehlerhaften Test nicht beendet, sondern FALSE als Rückgabewert zurück gegeben. Die Rückgabewerte kann man dann entsprechend auswerten und Meldungen ausgeben.

Falls man mit den Standardmeldungen nicht glücklich ist, kann man diese ebenfalls recht einfach anpassen. Einfach der Klasse einen neuen String übergeben. Das macht z.B. Übersetzungen der Fehlermeldungen recht einfach. Auch hierzu gibt es ein Beispiel im Gist.

Vielleicht findet der eine oder andere die Klasse ganz nützlich oder erweitert sie sogar. In beiden Fällen würde ich mich über Feedback natürlich freuen. Und hier noch der Gist zur Klasse und den Beispielen:

<?php
/**
*
* Class to check the environment (WordPress-, PHP and MySQL-version)
* Test only on minimum or equal version
*
* @author Ralf Albert
* @version 1.0
*
* @var array|object $versions (optional) Array with key=>val or object $version->wp|php|mysql; what to test => minimum version
*
*/
class WP_Environment_Check
{
/**
*
* WP version
* @access public
* @var string minimum or equal version of WordPress
*/
public $wp = '3.2';

/**
*
* PHP version
* @access public
* @var string minimum or equal version of PHP
*/
public $php = '5.2';

/**
*
* MySQL version
* @access public
* @var string minimum or equal version of MySQL
*/
public $mysql = '5.0';

/**
*
* Exit message if WordPress test failed
* @access public
* @var string
*/
public $exit_msg_wp = '';

/**
*
* Exit message if PHP test failed
* @access public
* @var string
*/
public $exit_msg_php = '';

/**
*
* Exit message if MySQL test failed
* @access public
* @var string
*/
public $exit_msg_mysql = '';

/**
*
* If set to true, the class will die with a message if a WP|PHP|MySQL test fail.
* Does not affect is_WP() or if forbidden_headers() is called withot a message
* @access public static
* @var bool true (default)|false
*/
public static $die_on_fail = TRUE;

/**
*
* Constructor
* Run all test that are defined in $version
* @access public
* @param array|object $versions
*/
public function __construct( $versions = NULL ){
self::is_WP();

if( ! empty( $versions ) || ( is_array( $versions ) || is_object( $versions ) ) )
$this->run_all_tests( $versions );
}

/**
*
* Set $die_on_fail
* @param bool $status True exits the script with a message
*/
public function set_die_on_fail( $status = TRUE ){
if( ! is_bool( $status ) )
$status = (bool) $status;

self::$die_on_fail = $status;
}

/**
*
* Check if WordPress is active (if $wp is an object of class wp() )
* @access public static
* @return bool true|die with message and send forbidden-headers if WP is not active
*/
public static function is_WP(){
global $wp;

if( ! $wp instanceof WP )
self::forbidden_header();
else
return TRUE;
}

/**
*
* Run all tests
* @access public
* @param array|object $versions
* @return bool true if all tests passed successfully
*/
public function run_all_tests( $versions = NULL ){
if( empty( $versions ) || ( ! is_array( $versions ) && ! is_object( $versions ) ) )
return FALSE;

$tests = array( 'wp', 'php', 'mysql' );

foreach( $versions as $test => $version ){
// check if the wanted test is available (means: is the test x a method 'check_x')
if( in_array( strtolower( $test ), $tests ) ){
$method = strtolower( $test );
$func = 'check_' . $test; // create the method (check_wp|check_php|check_mysql)
$this->$method = $version; // set $this->wp|php|mysql to version x

if( ! call_user_func( array( &$this, $func ) ) )
die( 'Test ' . __CLASS__ . '::' . $func . ' failed!' ); // this should never happen...
}
}

return TRUE;
}

/**
*
* Check WordPress version
* @access public
* @return bool true returns true if the test passed successfully. Die with a message if not.
*/
public function check_wp(){
if( empty( $this->wp ) )
return FALSE;

if( empty( $this->exit_msg_wp ) )
$this->exit_msg_wp = 'This plugin requires WordPress ' . $this->wp . ' or newer. <a href="http://codex.wordpress.org/Upgrading_WordPress">Please update WordPress</a> or delete the plugin.';

global $wp_version;
if( ! version_compare( $wp_version, $this->wp, '>=' ) ){
return self::forbidden_header( $this->exit_msg_wp );
}

return TRUE;
}

/**
*
* Check PHP version
* @access public
* @return bool true|die with message
*/
public function check_php(){
if( empty( $this->php ) )
return FALSE;

if( empty( $this->exit_msg_php ) )
$this->exit_msg_php = 'This plugin requires at least PHP version <strong>' . $this->php . '</strong>';

if( ! version_compare( PHP_VERSION, $this->php, '>=' ) ){
return self::forbidden_header( $this->exit_msg_php );
}

return TRUE;
}

/**
*
* Check MYSQL version
* @access public
* @return bool true|die with message
*/
public function check_mysql(){
if( empty( $this->mysql ) )
return FALSE;

if( empty( $this->exit_msg_mysql ) )
$this->exit_msg_mysql = 'This plugin requires at least MySQL version <strong>' . $this->mysql . '</strong>';

global $wpdb;
if( ! version_compare( $wpdb->db_version(), $this->mysql, '>=' ) ){
return self::forbidden_header( $this->exit_msg_mysql );
}

return TRUE;
}

/**
*
* Send forbidden-headers (403) if no message is set. Only dies if a message is set
* @access public static
* @param string (optional) $exit_msg
*/
public static function forbidden_header( $exit_msg = '' ){

if( empty( $exit_msg ) ){
header( 'Status: 403 Forbidden' );
header( 'HTTP/1.1 403 Forbidden' );
die( "I'm sorry Dave, I'm afraid I can't do that." );
} else {
if( FALSE === self::$die_on_fail )
return FALSE;
else
die( $exit_msg );
}
}
}

<?php
/*
* Mocking
*/
class WP{}
$wp = new WP;

class WPDB
{
public function db_version(){
return '5.1';
}
}
$wpdb = new WPDB();

$wp_version = '3.3.1';
/* end mocking */

/*
* Creating an instance ov WP_Environment_Check
* Setup the minimum versions
* Setup an exit-message if the WordPress test fail
* Performs every single test one by one
*/

$a = new WP_Environment_Check();
$a->wp = '3.3.1';
$a->php = '5.2';
$a->mysql = '5.1';
$a->exit_msg_wp = 'The plugin <em><a href="http://example.com/my_plugin/">Acme Plugin</a></em> requires WordPress ' . $a->wp . ' or newer. <a href="http://codex.wordpress.org/Upgrading_WordPress">Please update WordPress</a> or delete the plugin.';
$a->check_wp();
$a->check_php();
$a->check_mysql();

/*
* Creating an object with the minimum versions
* Create an instance of WP_Environment_Check
* Performs all tests at once
*/

$v = new stdClass();
$v->wp = '3.3.1';
$v->php = '5.2';
$v->mysql = '5.0';
$a = new WP_Environment_Check();

$a->run_all_tests( $v );


/*
* Setup an array with the minimum versions
* Performs all test by creating an instance of WP_Environment_Check
*/

$v = array( 'wp' => '3.0', 'php' => '5.2', 'MySQL' => '5.1' );
$a = new WP_Environment_Check( $v );

/*
* Store testresult in an object. Disable dying if tests failed
*/

$a = new WP_Environment_Check();
$a->set_die_on_fail( FALSE );
$a->wp = '3.3.1';
$a->php = '5.2';
$a->mysql = '7.0';

$r = new stdClass();

$r->wp = $a->check_wp();
$r->php = $a->check_php();
$r->mysql = $a->check_mysql(); // fail on MYSQL -> 'mysql' => false

var_dump( $r );