Kommentar-Funktion

1. Einleitung

Dieses Tutorial möchte zeigen, wie das Kommentar-Modul technisch aufgebaut ist und welchen Komponenten welche Funktion erfüllen. Das Ergebnis findet sich am Ende dieser Seite zum Ausprobieren.

Es soll zudem verdeutlicht werden, welche Paradigmen und Design-Pattern Anwendung finden. Dazu werden die unter Grundlagen bereits vermittelten Design-Pattern praktisch für diesen Anwendungsfall aufbereitet.

2. Implementierung der Software

Die Kommentar-Funktion besteht aus Sicht eines Anwenders aus zwei Teilen: Ausgabe und Formular. Aus Sicht eines Entwicklers aus der Ausgabe, dem Formular, einer Datenbank und der zugehörigen Logik, die das Auslesen der Daten und das Füllen der Datenbank organisiert.

Diesen Komponenten wird im Folgenden Rechnung getragen. Die Struktur der Software wird gemäß einer Schichten-Architektur in drei Bereiche aufgeteilt.

Für das Vorgehen bei der Implementierung der Software gibt es unterschiedliche Ansätze: top-down bzw. bottom-up. In diesem Fall nutzen wir letzteres.

2.1. Struktur des Moduls

Zunächst soll die Ordner- bzw. Namespace-Struktur der Software angelegt werden. Da es sich bei der Kommentar-Funktion gemäß Grundlagen um ein Stück Software handelt, das in mehreren Projekten (=Webseiten) eingesetzt werden kann, wird das Modul im Ordner /apps/modules abgelegt wird.

Für die Implementierung von eigenen Modulen wird davon abgeraten, den Ordner modules zu nutzen, da sonst eigene Software beim Update u.U. überschrieben wird. Das das Kommentar-Modul ein Teil des APF ist, ist diese Vorgehensweise für dieses Tutorial in Ordnung.

Das Modul erhält den Namen comments, der gleichzeitig auch der Ordner-Name ist. Zur Strukturierung der Software werden nun die Ordner data, biz und pres angelegt. Innerhalb von pres fügen wir jeweils einen Ordner für die Controller (documentcontroller) und die View-Komponenten (templates) ein. Es ergibt sich somit folgende Ordnerstruktur

Code
/apps /config /core /extensions /modules /comments /biz /data /pres /css /documentcontroller /templates /tools

Da für einige Komponenten Konfigurationen erforderlich sind, existiert in der Übersicht zusätzlich ein Ordner config. Der Ordner css ist für die mitgelieferten Stylesheets gedacht.

2.2. Domain-Objekt

Als erste Klasse legen wir das von allen Schichten genutzte Domain-Objekt ArticleComment an. Dazu wird die Datei ArticleComment.php im Ordner /apps/modules/comments/biz erstellt und mit folgenden Inhalt gefüllt:

PHP-Code
class ArticleComment { private $id = null; private $name; private $email; private $comment; private $date; private $time; private $categoryKey; public function getId() { return $this->id; } public function setId($id) { $this->id = $id; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; } public function getEmail() { return $this->email; } public function setEmail($email) { $this->email = $email; } public function getComment() { return $this->comment; } public function setComment($comment) { $this->comment = $comment; } public function getDate() { return $this->date; } public function setDate($date) { $this->date = $date; } public function getTime() { return $this->time; } public function setTime($time) { $this->time = $time; } public function getCategoryKey() { return $this->categoryKey; } public function setCategoryKey($categoryKey) { $this->categoryKey = $categoryKey; } }

2.3. Datenschicht

Die Datenschicht besteht aus einem DataMapper, der zunächst nur eine Lese-Funktion erhält.

An dieser Stelle sei vorweggenommen, dass zur Ausgabe einer blätterbaren Ansicht der Pager zum Einsatz kommt. Dieser setzt voraus, dass eine entsprechende Datenschicht-Komponente eine Methode zum Laden von Einträgen über eine eindeutige Id besitzt. Aus diesem Grund wird unser Mapper mit der Methode loadArticleCommentByID() ausgestattet.

Zunächst erstellen für unseren Mapper eine Datei mit dem Namen commentMapper.php unter /apps/modules/comments/data und sehen folgenden Inhalt vor:

PHP-Code
import('modules::comments::biz', 'ArticleComment'); class commentMapper extends APFObject { public function loadArticleCommentByID($commentId) { $SQL = &$this->getConnection(); $select = 'SELECT ArticleCommentID, Name, EMail, Comment, Date, Time FROM article_comments WHERE ArticleCommentID = \'' . $commentId . '\';'; $result = $SQL->executeTextStatement($select); return $this->mapArticleComment2DomainObject($SQL->fetchData($result)); } public function saveArticleComment(ArticleComment $comment) { $conn = &$this->getConnection(); if ($comment->getId() == null) { $insert = 'INSERT INTO article_comments (Name, EMail, Comment, Date, Time, CategoryKey) VALUES (\'' . $conn->escapeValue($comment->getName()) . '\',\'' . $conn->escapeValue($comment->getEmail()) . '\',\'' . $conn->escapeValue($comment->getComment()) . '\',CURDATE(),CURTIME(),\'' . $comment->getCategoryKey() . '\');'; $conn->executeTextStatement($insert); } } private function &getConnection() { $cM = &$this->getServiceObject('core::database', 'ConnectionManager'); $config = $this->getConfiguration('modules::comments', 'comments.ini'); $connectionKey = $config->getSection('Default')->getValue('Database.ConnectionKey'); if ($connectionKey == null) { throw new InvalidArgumentException('[commentMapper::getConnection()] The module\'s ' . 'configuration file does not contain a valid database connection key. Please ' . 'specify the database configuration according to the example configuration files!', E_USER_ERROR); } return $cM->getConnection($connectionKey); } private function mapArticleComment2DomainObject($resultSet) { $comment = new ArticleComment(); $comment->setId($resultSet['ArticleCommentID']); $comment->setName($resultSet['Name']); $comment->setEmail($resultSet['EMail']); $comment->setComment($resultSet['Comment']); $comment->setDate($resultSet['Date']); $comment->setTime($resultSet['Time']); return $comment; } }

Der Quellcode hat dabei folgende Bedeutung:

  • Der import()-Befehl bindtn benötigten Klassen ein. Hierzu gehört das Domain-Objekt ArticleComment, der ConnectionManager wird direkt über den ServiceManager eingebunden.
  • Die Methode loadArticleCommentByID() läd einen Kommentar an Hand einer ID aus der Datenbank. Es wird hier auf eine Konfiguration für Feldnamen und Datenbank-Tabelle verzichtet, da der commentMapper ohnehin eine konkrete Implementierung eines Data-Mappers für die vorliegende Applikation ist. Die beschleunigt nicht nur das Lade-Verhalten, es ist zudem einfacher zu entwickeln. Die Datebank-Verbindung wird über den ConnectionManager bezogen und das Ergebnis der Abfrage der Methode mapArticleComment2DomainObject() übergeben.
  • mapArticleComment2DomainObject() dient zur Übersetzung des Ergebnisses der Datenbank-Abfrage in das Domänen-Objekt.
  • Die Funktion getConnection() liefert die für das Modul konfigurierte Datenbank-Verbindung zurück.

Um die für dieses Modul notwenige Datenbank-Layout anlegen zu können, muss das im Ordner /apps/modules/comments/data/scripts vorhandene SQL-Script init_comments.sql ausgeführt werden.

Für eigene Anwendungen schickt es sich ebenso, die notwenigen Datenbank-Initialisations-Skripte direkt in einem Ordner des Moduls bzw. der Software abzulegen um später nachvollziehen zu können, welche Anwendung welche Tabellen der Datenbank nutzt.

Die Konfigurationsdatei {ENVIRONMENT}_comments.ini beinhaltet eine Referenz auf eine Datenbank-Verbindung. Letztere findet sich in der globalen Datenbank-Konfiguration im Namespace core::database. Details zur Konfiguration von Datenbank-Verbindungen können dem Kapitel ConnectionManager entnommen werden.

2.4. Business-Schicht

Zur Business-Schicht zählt eine Manager-Klasse, die den Ablauf der Software regelt. Dazu legen wir eine Datei mit dem Namen commentManager.php im Ordner /apps/modules/comments/biz an. Im Fall der Kommentar-Funktion (lesender Zugriff) muss diese lediglich die Daten aus der Datenschicht beziehen und zurück an die Präsentations-Schicht geben.

Wie bereits oben angedeutet verwendet die Kommentar-Funktion den Pager. Aus diesem Grund ist es später notwendig weitere Mechanismen vorzusehen und den Pager entsprechend zu konfigurieren.

Das Grundgerüst der Klasse sieht folgendes vor:

PHP-Code
class commentManager extends APFObject { protected $categoryKey; public function init($initParam) { $this->categoryKey = $initParam; } public function loadEntries() { } }

Der Quellcode hat dabei folgende Bedeutung:

  • Die Klassenvariable $categoryKey speichert die Katorgorie, deren Kommentare geladen werden sollen.
  • Um die Business-Komponente mit der Methode getAndInitServiceObject() zur Übergabe der Kategorie aus der Präsentations-Schicht verwenden zu können muss diese eine init()-Methode implementieren.
  • loadEntries() ist der Prototyp der Lade-Methode, die die Einträge an die Präsentations-Schicht zurück gibt.

Kümmern wir uns nun um die Integration des Pager. Dieser wird mit Hilfe der PagerManagerFabric bezogen, der ein Initialisierungs-Parameter mitgegeben werden wird:

PHP-Code
$pMF = &$this->getServiceObject('modules::pager::biz', 'PagerManagerFabric'); $pM = &$pMF->getPagerManager('ArticleComments');

Die Konfiguration des Pager's ist unter dessen Namespace (modules::pager) und dem aktuellen Context der Applikation abgelegt. Dort befindet sich eine Datei mit dem Namen DEFAULT_pager.ini mit folgendem Inhalt:

APF-Konfiguration
[ArticleComments] Pager.EntriesPerPage = "5" Pager.ParameterPageName = "PgrPg" Pager.ParameterCountName = "PgrAnz" Pager.StatementNamespace = "modules::comments" Pager.CountStatement = "load_entries_count.sql" Pager.CountStatement.Params = "CategoryKey:standard" Pager.EntriesStatement = "load_entry_ids.sql" Pager.EntriesStatement.Params = "CategoryKey:standard" Pager.DesignNamespace = "modules::pager::pres::templates" Pager.DesignTemplate = "pager_2" Pager.DatabaseConnection = "MySQLx" Pager.AllowDynamicEntriesPerPage = "true"

Details zum APF-Konfigurations-Mechanismus können dem Kapitel Konfiguration entnommen werden.

Mit dem oben beschrieben Satz an Parametern kann der Pager vollständig konfiguriert werden. Als Vorlage dient uns die Datei /config/modules/comments/EXAMPLE_comments.ini aus dem apf-configpack-*-Release-Paket.

Die ersten drei Parameter betreffen die Anzahl der angezeigten Einträge pro Seite und die Namen der URL-Parameter. Im Weiteren werden die Statements konfiguriert, die der Pager zur Ausführung benötigt. Mit dem ersten Statement wird die Zahl der Einträge geladen, das zweite dient dem Auslesen der IDs der Einträge für die gewählte Seite. Die Parameter *.Params dienen dazu Standard-Werte für Statement-Parameter zu definieren.

Wie unter Konfiguration beschrieben, muss dem Dateinamen der Wert der Umgebungs-Variable vorangestellt und die Endung angehängt werden. Im Fall der SQL-Statements ist die Endung .sql. Die Dateien heißen deshalb

  • DEFAULT_load_entries_count.sql
  • DEFAULT_load_entry_ids.sql

Der Inhalt der beiden Statement-Dateien unterliegt einer gewissen Konvention was die Benennung der dynamischen Parameter angeht. Für das Laden der Anzahl der Einträge pro Kategorie ist folgender Inhalt notwendig:

SQL-Statement
SELECT COUNT(*) AS EntriesCount FROM article_comments WHERE CategoryKey = '[CategoryKey]' GROUP BY CategoryKey;

Der Pager erwartet, dass das Ergebnis der Abfrage in der Variable EntriesCount geliefert wird. Aus diesem Grund muss das Ergebnis mit einem Alias versehen werden. Die Kategorie wird bei der obigen Abfrage in der Variable CategoryKey geliefert und muss daher in eckigen Klammern notiert werden.

Das Statement zum Laden der Einträge gilt folgende Semantik:

SQL-Statement
SELECT ArticleCommentID AS DB_ID FROM article_comments WHERE CategoryKey = '[CategoryKey]' ORDER BY Date DESC, Time DESC LIMIT [Start],[EntriesCount];

Auch hier erwartet der Pager die Ergebnis-IDs in einer Variable: DB_ID. Diese wird ebenfalls über ein Alias realisiert. Die Kategorie, Start und Anzahl werden jeweils als Variable in eckigen Klammern notiert. Um die Anzahl zu limitieren wird die LIMIT-Anweisung mit den Parametern

  • [Start]
  • [EntriesCount]

gefüllt. Diese werden vom Pager gefüllt um jeweils nur diejenigen Einträge auszugeben, die für die aktuelle Seite vorgesehen sind. Neben diesen festen Bestandteilen, kann das Statement beliebig komplex formuliert werden.

Nachdem die Konfiguration für den Pager angelegt ist, können wir die Methode loadEntries() des Managers implementieren:

PHP-Code
public function loadEntries() { $pMF = &$this->getServiceObject('modules::pager::biz', 'PagerManagerFabric'); $pM = &$pMF->getPagerManager('ArticleComments'); $M = &$this->getServiceObject('modules::comments::data', 'commentMapper'); return $pM->loadEntriesByAppDataComponent($M, 'loadArticleCommentByID', array('CategoryKey' => $this->categoryKey)); }

Im ersten Schritt wird die gewünschte Pager-Instanz über die PagerManagerFabric bezogen. Im zweiten Schritt wird eine Instanz des Mappers der Applikation erzeugt, der anschließened dem Pager dazu dient, die Inhalte aus der Datenbank zu laden.

Alternativ zu dieser Implementierung ist es ebenfalls möglich, den Pager lediglich zu dazu zu nutzen, die IDs derjenigen Objekte zu laden, die für die aktuelle Seite relevant sind. Anschließend kann die Datenschicht-Komponente der aktuellen Applikation dazu genutzt werden um die zugehörigen Objekte zu laden:

PHP-Code
public function loadEntries(){ $pMF = &$this->getServiceObject('modules::pager::biz','PagerManagerFabric'); $pM = &$pMF->getPagerManager('ArticleComments'); $entries = $pM->loadEntries(array('CategoryKey' => $this->categoryKey)); $M = &$this->getServiceObject('modules::comments::data','commentMapper'); $entryList = array(); for($i = 0; $i < count($entries); $i++){ $entryList[] = $M->loadArticleCommentByID($entries[$i]); } return $entryList; }

2.5. Präsentations-Schicht

Die Präsentations-Schicht besteht hinsichtlich der Ausgabe lediglich aus einem View: der Liste der Einträge. Zunächst widmen wir uns der Einbindung, da die Kommentar-Funktion für den Entwickler sehr einfach mit einem <core:importdesign />-Tag einbindbar sein soll.

Weiterhin soll der Template-Entwickler eine Möglichkeit erhalten zu bestimmen, welche Kommentare angezeigt werden. Dazu führen wir ein neues Attribut categorykey ein. Dieses definiert die Kategorie derjenigen Kommentaren die angezeigt und eingetragen werden können.

Die Einbindung in ein bestehendes Template kann damit via

APF-Template
<core:importdesign namespace="modules::comments::pres::templates" template="comment" categorykey="****" />

realisiert werden. Das im Tag adressierte Template enthält im wesentlichen einen weiteren <core:importdesign />, der über die Anzeige des Formulars oder der Liste der Einträge entscheidet:

APF-Template
<core:importdesign namespace="modules::comments::pres::templates" template="[coview = listing]" incparam="coview" />

Ist der Parameter coview nicht in der URL vorhanden oder enthält den Wert listing, so wird die Ausgabe der Einträge angezeigt. Für die Ausgabe der Liste dient das Template listing.html:

APF-Template
<@controller namespace="modules::comments::pres::documentcontroller" class="CommentListingController" @> <div class="cm--list"> <div class="cm--list-head"> <html:getstring namespace="modules::comments" config="language" entry="listing.text.1" /> <a rel="nofollow" href="<html:placeholder name="Link" />#comments" title="<html:getstring namespace="modules::comments" config="language" entry="listing.text.2.title" />"><strong><html:getstring namespace="modules::comments" config="language" entry="listing.text.2" /></strong></a> <html:getstring namespace="modules::comments" config="language" entry="listing.text.3" /> </div> <div class="cm--list-pager"> <html:placeholder name="Pager" /> </div> <div class="cm--list-items"> <html:placeholder name="Content" /> </div> </div> <html:template name="ArticleComment"> <div class="cm--list-item"> <div class="cm--list-item-head"> <div class="cm--list-item-head-num"><template:placeholder name="Number" /></div> <div class="cm--list-item-head-date"> <span><template:placeholder name="Name" /></span> <em><template:placeholder name="Date" />, <template:placeholder name="Time" /></em> </div> <div style="clear: left;"></div> </div> <div class="cm--list-item-body"> <template:placeholder name="Comment" /> </div> </div> </html:template> <html:template name="NoEntries"> <div class="cm--list-noentries"> <template:getstring namespace="modules::comments" config="language" entry="noentries.text" /> </div> </html:template>

Im Template findet sich die Definition des zugehörigen Controllers, ein Einleitungs-Text, Platz für die Ausgabe des Pagers und die Ausgabe der Liste. Weiterhin ist ein Templates vorgesehen, das angezeigt wird, wenn keine Einträge vorhanden sind. Die Beschreibung der verwendeten Tags kann unter Standard TagLibs nachgelesen werden.

Die oben beschriebene Einbindung des Templates über das <core:importdesign />-Tag weist im Gegensatz zur Beschreibung des Standard-Tags die Besonderheit auf: die Definition der Kategorie.

Da der Page-Controller jede Template-Datei und jeden Tag als eigenständiges Dokument innerhalb des Objekt-Baums erzeugt, steht auch das Attribut categorykey im entsprechenden DOM-Objekt zur Verfügung. Wie der vorhergehenden Beschreibung der Template-Struktur zu entnehmen ist, wird das Template zur Ausgabe und Anzeige des Formulars nocheinmal im Haupt-Template des Moduls "abstrahiert", sprich als bedingtes Sub-Template eingebunden. Diese Struktur ist im DOM-Baum jeweils durch einen eigenen Knoten abgebildet.

Um nun aus dem Document-Controller heraus auf ein Attribut des Baumes zugreifen zu können, kann zunächst die Referenz auf den DOM-Baum genutzt werden von dem aus eine Navigation möglich ist. Da es sich hierbei um eine Vater-Kind-Beziehung von Template der Listen-Ausgabe zu einbindendem <core:importdesign />-Tag handelt kann das Attribut wie folgt bezogen werden:

PHP-Code
$parent = &$this->getDocument()->getParentObject(); $categoryKey = $parent->getAttribute('categorykey', 'standard');

Da diese Funktion in beiden Document-Controllern (Erzeugen der Liste, Eintragen eines Kommentars) genutzt wird, findet sich diese Funktion in einmn gemeinsamen Basis-Document-Controller wieder. Dieser wird in der Datei /apps/modules/coments/pres/documentcontroller/CommentBaseDocumentControllerntroller.php abgelegt und trägt den Namen CommentBaseDocumentController.

Der konkrete Document-Controller CommentListingController (siehe obiges Template) wird nun mit den Aufgaben betraut, die gewünschten Einträge und den Pager, bzw. ohne Kommentare eine entsprechende Meldung auszugeben. Dieser hat folgenden Inhalt:

PHP-Code
class CommentListingController extends CommentBaseDocumentController { public function transformContent() { $M = &$this->getAndInitServiceObject('modules::comments::biz', 'commentManager', $this->getCategoryKey()); $entries = $M->loadEntries(); $buffer = (string) ''; $template = &$this->getTemplate('ArticleComment'); $bP = &$this->getServiceObject('tools::string', 'AdvancedBBCodeParser'); $bP->removeProvider('standard.font.color'); $bP->removeProvider('standard.font.size'); $i = 1; foreach ($entries as $entry) { $template->setPlaceHolder('Number', $i++); $template->setPlaceHolder('Name', $entry->getName()); $template->setPlaceHolder('Date', date('d.m.Y', strtotime($entry->getDate()))); $template->setPlaceHolder('Time', $entry->getTime()); $template->setPlaceHolder('Comment', $bP->parseCode($entry->getComment())); $buffer .= $template->transformTemplate(); } if (count($entries) < 1) { $templateNoEntries = &$this->getTemplate('NoEntries'); $buffer = $templateNoEntries->transformTemplate(); } $this->setPlaceHolder('Content', $buffer); $this->setPlaceHolder('Pager', $M->getPager('comments')); $urlParams = $M->getURLParameter(); $this->setPlaceHolder('Link', LinkGenerator::generateUrl(Url::fromCurrent()->mergeQuery(array( $urlParams['PageName'] => '', $urlParams['CountName'] => '', 'coview' => 'form' ) ) ) ); } }

Wie der Code-Box entnommen werden kann werden die URL-Paramater des Pagers beim Generieren von Links zurückgesetzt. Dies wird durchgeführt, damit nach dem Erstellen eines Eintrags immer die erste Seite angezeigt wird.

3. Erweiterung der Software

Die Software, wie sie bis Ende des vierten Kapitels beschrieben wurde, könnte nun alle per PHPMyAdmin erzeugte Kommentare einer Kategorie auf einer Seite ausgeben, in die das Modul eingebunden ist. Ein Eintragen ist jedoch nicht möglich. Aus diesem Grund soll die Software um das Erstellen von Kommentaren schrittweise erweitert werden. Dazu gehen wir nun in der umgekehrten Reihenfolge vor und beginnen mit der Präsentations-Schicht.

3.1. Präsentations-Schicht

Unter 2.4. haben wir bereits die Möglichkeit vorgesehen einen durch den URL-Parameter coview gesteuerten View einzublenden. Der neu erstellte View für das Formular soll form heißen. Wie das Domänen-Objekt bereits vorgibt sollen vom Benutzer die Eingaben

  • Name
  • E-Mail
  • Kommentar

abgefragt werden. Der Formular-View gestaltet sich wie folgt:

APF-Template
<@controller namespace="modules::comments::pres::documentcontroller" class="CommentCreateEntryController" @> <core:addtaglib namespace="tools::form::taglib" class="HtmlFormTag" prefix="html" name="form" /> <div class="cm--create"> <div class="cm--create-head"> <html:getstring namespace="modules::comments" config="language" entry="formhint.text.1" /> <a href="<html:placeholder name="back" />#comments" title="<html:getstring namespace="modules::comments" config="language" entry="formhint.text.2.title" />"> <strong><html:getstring namespace="modules::comments" config="language" entry="formhint.text.2" /></strong></a> <html:getstring namespace="modules::comments" config="language" entry="formhint.text.3" /> </div> <div class="cm--create-form"> <html:form name="AddComment" method="post"> <span><form:getstring namespace="modules::comments" config="language" entry="form.name" />*</span> <form:text maxlength="100" name="Name" class="cm--create-element-name" /> <form:addvalidator class="TextLengthValidator" button="Save" control="Name|Comment" /> <br /> <span><form:getstring namespace="modules::comments" config="language" entry="form.email" />*</span> <form:text maxlength="100" name="EMail" class="cm--create-element-email" /> <form:addvalidator class="EMailValidator" button="Save" control="EMail" /> <form:addfilter class="EMailFilter" button="Save" control="EMail" /> <br /> <br /> <form:getstring namespace="modules::comments" config="language" entry="form.comment" /> <br /> <form:area name="Comment" class="cm--create-element-comment" cols="50" rows="6" /> <br /> <br /> <span><form:getstring namespace="modules::comments" config="language" entry="form.confirm" />*</span> <br /> <br /> <form:addtaglib namespace="modules::captcha::pres::taglib" class="SimpleCaptchaTag" prefix="form" name="captcha" /> <form:captcha text_class="cm--create-element-captcha" name="Captcha" clearonerror="true" /> <form:addvalidator namespace="modules::captcha::pres::validator" class="CaptchaValidator" control="Captcha" button="Save" /> <br /> <br /> <form:button name="Save" class="cm--create-element-button" /> </html:form> </div> </div>

Der zugehörige Document-Controller (CommentCreateEntryController) hat dabei die Aufgabe das Formular entsprechend auszugeben und das Absenden zu kontrollieren. Hat der Benutzer alle Eingaben hinsichtlich der Validierung korrekt getätigt, nutzt er die Business-Komponente zur Speicherung des Eintrags. Dazu implementiert der Manager eine neue Methode saveEntry(), der ein Domain-Objekt übergeben werden muss. Hier der Code des Controllers:

PHP-Code
class CommentCreateEntryController extends CommentBaseDocumentController { public function transformContent() { $form = &$this->getForm('AddComment'); if ($form->isSent() == true) { $M = &$this->getAndInitServiceObject('modules::comments::biz', 'commentManager', $this->getCategoryKey()); if ($form->isValid() == true) { $articleComment = new ArticleComment(); $name = &$form->getFormElementByName('Name'); $articleComment->setName($name->getAttribute('value')); $email = &$form->getFormElementByName('EMail'); $articleComment->setEmail($email->getAttribute('value')); $comment = &$form->getFormElementByName('Comment'); $articleComment->setComment($comment->getContent()); $M->saveEntry($articleComment); } else { $this->buildForm(); } } else { $this->buildForm(); } } private function buildForm() { $form = &$this->getForm('AddComment'); $form->setAttribute('action', $_SERVER['REQUEST_URI'] . '#comments'); $config = $this->getConfiguration('modules::comments', 'language.ini'); $button = &$form->getFormElementByName('Save'); $button->setAttribute('value', $config->getSection($this->language)->getValue('form.button')); $form->transformOnPlace(); $link = LinkGenerator::generateUrl(Url::fromCurrent()->mergeQuery(array('coview' => 'listing'))); $this->setPlaceHolder('back', $link); } }

3.2. Business-Schicht

In diesem Kapitel stellt sich nun die Aufgabe, die zuvor beschriebene Business-Schicht-Methode saveEntry() mit Leben zu füllen. Im Wesentlichen besteht die Aufgabe darin, den neuen Datensatz zu speichern und den Ausgabe-View anzuzeigen. Wie auch beim Laden der Daten muss dazu die Datenschicht-Komponente herangezogen werden. Dieser schreiben wir nun - ohne diese bereits implementiert zu haben - eine Methode saveArticleComment() zu, die wir in der Business-Schicht zur Speicherung des neuen Kommentars nutzen. Die Weiterleitung erledigt ein einfacher Redirect auf den Anzeige-View. Hier muss natürlich darauf geachtet werden, dass die erzeugte Seite auch wieder korrekt angezeigt wird.

PHP-Code
public function saveEntry(ArticleComment $articleComment) { $M = &$this->getServiceObject('modules::comments::data', 'commentMapper'); $articleComment->setCategoryKey($this->categoryKey); $M->saveArticleComment($articleComment); $link = LinkGenerator::generateUrl(Url::fromCurrent()->mergeQuery(array('coview' => 'listing'))); HeaderManager::forward($link . '#comments'); }

Wie Zeile 5 ($articleComment->setCategoryKey(...)) zeigt, manipuliert die Business-Schicht das Domain-Objekt, damit dieses mit der korrekten Kategorie gespeichert wird. Anschließend wird - wie bereits für die Generierung des Links für das Formular - der LinkGenerator::generateUrl verwendet.

3.3. Datenschicht

Die Datenschicht muss nun noch die Methode saveArticleComment() implementieren:

PHP-Code
public function saveArticleComment(ArticleComment $comment) { $conn = &$this->getConnection(); if ($comment->getId() == null) { $insert = 'INSERT INTO article_comments (Name, EMail, Comment, Date, Time, CategoryKey) VALUES (\'' . $conn->escapeValue($comment->getName()) . '\', \'' . $conn->escapeValue($comment->getEmail()) . '\', \'' . $conn->escapeValue($comment->getComment()) . '\', CURDATE(), CURTIME(), \'' . $comment->getCategoryKey() . '\');'; $conn->executeTextStatement($insert); } }

Die Bedeutung der Code-Zeilen lässt sich leicht erschließen: zunächst wird eine Instanz der jeweils konfigurierten Datenbank-Connection über den ConnectionManager bezogen. Anschließend werden die Daten nach einer Prüfung in in eine flache, relationale Strukur zerlegt und per SQL-Statament gespeichert.

4. Ausblick

Anschließend an das aktuelle Tutorial empfiehlt sich der Artikel Objektorientiertes Design eines Gästebuchs als Lektüre.

Hinsichtlich der Implementierung sei an dieser Stelle nochmals darauf hinweisen, dass die Methoden getServiceObject() und getAndInitServiceObject() des ServiceManager die jeweils verwendeten Komponeten mit den notwendigen Informationen (z.B. Sprache, Context, ...) versorgen. Es ist daher für die Nutzung von Service-Komponenten stets ratsam, den ServiceManager zur Erzeugung zu nutzen um durchgängig Zugriff auf die genannten Eigenschaften zu haben.

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.