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.

2 Trackbacks

  1. Von Trenne Ausgabe und Logik – Yoda Condition am 1. April 2012 um 02:23

    [...] Yoda Condition Debuggen du musst Gehe zum Beitrag AboutKontaktImpressum « Strings bequem formatieren [...]

  2. Von Templating mit WordPress – Yoda Condition am 26. Mai 2012 um 23:37

    [...] ersten Teil hatte ich einen Formatter vorgestellt mit dem ich auf vergleichsweise einfache und flexible Art aus Daten eine Ausgabe machen kann. Im [...]

2 Kommentare

  1. Hey Ralf,

    wie immer ein schöner, gut durchdachter und interessanter Artikel. Schau Dir vielleicht einmal die native Php-Funktion strtr() an :)

    Eine andere php 5.x+ Methode könnte Dir auch Spaß machen: __toString();

    An Stelle der Exceptions würde ich folgendes Konstrukt innerhalb von __toString() vorschlagen. Einfach vorher new WP_Error( 'code', 'message', $data ); setzen und dann so auswerten:


    public function __toString()
    {
    $output = self :: sprintf();

    if ( is_wp_error( $output ) )
    {
    // No error message for Guests or Subscribers or anyone below admin capability "manage_options"
    // Assuming that no one has activated caching plugins when debugging
    // and not set WP_DEBUG to TRUE on a live site
    if (
    ! is_user_logged_in()
    AND ! current_user_can( 'manage_options' )
    AND ( ! defined( 'WP_DEBUG' ) OR ! WP_DEBUG )
    )
    return '';

    return "{$output->get_error_message( 'no_attachment' )}: {$output->get_error_data()}";
    }

    return $output;
    }

    Beste Grüße!

    Veröffentlicht 18. März 2012 am 17:46 | Permalink
  2. Ralf

    Ähmm ne. Ich würde eine WordPressException schreiben die sich um das Geraffel kümmert. Da ich es bisher noch nicht gemacht habe, habe ich einfach eine normale Exception verwendet.
    Die __toString()-Methode hat auch ganz andere Aufgaben. Im Prinzip ist es ein Type-Casting. Deswegen würde ich es nicht “missbrauchen” wollen.

    Veröffentlicht 20. März 2012 am 06:56 | Permalink