Implementierung von Tags

1. Einführung

Dieses Kapitel widmet sich den APF-Tags (a.k.a. Taglibs), deren Bedeutung und Implementierung.

Der Page-Controller basiert auf dem gleichnamigen Software-Entwurfsmuster und ist ein zentraler Bestandteil des APF. Er ermöglicht dem Entwickler über das Tag-Konzept, eigene Funktionalitäten zur Erzeugung und Transformation des HMVC-DOM-Baumes zu injizieren. Tags bilden damit nicht nur die Basis des APF-HMVC-Konzepts, sondern können auch zur Erweiterung desselben genutzt werden.

Des Weiteren können Tags dazu verwendet werden um darin enthaltene Funktionalitäten in unterschiedlichen Projekten wiederzuverwenden. APF-Templates verarbeiten - als Schutz vor unkontrollierter Verteilung von Logik - lediglich Tags und keinen PHP-Code. Damit ist automatisch sichergestellt, dass View-Logik lediglich in Tags oder Controller enthalten sein kann.

1.1. Page-Controller

Aufgabe des Page-Controller ist der Aufbau und die Transformation der internen Template-Struktur. Hierzu bietet er ein Tag-API, die einem definierten Timing-Modell folgend Tags einliest, daraus Dokumente im Baum erzeugt und aus dem Baum anschließend eine HTML-Ausgabe erzeugt. Andere Formate wie beispielsweise XML sind ebenfalls möglich. Der Parser ist kein "echter" XML-Parser, beherrscht jedoch die Auflösung von explizit- und selbst-schließenden Tags sowie die Schachtelung von Tags in beliebig tiefen Hierarchien.

Bitte beachten Sie die Hinweise im Kapitel Der APF-Parser zu den Restriktionen des Parsers.

Die mit dem APF ausgelieferten Standard TagLibs bieten bereits eine Reihe an Standard-Funktionalitäten wie das Einbinden einer weiteren Template-Datei, die Definition von wiederverwendbaren Template-Fragmenten und das Bekanntgeben von eigenen Tags an.

1.2. XML-Parser

Der XMLParser dient dem Page-Controller zur Analyse einer Tag-Definition im Template und wird bei der Erstellung von Document-Instanzen genutzt.

Der XMLParser verarbeitet aus Performance-Gründen nur durch Leerzeichen getrennte Attribute. Die Werte von Attributen müssen mit doppelten Anführungszeichen umschlossen sein.

2. Definition eines Tags

Ein APF-Tag definiert sich durch folgende Bestandteile:

  • Prefix: das Prefix des Tags dient im Allgemeinen zur Strukturierung der Tags in Gruppen (z.B. core für Tags, die mit dem APF mitgeliefert werden). Dieser Teil ist mit XML-Namespaces vergleichbar.
  • Name: der Name des Tags bezeichnet den Tag selbst. Dieser Teil ist mit XML-Tag-Namen vergleichbar.
  • Attribute: die Attribute des Tags können zur Konfiguration des Tags genutzt werden. Sie dürfen keine weiteren Tags enthalten.
  • Inhalt: der Inhaltsbereich des Tags kann weitere Tags oder Text enthalten. Der APF-Parser löst dort enthaltene Tags entsprechend auf und hängt diese als Kinder in die aktuelle Hierarchie ein. Einfacher Text ist später ebenfalls in der Tag-Instanz verfügbar.

Folgende Tag-Definition könnte zur Ausgabe des aktuellen Datums in einem Template genutzt werden:

APF-Template
<current:date format="H:i" />

Dabei ist current das Prefix des Tags, date der Name und das Attribut format beinhaltet das Ausgabe-Format. Der Tag definiert keinen Inhalt.

Zur Darstellung einer Bildergalerie könnte folgendes Tag genutzt werden:

APF-Template
<img:gallery> <h3>Mein Urlaub 2012</h3> <p> Diese Bilder stammen aus meinem Urlaub im Jahr 2012: </p> <gallery:datasource namspace="..." class="..." /> </img:gallery>

In diesem Beispiel beinhaltet der <img:gallery />-Tag statisches HTML, was später zur Formatierung der Ausgabe genutzt werden kann und einen weiteren Tag zur Angabe der Datenquelle (z.B. aus einer Datenbank). Zur Verwendung des Tags muss auch dieses zunächst dem APF-Parser bekannt gegeben werden.

3. DOM-Struktur

Wie im Kapitel Kapitel 2 angesprochen erzeugt der Page-Controller aus der Tag-Struktur in den Template-Dateien einen DOM-Baum. Dieser ähnelt dem eines Browsers, der aus den HTML-Tags ebenfalls eine Speicherstruktur erzeugt.

Der Unterschied zur DOM-Struktur eines Browsers besteht darin, dass die einzelnen Knoten jeweils eigene Funktionalität zur Transformation bzw. Darstellung beinhalten, die der Page-Controller während der Transformationsphase nutzt. Details zum Timing-Modell des Page-Controller finden Sie hier.

Jeder Tag - bzw. ab einem definierten Zeitpunkt seine Instanz - durchläuft einen definierten Zyklus. Zunächst wird die Struktur des Tags analysiert, dann seine Substanz (Attribute und Inhalt). Anschließend wird gemäß der Tag-Definition eine Instanz des Tags erstellt und mit dem vom XMLParser analysierten Inhalt initialisiert.

Der Page-Controller verarbeitet alle Tags der Form

APF-Template
<prefix:name />

(selbstschließend) bzw.

APF-Template
<prefix:name></prefix:name>

(explizit schließend), die in einer Template-Datei definiert sind. Gemäß Kapitel 2 können Tags eine beliebige Anzahl von Attributen beinhalten.

Bei der Analyse eines Templates greift der APF-Parser auf die Liste der registrierten Tags zurück um die Tag-Instanzen zu erstellen. Diese Liste beinhaltet den Klassen-Namen, der die Implementierung eines Tags beschreiben. Die mit dem APF mitgelieferten Tags werden bereits in der bootstrap.php registriert.

Tags lassen sich sowohl statisch für alle Templates als auch dynamisch pro DOM-Knoten registrieren. Dies ermöglicht es, die für ein Projekt eingesetzten Tags an einer zentralen Stelle zu registrieren und gleichzeitig - sofern notwendig - Standard-Tags des APF lokal überschreiben.

Möchten Sie einen Tag für das gesamte Projekt registrieren, lässt sich das sehr einfach in der Bootstrap-Datei erledigen:

PHP-Code
Document::addTagLib('VENDOR\..\DateDisplayTag', 'date', 'display');

Soll ein Tag für einen dedizierten DOM-Knoten registriert werden, so lässt sich dies in einer Tag-Implementierung wie folgt erreichen:

PHP-Code
use APF\core\pagecontroller\Document; class MyTag extends Document { public function __construct() { $this->addInstanceTagLib('VENDOR\..\SpecialDateDisplayTag', 'date', 'display'); } }

Wird der <date:display />-Tag innerhalb des Projektes eingesetzt, so erzeugt der APF-Parser eine Instanz der Klasse DateDisplayTag. Wird er innerhalb von MyTag eingesetzt, so erzeugt der APF-Parser eine SpecialDateDisplayTag-Instanz.

Innerhalb eines Templates lassen sich eigene Tags mit Hilfe des <core:addtaglib />-Tag hinzufügen. Auch hier haben Sie die Möglichkeit, den Tag für alle Templates oder nur für den aktuelle DOM-Knoten zu registrieren. Details hierzu finden Sie in Kapitel Standard TagLibs.

Bitte beachten Sie, dass der APF-Parser alle XML-Tags der Form <prefix:name > verarbeitet. Dies kann ggf. zu Problemen führen, wenn Sie beabsichtigen, Namespace-behaftete XML-Tags direkt in den HTML-Quelltext zu schreiben. Nutzen Sie beispielsweise die alte Notation für die Einbindung von Google +1 über
APF-Template
<g:plusone></g:plusone>
so wird der APF-Parser dies mit einer fehlenden Tag-Definition quittieren. In solchen Fällen empfiehlt es sich auf alternative Notationen auszuweichen oder einen Wrapper-Tag für die Ausgabe zu implementieren.

4. Klassenstruktur eines Tags

Die Struktur eines Tags beschreibt die Klasse Document. Diese ist die Mutter aller Tag-Implementierungen des APF und definiert eine Reihe von Methoden, die innerhalb des Timing-Modells des Page-Controllers zu unterschiedlichen Zwecken aufgerufen werden.

Die Signatur de Klasse Document ist wie folgt (gekürzt):

PHP-Code
class Document extends APFObject { protected $objectId = null; protected $parentObject = null; protected $children = array(); protected $content; protected static $knownTags = array(); protected $knownInstanceTags = array(); protected static $knownExpressions = array(); protected $data = array(); protected static $documentIndex = array(); public function __construct() { } public function onParseTime() { } public function onAfterAppend() { } public function transform() { } }

Den aufgeführten Elementen kommt dabei folgende Bedeutung zu:

  • Im Feld objectId wird die eindeutige interne Objekt-Id gespeichert, die jeder Tag vom APF-Parser bei Erzeugen erhält. Diese wird u.a. bei der Vergabe von Benchmark-Tags genutzt um jedes DOM-Element eindeutig zu referenzieren.
  • parentObject referenziert auf das Vater-Objekt innerhalb des DOM-Baumes. Diese Referenz kann zur Traversierung des DOM-Baumes genutzt werden. Der Root-Knoten hat keinen Vater.
  • In der Liste children finden sich alle Kind-Knoten des aktuellen DOM-Elements. Diese Liste kann zur Traversierung des DOM-Baumes genutzt werden. Hat ein DOM-Knoten keine Kinder, ist die Liste leer.
  • Die Variable content beinhaltet den textuellen Inhalt eines Tags und die Platzhalter, die der Page-Controller bei der Erzeugung des DOM-Baumes hinterlässt um den exakten Zusammenbau des HTML-Quelltextes sicher zu stellen.
  • In der Variable attributes - aus der Klasse APFObject vererbt - findet sich eine assoziative Liste der Tag-Attribute wieder. Besitzt das Tag keine Attribute, ist die Liste leer.
  • Die (statische) knownTags-Liste beinhaltet die für alle Templates bekannte Tags. Findet der APF-Parser in einem Template einen Tag nach der für ihn bekannten Syntax, wird diese Liste genutzt um an Hand des Tag-Präfix und -Namens die korrespondierende Implementierung (voll qualifizierter Klassen-Name) zu finden.
  • Die knownInstanceTags-Liste speichert ebenfalls eine Liste aus Tag-Präfix und -Namen sowie der zu nutzenden Tag-Implementierung. Diese besitzt allerdings nur im aktuellen DOM-Knoten Gültigkeit. Findet der APF-Parser für den aktuellen DOM-Knoten eine lokale Tag-Definition, so wird diese der globalen vorgezogen. Zur Überschreibung von z.B. mit dem APF ausgelieferten Tag-Implementierungen lässt sich die Methode Document::addInstanceTagLib() nutzen. Diese befüllt im Gegensatz zur statischen Methode Document::addTagLib() die oben genannte Liste.
    Bitte beachten Sie, dass der APF-Parser bei Kind-Tags des aktuellen DOM-Knotens wieder auf die globale Liste zurück greift. Eine rekursive Vererbung von Überschreibungen ist nicht vorgesehen.
  • Der Konstruktor einer Tag-Klasse wird bei der Erzeugung der Tag-Instanz aufgerufen. Der Parser übergibt jedoch keine Argumente. Die Methode kann dazu genutzt werden, weitere Tags für die vorliegende Hierarchie bekannt zu geben oder Standard-Werte zu belegen.
    Zum Zeitpunkt der Ausführung des Konstruktors sind weder die Attribute des Tags noch der Inhalt verfügbar. Ebenso sind der Kontext und die Sprache des Tags noch nicht initialisiert.
  • Nachdem der Tag geparst wurde wird die Methode onParseTime() aufgerufen. Zu diesem Zeitpunkt sind die Attribute des Tags und der Inhalt in der Instanz verfügbar. Auch Kontext und Sprache des Tags sind initialisiert. Diese Methode kann dazu genutzt werden, den Tag mit Hilfe der verfügbaren Informationen weiter zu initialsieren oder den Tag-Inhalt weitere zu analysieren (z.B. enthaltene Kind-Tags parsen).
  • Ist der Knoten in den DOM-Baum eingehangen - sprich sind Vater und Kinder bekannt - wird die Methode onAfterAppend() aufgerufen. Innerhalb dieser Methode können Sie nun Logik platzieren, die auf Vater und Kinder Auswirkung hat.
  • Wird der aktuelle DOM-Knoten transformiert, ruft der Page-Controller die Methode transform() Ihres Tags auf. Platzieren Sie hier bitte diejenige Logik, die zur Erzeugung von HTML-Quelltext benötigt wird. Tags, die innerhalb des DOM-Baumes lediglich zur Konfiguration oder Initialisierung dienen generieren oft keine Ausgabe (z.B. <core:addtaglib />). Wie sich Ihr Tag letztlich verhält bleibt Ihrer Implementierung überlassen.
    Sofern die Methode transform() in eigenen TagLibs überschrieben wird, muss sich der Entwickler selbst um die Transformation der Kind-Knoten kümmern. Dies kann im einfachsten Fall durch
    PHP-Code
    foreach($this->children as $objectId => $DUMMY){ $this->content = str_replace( '<'.$objectId.' />', $this->children[$objectId]->transform(), $this->content ); }
    erreicht werden, wenn in den Methoden onParseTime() oder onAfterAppend() die Parser-Funktion extractTagLibTags() aufgerufen wurde. Weitere Hinweise können dem Foren-Beitrag transform() von eigenem taglib nicht ausgeführt entnommen werden.

Für die Transformation von Kind-Knoten können folgende Methoden genutzt werden:

  • transformChildren() Transformiert die Kind-Knoten im internen Inhalts-Puffer ($this->content).
  • transformChildrenAndPreserveContent() Transformiert die Kind-Knoten und gibt das Ergebnis zurück. Der interne Inhalts-Puffer ($this->content) wird dabei für eine erneute Transformation beibehalten.
  • transformChildrenAsEmpty() Entfernt die internen Positionsmarker des APF-Parsers (siehe Hinweis in Kapitel 5.2.4. Erzeugen der Ausgabe) im internen Inhalts-Puffer ($this->content).
  • transformChildrenAsEmptyAndPreserveContent() Entfernt die internen Positionsmarker des APF-Parsers (siehe Hinweis in Kapitel 5.2.4. Erzeugen der Ausgabe) und gibt das Ergebnis zurück. Der interne Inhalts-Puffer ($this->content) wird dabei für eine erneute Transformation beibehalten.

5. Implementierung

5.1. Einfacher Tag

Dieses Kapitel beschäftigt sich mit einfachen Tags. "Einfach" meint in diesem Zusammenhang einen Tag, der eine bestimmte Aufgabe erledigt, jedoch keine weitere Hierarchie erzeugt - sprich keine Kind-Tags besitzt.

Als Beispiel soll die Ausgabe der aktuellen Uhrzeit aus Kapitel 2 dienen. Hierzu muss der Tag lediglich zur Transformations-Zeit die Uhrzeit im gewünschten Format zurückgeben. Abhängigkeiten zu anderen Tags im Baum und die Notwendigkeit der Initialisierung bestehen nicht. Der Quellcode des Tags gestaltet sich damit wie folgt (Namespace: VENDOR\tags):

PHP-Code
use APF\core\pagecontroller\Document; class HourDisplayTag extends Document { public function transform() { return date($this->getAttribute('format')); } }

Innerhalb eines Templates lässt er sich wie anschließend gezeigt verwenden:

APF-Template
<core:addtaglib class="VENDOR\tags\HourDisplayTag" prefix="current" name="date" /> <current:date format="H:i:s" />

5.2. Komplexer Tag

Als "komplexer" Tag versteht das APF einen Tag mit weiteren - in der Hierarchie nicht eingeschränkten - Kind-Knoten.

Als Beispiel soll die Ausgabe einer Bildergalerie aus Kapitel 2 dienen. Dieser Tag definiert zunächst statischen Inhalt, der zur Ausgabe einer Überschrift und eines Einleitungstextes genutzt wird. Die Generierung der Ausgabe selbst wird vom <img:gallery />-Tag mit Hilfe eines Content-Providers, der durch den <gallery:datasource />-Tag definiert wird.

Bitte beachten Sie, dass die Struktur des Beispiels willkürlich gewählt und nicht für Ihre Implementierungen bindend ist. Es wäre sowohl denk- als auch realisierbar den Content-Provider über Tag-Attribute zu definieren und die Formatierung der Galerie über weitere Elemente (siehe z.B. Iterator) zu realisieren.
5.2.1. Basis

Die Basis-Struktur des Tags gestaltet sich wie folgt:

PHP-Code
use APF\core\pagecontroller\Document; use VENDOR\tags\GalleryDataSourceTag; class GalleryTag extends Document { public function __construct() { Document::addTagLib( 'VENDOR\tags\GalleryDataSourceTag', 'gallery', 'datasource' ); } ... }

Im Konstruktor des Tags wird dem Parser der <gallery:datasource />-Tag bekannt gegeben. In diesem Fall setzen wir auf eine globale Registrierung. Damit kann der Tag unabhängig von der Hierarchie auch in anderen Templates eingesetzt werden.

Die Registrierung des Tags können Sie ebenso in Ihrer Bootstrap-Datei vornehmen, da in diesem Beispiel eine statische (=globale) Registrierung vorgenommen wurde. Das bedeutet, dass der Zeitpunkt der Registrierung gegenüber dem Ort entscheidend ist. Der Konstruktor der GalleryTag-Klasse wird gemäß Timing-Modell vor dem Parsen seiner Kinder ausgeführt. Damit lässt sich die Registrierung von Tag-Implementierungen sehr schön innerhalb einer Komponente erledigen (Kapselung!) und somit die Anwendung erleichtern.
5.2.2. Konfiguration des Content-Provider

Widmen wir uns nun zunächst der Konfiguration des Content-Providers. Hierzu spendieren wir dem <img:gallery />-Tag ein privates Feld und einen Setter, damit der <gallery:datasource />-Tag die Instanz übergeben kann:

PHP-Code
use APF\core\pagecontroller\Document; use VENDOR\..\ImageGalleryContentProvider; class GalleryTag extends Document { private $contentProvider; ... public function setContentProvider(ImageGalleryContentProvider $provider) { $this->contentProvider = $provider; } ... }

Der Provider selbst definiert sich durch folgendes Interface:

PHP-Code
interface ImageGalleryContentProvider { /** * @return GalleryImage[] */ public function getImages(); } class GalleryImage { private $title; private $description; private $imageUrl; public function __construct($title, $description, $imageUrl) { $this->title = $title; $this->description = $description; $this->imageUrl = $imageUrl; } public function getDescription() { return $this->description; } public function getImageUrl() { return $this->imageUrl; } public function getTitle() { return $this->title; } }

Gemäß unseres Beispieles ist der <gallery:datasource />-Tag dafür zuständig den gewünschten Provider zu erzeugen und dem GalleryTag mitzugeben. Dies lässt sich innerhalb der onAfterAppend()-Methode erledigen, da zu diesem Zeitpunkt der Vater-Knoten bekannt ist:

PHP-Code
use APF\core\pagecontroller\Document; use VENDOR\tags\GalleryTag; class GalleryDataSourceTag extends Document { public function onAfterAppend() { $provider = &$this->getServiceObject( $this->getAttribute('class') ); /* @var $parent GalleryTag */ $parent = &$this->getParentObject(); $parent->setContentProvider($provider); } public function transform() { return ''; } }

In der Methode onAfterAppend() erzeugt das Tag den gewünschten Provider mit Hilfe des ServiceManager um der Instanz Kontext und Sprache des aktuellen Elements mitzugeben. Anschließend wird dem Vater-Element des DOM-Baumes - hier eine Instanz der Klasse GalleryTag wie mit dem Type-Hint angegeben wird - der Provider injiziert.

Die leer überschriebene transform()-Methode sorgt dafür, dass keine weiteren Kind-Elemente transformiert oder eine Ausgabe erzeugt wird. Dies ist im aktuellen Beispiel nicht notwendig.

5.2.3. Parsen des GalleryDataSourceTag

Damit der GalleryDataSourceTag seine Arbeit verrichten kann muss der GalleryTag dafür sorgen, dass er auch vom Page-Controller erfasst und ausgeführt wird. Dazu bringt der Page-Controller bereits eine Parser-Methode mit, die in jeder von Document erbenden Klasse ausgeführt werden kann - Document::extractTagLibTags().

Zu welchem Zeitpunkt diese aufgerufen werden soll entscheiden zunächst Sie selbst. Beachten Sie dabei jedoch immer das Ablaufdiagramm des Page-Controller um den für den Anwendungsfall richtigen Zeitpunkt zu wählen. In unserem Fall ist lediglich wichtig, dass der Provider vor der Erzeugung der Inhalte ausgeführt wird. Damit sind theoretisch drei Möglichkeiten denkbar:

  • In einer eigenen onParseTime()-Methode.
  • In einer eigenen onAfterAppend()-Methode.
  • Zu Beginn der transform()-Methode.

Üblicherweise schickt es sich für die Analyse von weiteren Kind-Strukturen die onParseTime()-Methode zu nutzen, da Sie damit sicherstellen, dass die weiteren Elemente im Baum ebenfalls die Möglichkeit haben, Kind-Knoten zeitlich korrekt zu erzeugen.

Um den GalleryDataSourceTag zu erzeugen und auszuführen, reicht folgender Quellcode:

PHP-Code
use APF\core\pagecontroller\Document; class GalleryTag extends Document { ... public function onParseTime() { $this->extractTagLibTags(); } ... }

Alles Weitere erledigt der APF-Parser für Sie und an Hand des Template-Inhaltes.

5.2.4. Erzeugen der Ausgabe

Die Ausgabe der Bildergalerie erfolgt in der Methode transform(), die vom Page-Controller bei der Transformation des Baumes aufgerufen wird. Diese muss nun dafür sorgen, dass die Bilder, die vom Provider geliefert werden entsprechend ausgegeben werden.

Unter der Annahme, dass alle Bilder als ungeordnete Liste mit entsprechendem Platz für Beschreibungen ausgegeben werden, gestaltet sich die Methode transform() wie folgt:

PHP-Code
use APF\core\pagecontroller\Document; class GalleryTag extends Document { ... public function transform() { $images = $this->contentProvider->getImages(); $buffer = '<ul class="gallery-images">'; foreach ($images as $image) { $buffer .= '<li>' . '<img src="' . $image->getImageUrl() . '" alt="' . $image->getTitle() . '" />' . '<p>' . $image->getDescription() . '</p>' . '</li>'; } $buffer .= '</ul>'; $this->setContent($this->getContent() . $buffer); } }

Mit der aktuellen Implementierung werden alle Bilder als Listen-Elemente nach dem im <img:gallery />-Tag vorhandenen statischen Inhalt angezeigt.

Soll in einem Tag kein statischer Inhalt zugelassen werden, so kann dieser in der transform()-Methode durch setContent() durch einen dynamisch erzeugten überschrieben werden.
Damit bei der Transformation die Inhalte eines Tags an der korrekten Stelle ausgegeben werden, erzeugt die Methode extractTagLibTags() Platzhalter der Form
APF-Template
<{OBJECT_ID} />
Dabei entspricht {OBJECT_ID} dem Wert der Klassen-Variable $this->objectId und dem Array-Offset, in dem die Kind-Tags gespeichert werden ($this->children). Dieser Wert kann dann bei der Implementierung von eigenen transform()-Methoden für die Ersetzung des Inhalts genutzt werden.

Im Fall des <img:gallery />-Tags sollen die Kind-Tags keine Ausgabe erzeugen, was prinzipiell mit der GalleryDataSourceTag::transform()-Methode sichergestellt. Da der APF-Parser jedoch zur Positionierung der Ausgabe Platzhalter erzeugt, müssen diese in unserem Beispiel dennoch ersetzt werden. Dies kann durch folgende Erweiterung passieren:

PHP-Code
use APF\core\pagecontroller\Document; class GalleryTag extends Document { ... public function transform() { $images = $this->contentProvider->getImages(); $buffer = '<ul class="gallery-images">'; foreach ($images as $image) { $buffer .= '<li>' . '<img src="' . $image->getImageUrl() . '" alt="' . $image->getTitle() . '" />' . '<p>' . $image->getDescription() . '</p>' . '</li>'; } $buffer .= '</ul>'; $this->setContent($this->getContent() . $buffer); foreach ($this->children as $objectId => $DUMMY) { $this->content = str_replace( '<' . $objectId . ' />', $this->children[$objectId]->transform(), $this->content ); } } }

5.3. Document-API

Zur Implementierung von Tag-Logik stehen zahlreiche Methoden zur Verfügung. Diese umfassen das Parsen von Tag-Strukturen, das Traversieren des Baumes und das Manipulieren oder Auslesen von Tag-Informationen. Diese sind:

  • extractTagLibTags(): Analysiert die bekannten Kind-Tags und erstellen den DOM-Baum daraus.
  • extractExpressionTags(): Analysiert und interpretiert APF Pseudo-Template-Ausdrücke und erstellen die zugehörigen DOM-Baum-Elemente. Bitte beachten Sie die Hinweise unter Templates zur Verwendung der Ausdrücke. Weitere Details dazu finden Sie ebenso im Proposal unter Erweitertes Templating.
  • getParentObject(): Liefert die Instanz des Vater-Tags im DOM-Baum. Sofern kein Vater vorhanden ist, liefert die Methode null.
  • setParentObject(): Injiziert eine Instanz als Vater-Tags des entsprechenden DOM-Knotens.
  • getChildren(): Liefert die Liste aller Kinder des Tags. Sofern keine Kinder vorhanden sind, wird eine leere Liste zurückgegeben.
  • getContent(): Liefert den Inhalt des befragten DOM-Elements.
  • setContent(): Beschreibt den Inhalt des entsprechenden DOM-Elements.
  • getChildNode(): Liefert einen Kind-Knoten, der einem definierten Selektor genügt.
  • getChildNodes(): Liefert mehrere Kind-Knoten, der einem definierten Selektor genügen.
  • getAttribute(): Liefert den Wert eines Attributes.
  • setAttribute(): Definiert den Wert eines Attributes.
  • getAttributes(): Liefert die Liste der aktuellen Attribute.
  • getAttributesAsString(): Generiert eine XML-Repräsentation der übergebenen Attribute an Hand einer optionalen Whitelist.

Weitere Methoden der Klasse Document oder der Basis-Klasse ihres aktuellen Tags finden Sie in der API-Dokumentation.

6. Der APF-Parser

6.1. Features

Der Parser des APF besitzt folgende Funktionalitäten:

  • Der Parser kann sowohl symmetrische als auch asymmetrische Strukturen von selbst- und explizit schließenden Tags erkennen und Verschränkungen auflösen. Beispiel:
    APF-Template
    <foo:bar> ... <foo:bar> ... </foo:bar> ... <foo:bar /> ... </foo:bar> <foo:bar> ... <zig:zag> ... <foo:bar /> ... </zig:zag> ... </foo:bar>
    Er erkennt dabei Tags mit gleichen und unterschiedlichen Kombinationen aus Präfix und Namen und ordnet diese der richtigen Hierarchie innerhalb des APF-DOM-Baums zu.
  • Registrierte XML-Tags lassen sich in allen Ebenen unter Verwendung des definierten Präfix und Namens wiederverwenden. Der Parser ordnet diese entsprechend der Positionierung im Template im APF-DOM-Baum an.
  • Die Verarbeitung von Inhalten sind der jeweiligen Tag-Implementierung überlassen. Dies betrifft insbesondere die Verarbeitung von Tag-Inhalten (statischer Inhalt und weitere Tags). Dies ermöglicht Ihnen, bei der Implementierung frei zu entscheiden, wie mit dem Inhalt umzugehen ist. Auf diese Weise lässt sich der Aufbau des APF-DOM-Baums sehr einfach beeinflussen und Inhalte z.B. nur unter bestimmten Bedingungen ausgeben.

Darüber hinaus implementiert der APF-Parser die Konzepte des APF Page-Controller und sorgt für den Aufbau des DOM-Baumes.

6.2. Restriktionen

Der Tag-Parser des APF besitzt aus Performance-Gründen einigen Restriktionen gegenüber einem "echten" XML-Parser. Diese sind:

  • Tag-Attribute können nur durch Leerzeichen getrennt werden. Tab-Zeichen sind nicht möglich.
  • Tags in Attribut-Werten können nicht verarbeitet werden. Eine solche Definition führt in der Regel zu einer ParserException!
  • Attribut-Werte müssen mit doppelten Anführungszeichen umschlossen sein.
  • Die Definition eines Tags wird nur dann erkannt, wenn Tag-Deklaration und Attribute durch ein Leerzeichen getrennt sind. Dies gilt insbesondere für mehrzeilige Tag-Definitionen von verschachtelten Tags. Folgendes Beispiel wird vom APF-Parser nicht erkannt:
    APF-Template
    <form:recaptcha name="my-captcha"> <recaptcha:getstring ... /> </form:recaptcha>
    Um die auftretende ParserException zu beheben, kann entweder hinter <form:recaptcha ein Leerzeichen eingefügt oder das Attribut name auf der selben Zeile wie <form:recaptcha aufgeführt werden.
    Dieser Fehler tritt vermehrt in IDEs auf, die beim Speichern von HTML-Dateien unnötige Leerzeilen am Ende einer Zeile automatisch entfernen.
  • Als verarbeitbare Tags werden nur APF-Tags erkannt. Einfache HTML-Tags können nicht erfasst werden und dies erfordert u.U. die Implementierung von Wrapper-Tags für die Abstraktion.
  • Um das Einlesen von Template-Dateien performant zu gestalten ist die Länge von Tag-Präfixes und -Namen auf 10 Zeichen beschränkt!

7. Anwendungsbeispiele

In diesem Kapitel finden Sie Anwendungsbeispiele für Tags, die Sie in Ihrer täglichen Arbeit mit dem APF als Vorlage für eigene Implementierungen bzw. als Coding-Guideline nutzen können. Als Daumenregel gilt:

Ein Tag wird erst notwendig, wenn die Anforderung mit Hilfe eines Templates - inkl. aller Standard TagLibs - und einem (Document-)Controller nicht mehr zu realisieren ist (z.B. Manipulationen des DOM-Baums). Weiterhin empfielt es sich, Funktionalität in Tags abzufassen, wenn immer wieder verwendete Elemente in unterschiedlichen Templates genutzt werden soll ohne den Code zu duplizieren oder unnötige Vererbungshierarchien zu erzeugen.

7.1. Einfacher Tag mit Attributen

Als Beispiel soll in diesem Kapitel der <html:text />-Tag implementiert werden. Dieser gibt einen sprachabhängigen Text aus, der an Hand eines Attributs definiert wird. Beispiel:

APF-Template
<html:text key="log-in.mousover.text" />

Die Implementierung des Tags beinhaltet die Verarbeitung des Attributs und die Ausgabe des Textes zur Transformationszeit. Dies kann mit folgender Implementierung bewerkstelligt werden:

PHP-Code
use APF\core\pagecontroller\Document; class TranslationTextTag extends Document { public function transform() { return gettext($this->getAttribute('key')); } }

Sollen die Attribute eines Tags bei der Generierung der Ausgabe einbezogen werden, kann die Methode getAttributesAsString() genutzt werden. Als Beispiel soll ein Tag genutzt werden, der die Ausgabe eines Bildes aus einer Medien-Datenbank an Hand einer externen ID erzeugt:

APF-Template
<html:img key="IMG-12345" width="100" height="120" alt="Diese Bild zeigt ein rotes Auto" />

Die zugehörige Implementierung kann wie folgt aussehen:

PHP-Code
use APF\core\pagecontroller\Document; class MAMImageTag extends Document { public function transform() { $width = $this->getAttribute('width', '50'); $height = $this->getAttribute('height', '50'); $key = $this->getAttribute('key'); $whiteList = array( 'alt', 'height', 'width', 'id', 'style', 'title' ); return '<img src="' . $this->getImageUrl($key, $width, $height) . '" ' . $this->getAttributesAsString($this->getAttributes(), $whiteList) . ' />'; } private function getImageUrl($key, $width, $height) { ... } }

In der obigen Code-Box wird die Methode getAttribute() dazu genutzt um die Werte der angegebenen Attribute auszulesen. Da einige der Attribute für die Ausgabe wiederverwendet werden sollen, wird der Methode getAttributesAsString() eine Liste an erlaubten Attributen - in diesem Fall kompatibel zur XHTML- bzw. HTML5-Spezifikation - mitgegeben. Die Ausgabe des Tags ist damit ein Image-Tag, der mit Hilfe der (HTML-)Attribute id, style und class formatiert werden kann.

Möchten Sie das Attribut key als Pflicht-Attribut kennzeichnen, so können Sie dieses mit Hilfe der Methode Document::getRequiredAttribute() beziehen. Die Methode wirft eine InvalidArgumentException sofern das geforderte Attribut nicht gesetzt ist. Dies erleichtert die Implementierung und erledigt die notwendigen Prüfungen bereits für Sie.

7.2. Einfacher Tag mit Inhalt

Neben den Attributen eines Tags kann dieser auch einfachen und komplexen Inhalt definieren. Als Implementierungsbeispiel soll in diesem Kapitel folgender Tag genutzt werden:

APF-Template
<html:entityencode>nobody@example.com</html:entityencode>

Ausgabe des Tags soll eine in HTML-Entitäten codierte E-Mail-Adresse sein, um sie vor Bots oder Spidern zu schützen. Hierzu muss der Inhalt des Tags ausgelesen und im Rahmen der Transformation des Tags ausgegeben werden. Dies führt zu folgender Implementierung:

PHP-Code
use APF\core\pagecontroller\Document; use APF\tools\string\StringAssistant; class EntityEncodeTag extends Document { public function transform() { return StringAssistant::encodeCharactersToHTML($this->getContent()); } }

Mit der Methode getContent() kann auf den Inhalt des Tags zugegriffen werden. Dieser wird mit Hilfe des StringAssistant in HTML-Entitäten umgewandelt.

7.3. Zugriff auf den umliegenden DOM-Baum

Wie im Kapitel 1 angesprochen erzeugt der Page-Controller aus den genutzten Templates und den darin enthaltenen Tags einen DOM-Baum. Das Verhalten ist - bis auf die Art der Erzeugung - mit dem eines Browsers vergleichbar, der ebenfalls aus dem angelieferten HTML einen DOM-Baum erzeugt und diesen grafisch aufbereitet.

Da Tags und (Document-)Controller Teil des Baumes sind haben Sie dort die Möglichkeit auf alle umliegenden Knoten zuzugreifen. Hierzu gibt es im Wesentlichen zwei Methoden:

  • getParentObject()
  • getChildren()

Innerhalb eines Tags können Sie mit diesen Methoden direkt auf die umliegenden Elemente zugreifen, innerhalb eines (Document-)Controller beziehen Sie mit getDocument() eine Referenz auf den aktuellen DOM-Knoten und können ab diesem mit den oben genannten Methoden navigieren.

Als Beispiel für den Zugriff auf soll folgendes Template dienen:

APF-Template
<core:addtaglib class="VENDOR\..\TemplateNameDisplayTag" prefix="template" name="display-name" /> <html:template name="test1"> ... </html:template> <html:template name="test2"> ... <template:display-name /> ... </html:template>

Der TemplateNameDisplayTag hat dabei die Aufgabe, die Namen aller Templates auszugeben, die im Baum auf gleicher Ebene hängen wie sein Vater. Hierzu kann folgende Implementierung genutzt werden:

PHP-Code
use APF\core\pagecontroller\Document; class TemplateNameDisplayTag extends Document { public function transform() { $template = &$this->getParentObject(); $grandFather = &$template->getParentObject(); $nodes = $grandFather->getChildren(); $buffer = '<ul>'; foreach ($nodes as $objectId => $DUMMY) { $buffer .= '<li>' . $nodes[$objectId]->getAttribute('name') . '</li>'; } return $buffer . '</ul>'; } }

Ein weiterführendes Beispiel finden Sie im Forum unter Placeholder über Taglib füllen.

7.4. Komplexer Tag mit Attributen und Inhalt

Die vorangegangenen Kapitel haben sich mit dem Zugriff auf Attribute, der Ausgabe von Attributen und dem Zugriff und der Verarbeitung von Tag-Inhalten beschäftigt. In diesem Abschnitt beschäftigen wir uns mit komplexeren Tags, die weitere Strukturen definieren und Inhalte sowie Attribute in die Abbildung ihrer Logik einbeziehen.

Als Beispiel soll uns ein Tag dienen, der Navigationsknoten vom Typ NavigationNode ausgibt:

APF-Template
<core:addtaglib class="APF\extensions\navigation\pres\tags\NavigationNodeTag" prefix="navi" name="template" /> <navi:template id="main-navi"><!-- NavigationNodeTag --> <navi:item status="active"><!-- NavigationItemTag --> <li class="active"> <item:content/><!-- ItemTemplateContentTag --> </li> </navi:item> <navi:item status="inactive"> <li> <item:content/> </li> </navi:item> <ul id="main-navigation"> <navi:content/><!-- NavigationContentTag --> </ul> </navi:template>

Der Tag wird in einem (Document-)Controller gefüllt und stellt die Liste der Kinder des übergebenen Navigationsknotens gemäß den definierten Formatierungen dar. Dabei beschreiben die <navi:item />-Tags die aktiven und inaktiven Zustände der Navigationsknotens und mit Hilfe von <navi:content/> lässt sich die Ausgabe in ein HTML-Gerüst packen. Der Tag <item:content/> platziert die Ausgabe eines konkreten Knotens und kann ebenfalls mit eigenem HTML versehen werden. Zur Erläuterung der Tag-Hierarchie sind die Namen der korrespondierenden Tags als HTML-Kommentar angefügt.

Zur Befüllung des <navi:template />-Tags in einem Template lässt sich folgender Controller nutzen:

PHP-Code
use APF\core\pagecontroller\BaseDocumentController; use APF\extensions\navigation\biz\SimpleNavigationNode; class NavigationTagExampleController extends BaseDocumentController { public function transformContent() { $root = new SimpleNavigationNode(null, null, null); $levelOne = new SimpleNavigationNode('Level 1', '#'); $root->setChildren(array( clone $levelOne->setInactive(), clone $levelOne->setActive(), clone $levelOne->setInactive() )); $navi = $this->getDocument()->getChildNode('id', 'main-navi', 'NavigationNodeTag'); $navi->setNode($root); } }

Die folgende Code-Box zeigt nun die Implementierung der Tags:

PHP-Code
use APF\core\pagecontroller\Document; interface NavigationNode { public function getLabel(); public function getUrl(); public function isActive(); public function getParent(); public function getChildren(); } class SimpleNavigationNode implements NavigationNode { private $label; private $url; private $isActive = false; private $parent; private $children = array(); public function __construct($label, $url) { $this->label = $label; $this->url = $url; } public function getLabel() { return $this->label; } public function getUrl() { return $this->url; } public function isActive() { return $this->isActive; } public function setActive() { $this->isActive = true; return $this; } public function setInactive() { $this->isActive = false; return $this; } public function getParent() { return $this->parent; } public function getChildren() { return $this->children; } public function setParent(NavigationNode $node) { $this->parent = $node; } public function setChildren(array $nodes) { $this->children = $nodes; } } class NavigationNodeTag extends Document { private $node; public function __construct() { self::addTagLib('EXAMPLE\navigation\pres\taglibs\NavigationItemTag', 'navi', 'item'); self::addTagLib('EXAMPLE\navigation\pres\taglibs\NavigationContentTag', 'navi', 'content'); } public function setNode(NavigationNode $node) { $this->node = $node; } public function onParseTime() { $this->extractTagLibTags(); } public function transform() { $buffer = ''; $navigationNodes = $this->node->getChildren(); if (count($navigationNodes) > 0) { foreach ($navigationNodes as $node) { $buffer .= $this ->getTemplate($node->isActive() ? 'active' : 'inactive') ->getOutput($node); } } $content = $this->getContent(); $children = &$this->getChildren(); foreach ($children as $objectId => $DUMMY) { if ($children[$objectId] instanceof NavigationContentTag) { // fill the navi:content place holder if we get him $content = str_replace('<' . $objectId . ' />', $buffer, $content); } else { // replace parser marker to avoid direct tag output $content = str_replace('<' . $objectId . ' />', '', $content); } } return $content; } private function getTemplate($status) { return $this->getChildNode('status', $status, 'NavigationItemTag'); } } class NavigationItemTag extends Document { public function __construct() { self::addTagLib('EXAMPLE\navigation\pres\taglibs\ItemTemplateContentTag', 'item', 'content'); } public function onParseTime() { $this->extractTagLibTags(); } public function getOutput(NavigationNode $node) { $content = $this->getContent(); $children = &$this->getChildren(); foreach ($children as $objectId => $DUMMY) { if ($children[$objectId] instanceof ItemTemplateContentTag) { // fill the item:content place holder if we get him $content = str_replace('<' . $objectId . ' />', $children[$objectId]->setNode($node)->transform(), $content); } else { // replace parser marker to avoid direct tag output $content = str_replace('<' . $objectId . ' />', '', $content); } } return $content; } public function transform() { return ''; } } class ItemTemplateContentTag extends Document { private $node; public function setNode(NavigationNode $node) { $this->node = $node; return $this; } public function transform() { if ($this->node === null) { return ''; } return '<a href="' . $this->node->getUrl() . '">' . $this->node->getLabel() . '</a>'; } } class NavigationContentTag extends Document { }
Das hier beschriebene Beispiel dient dazu, die Möglichkeiten des APF aufzuzeigen und ist nicht als Referenz-Implementierung für die Ausgabe von Navigationen gedacht.

Sofern Sie weitere Informationen benötigen, können Sie folgende Threads aus dem Forum nutzen:

Im Wiki finden sich folgende Seiten zu den APF-Tags:

Kommentare

Möchten Sie den Artikel eine Anmerkung hinzufügen, oder haben Sie ergänzende Hinweise? Dann können Sie diese hier einfügen. Die bereits verfassten Anmerkungen und Kommentare finden Sie in der untenstehenden Liste.
Für diesen Artikel liegen aktuell keine Kommentare vor.