TYPO3: Zehn Tipps & Tricks für Extbase und Fluid
Fluid-Snippets in Netbeans
Mag. Artikel Heft 24
Bei der Arbeit mit Fluid fallen zwei Dinge sofort auf: Es ist sehr viel Code nötig, und durch die zunächst ungewöhnliche Notation entstehen immer wieder Fehler, die man schwer findet. Da wäre eine Art Snippet-Verzeichnis praktisch, mit dem man oft benötigte Code-Fragmente auf Knopfdruck einfügen kann. Zumindest für die beliebte IDE Netbeans [1] gibt es nun etwas Derartiges – so genannte Code Templates [2]. Eine überarbeitete und erweiterte Version der Datei findet sich außerdem bei Typovision [3].
Code | Code-Fragment |
alias | <f:alias map=“{key:value}“> |
base | <f:base /> |
ts bzw. cobject | <f:cObject typoscriptObjectPath=“path“></f:cObject> |
count | <f:count subject=“{objects}“ /> |
cycle | <f:cycle values=“{array}“ as=“value“></f:cycle> |
debug | <f:debug title=“debugname“>{debug_var}</f:debug> |
esc bzw. escape | <f:escape>text</f:escape> |
flash | <f:renderFlashMessages /> |
for | <f:for each=“{array}“ as=“value“ key=“key“></f:for> |
crop | <f:format.crop maxCharacters=“15″ append=“ […]“></f:format.crop> |
currency | <f:format.currency currencySign=“sign“ decimalSeparator=“dsep“ thousandsSeparator=“tsep“></f:format.currency> |
date | <f:format.date format=“d.m.Y-H:i:s“></f:format.date> |
html | <f:format.html parseFuncTSPath=“lib.parseFunc“></f:format.html> |
nl2br | <f:format.nl2br></f:format.nl2br> |
number | <f:format.number decimals=“decimals“ decimalSeparator=“dsep“ thousandsSeparator=“tsep“></f:format.number> |
padding | <f:format.padding padLength=“length“ padString=“string“></f:format.padding> |
printf | <f:format.printf arguments=“arguments“></f:format.printf> |
form | <f:form action=“action“ controller=“controller“ object=“{object}“ name=“name“ arguments=“arguments“ ></f:form> |
checkbox | <f:form.checkbox property=“property“ checked=“{condition}“ /> |
errors | <f:form.errors for=“for“>{error.propertyName} {error.message} {error.code}</f:form.errors> |
hidden | <f:form.hidden name=“name“ value=“value“ /> |
password | <f:form.password property=“property“ /> |
radio | <f:form.radio property=“property“ checked=“{condition}“ /> |
select | <f:form.select property=“property“ options=“{key:value}“ optionLabelField=“labelfield“ optionValueField=“valuefield“ /> |
submit | <f:form.submit value=“value“ /> |
textarea | <f:form.textarea property=“property“ rows=“rows“ cols=“cols“ /> |
textfield bzw. input | <f:form.textfield property=“property“ /> |
upload | <f:form.upload property=“property“ /> |
group | <f:groupedFor each=“{array}“ as=“items“ groupBy=“group_by“ groupKey=“group_key“>{group_key}:<f:for each=“{items}“ as=“value“ key=“key“></f:for> </f:groupedFor> |
if | <f:if condition=“{condition}“></f:if> |
ife | <f:if condition=“{condition}“> <f:then></f:then><f:else></f:else></f:if> |
img bzw. image | <f:image src=“source“ alt=“alt“ /> |
layout | <f:layout name=“name“ /> |
section | <f:section name=“name“></f:section> |
render | <f:render partial=“name“ arguments=“arguments“ /> |
link | <f:link.action action=“action“ controller=“controller“></f:link.action> |
linka | <f:link.action action=“action“ controller=“controller“ arguments=“{key:value}“></f:link.action> |
extlink | <f:link.external uri=“url“ target=“_blank“></f:link.external> |
<f:link.email email=“email“ /> | |
page | <f:link.page pageUid=“uid“></f:link.page> |
securityifa | <f:security.ifAuthenticated“><f:then></f:then></f:security.ifAuthenticated“> |
securityifh | <f:security.ifHasRole“ role=“role“> <f:then></f:then></f:security.ifHasRole“> |
trans bzw. translate | <f:translate key=“key“ /> |
Nach dem Download lassen sich die Templates in Netbeans über „Einstellungen, Import“ laden. In den Import-Optionen reicht ein „All“ und „OK“. Nach einem Neustart stehen die Code-Templates zur Verfügung. Um nun ein Code-Template zu verwenden, reicht in einer Fluid-Datei (Typ: HTML) ein Code-Schnipsel aus der abgedruckten Tabelle. Ein anschließender Klick auf Tab fügt das zugehörige Fragment ein.
String-Vergleiche in Fluid
Im If-ViewHelper von Fluid kann man über die Eigenschaft „condition“ nahezu alles vergleichen – Zahlen (kleiner, größer, gleich), Booleans (TRUE, FALSE) etc. – nicht jedoch auf Gleichheit mit einem String. Einem Bugtracker-Eintrag zufolge sei das schwierig zu implementieren.
Aber gerade eine solche Abfrage braucht man in der Praxis relativ oft. Man kann nun anfangen, über den Alias-ViewHelper eben einen Alias für den abzufragenden String zu erstellen und gegen diesen zu prüfen. Das bringt allerdings zwei Nachteile mit sich: Für jeden abzufragenden String müsste der in Frage kommende Programmteil mit einem Alias-ViewHelper ummantelt werden. Außerdem wird der Code unübersichtlich, da an der Stelle der Überprüfung nun nicht mehr der Prüfstring selbst, sondern der Alias verwendet wird – beides also suboptimal. Auch das Schreiben eines eigenen ViewHelper scheint nicht adäquat.
Aber es geht auch einfacher: Die beiden Operatoren müssen dazu lediglich in ein Array verwandelt werden und schon funktioniert der Vergleich wie gewünscht.
<f:if condition="{0:blog.title} == {0:'Testtitel'}"> Funktioniert: Der Titel lautet 'Testtitel'. </f:if>
Listing 1
Kommentare im ViewHelper
Um im anfänglichen Code-Gewusel der Views den Überblick nicht zu verlieren, eignen sich Kommentare zur Strukturierung. Da man sich aber letztlich in HTML befindet, ist es eben nur möglich, HTML-Kommentare zu verwenden. Diese allerdings sind im Quellcode selbst sichtbar.
Eine andere Lösung muss also her. Hier bietet es sich an, einen eigenen ViewHelper zu erstellen, der nichts anderes macht, als Kommentare nicht anzuzeigen: Nach der Installation der Extension „efempty“ – diese enthält ein Extbase/Fluid-Grundgerüst – wird der ViewHelper um eine Klasse erweitert. Im Verzeichnis Classes/ViewHelpers/ genügt dafür eine Datei CommentViewHelper.php mit folgendem Inhalt:
<?php class Tx_Efempty_ViewHelpers_CommentViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper { public function render() { return ''; } } ?>
Listing 2
Anschließend reicht überall dort, wo der ViewHelper verwendet wird, eine Namespace-Anweisung am Anfang der Datei.
{namespace tv = Tx_Efempty_ViewHelpers} ... <tv:comment> Hier können Sie nun beliebig kommentieren. Sie können sogar andere ViewHelper auskommentieren! :-)</tv:comment>
Listing 3
Übersetzte Fehlermeldungen
Wer mittels „<f:form.errors>{error.propertyName} {error.message} {error.code}</f:form.errors>“ Fehlermeldungen ausgibt, wird schnell feststellen, dass diese lediglich in englischer Sprache erscheinen. Übersetzen lassen sich die Ausgaben nicht, denn bislang sind sie hart kodiert (dies soll sich zukünftig ändern).
Um dieses Problem zu lösen, ist etwas Fantasie nötig: Für jeden Fehler gibt es einen eindeutigen Code. Dieser besteht laut Vereinbarung aus dem Timestamp, an dem der Fehler „erfunden“ wurde. Daher kann davon ausgegangen werden, dass es in einem System nur einen Fehler mit eben dieser Meldung gibt. Somit kann über den Translate-ViewHelper und den Code als Schlüssel die Fehlermeldung übersetzt werden.
<languageKey index="de" type="array"> <label index="1221560718">Der Wert ist leer!</label> </languageKey>
Listing 4
<f:translate key="{error.code}" />
Listing 5
Default-Sortierung im Repository
Nicht unbedingt unbekannt, aber weitestgehend ungenutzt ist ein Feature, welches in Extbase 1.3 hinzugekommen ist: Im Repository besteht die Möglichkeit, das Ergebnis nach bestimmten Feldern aufsteigend oder absteigend zu sortieren. Dies wird beispielsweise mittels „$query->setOrderings(array(’name‘ => Tx_Extbase_Persistence_QueryInterface::ORDER_DESCENDING));“ erledigt.
Um dies nicht für jede einzelne Repository-Funktion einstellen zu müssen, gibt es die Eigenschaft defaultOrderings:
protected $defaultOrderings = array ('name' => Tx_Extbase_Persistence_QueryInterface::ORDER_DESCENDING);
Listing 6
Eigenes Template im Controller
Normalerweise ist in Extbase und Fluid immer alles genau geregelt – „Convention over Configuration“ lautet das Motto und das macht durchaus Sinn. So ist beispielsweise festgelegt, dass im Controller automatisch der View (und damit zunächst die HTML-Datei) ermittelt wird, der zum Controller und zur Action passt. Eine List-Action des Blog-Controllers (List.html) erwartet das Template somit im Verzeichnis Resources/Private/Templates/Blog/.
Nun kann es aber durchaus sein, dass man von dieser Vorgabe abweichen und ein eigenes Template definieren möchte – beispielsweise damit ein eingeloggter Administrator ein anderes Template erhält als der normale User. Dies kann über die Methode setTemplatePathAndFilename wie folgt realisiert werden:
public function listAction() { ... $this->view->setTemplatePathAndFilename( 'typo3conf/ext/' . $this->request->getControllerExtensionKey() . '/Resources/Private/Templates/Blog/NeuesTemplate.html' ); ... $this->view->assign(...); }
Listing 7
Individuelle SQL-Abfragen
Extbase besitzt einen sehr leistungsfähigen Query-Manager. Dieser baut Abfragen so zusammen, wie es SQL eben passt – getreu dem Motto „Don’t care about databases“. Tatsächlich gibt es im Domain Driven Design (der Philosophie, die hinter FLOW3 und damit auch hinter Extbase steht) keine Datenbanken, sondern nur „Repositories“.
Damit wird die technische Realisierung der Speicherung letztlich außerhalb der Domäne transportiert. Folgerichtig befindet sich in der Domäne kein SQL – denn dies funktioniert ja nur mit einer Datenbank. Die Daten könnten aber auch im Filesystem oder in einem Webservice gespeichert sein.
Im Alltag wird aber nach wie vor eine MySQL-Datenbank verwendet werden und so existiert auch in Extbase eine Möglichkeit, Datenbanken direkt anzusprechen. Dies kann zum Beispiel dann von Nutzen sein, wenn komplexe Queries für statistische Auswertungen oder auf Performance optimierte Befehle abgesetzt werden sollen.
Damit dies allerdings gelingt, muss der interne Mechanismus ausgeschaltet werden, mit dem Extbase probiert, aus dem Query-Result wieder fertige Objekte zu konstruieren. Hier kommen die so genannten Query-Settings und mit ihnen die Statement-Methode der Query-Factory ins Spiel:
public function findRaw(){ $query = $this->createQuery(); $query->getQuerySettings()->setReturnRawQueryResult(TRUE); $query->statement('SELECT * from tx_simpleblog_domain_model_blog'); return $query->execute(); }
Listing 8
Es wird nun statt des Objekt-Interfaces ein Array mit dem Ergebnis der Datenbankabfrage zurückgeliefert. Allerdings verliert Fluid damit seine Aufwärtskompatibilität zu FLOW3. Nun noch Controller und View angepasst und schon steht dem Datenbankzugriff nichts mehr im Weg:
$this->view->assign('result',$this->blogRepository->findRaw());
Listing 9
<f:for each="{result}" as="row"> {row.title}<br /> </f:for>
Listing 10
Settings-Zugriff
Extbase macht es einem wirklich leicht. Unter anderem kann direkt auf TypoScript/Flexform-Settings zugreifen, wer sich an ein paar einfache Regeln hält:
plugin.tx_[lowercasedextensionname] { settings { setting1 = wert1 } }
Listing 11
<T3DataStructure> ... <el> <settings.setting1> ... </settings.setting1> </el> ... </T3DataStructure>
Listing 12
So lässt sich über {settings.setting1} im View beziehungsweise $this->settings[’setting1′] im Controller auf die Werte zugreifen. Was aber, wenn im Repository, im ViewHelper oder im Validator diese Settings gebraucht werden? Wirklich elegant geht dies nur im Repository, wenn der Controller aus der Repository-Methode das Setting direkt als Parameter mitgibt. Alternativ (wenn auch nicht offiziell) klappt es auch über den Configuration-Manager und der seit Extbase 1.3 hinzugefügten Dependency Injection:
class Tx_Efempty_ViewHelpers_TestViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper { protected $configurationManager; public function injectConfigurationManager(Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager) { $this->configurationManager = $configurationManager; } public function render() { // Hier werden nun die Settings ermittelt. // Der erste Parameter von getConfiguration muss 'Settings' lauten. // Der zweite Parameter beinhaltet den Namen der Extension. // Der dritte Parameter beinhaltet den Namen des Plugins. $settings = $this->configurationManager->getConfiguration('Settings','Efempty','Pi1'); ... } }
Listing 13
GET/POST Vars
Wenn schon mit Extbase und Fluid programmieren, dann auch ausschließlich. Methoden der „klassischen“ API – also etwa t3lib_div::_GP(‚myGPVar‘) – sollten tabu sein. Wie aber werden dann die GET- beziehungsweise POST-Variablen genutzt?
Auch das ist im Grunde sehr einfach: Die Variable muss einfach automatisch zur Verfügung stehen. Dafür muss schlicht der Namespace passen. Dieser wird automatisch hinzugefügt, wenn beispielsweise ein Feld in einem Formular mit dem entsprechenden ViewHelper anlegt wird. Da keine Zuordnung zum Model benötigt wird, kann auch auf den Property-Parameter verzichtet werden. Im Controller kann nun die Variable über die Annotation registriert und innerhalb der Methode darauf zugegriffen werden. Das Beispiel zeigt ein Suchformular:
<f:form method="post" action="index"> <f:form.textfield value="{search}" name="search" /> <f:form.submit value="Suchen" /> </f:form> ... /** * @param string $search */ public function indexAction($search='*') { $this->view->assign('search', $search);
Listing 14
Der Zugriff auf die GET/POST-Variablen kann zudem über $this->request->hasArgument($var) und $this->request->getArgument($var) erfolgen.
Migration zu FLOW3
Wer nur die Boardmittel von Extbase und Fluid verwendet (statt „alter“ TYPO3-API-Befehle), der kann seine Extbase-Extension auch unter FLOW3 verwenden – so die Theorie. In der Praxis funktioniert das aber nur unter folgender Anleitung.
Ausgegangen wird von der Extension „efempty“, die zunächst aus dem TER installiert werden muss. Zusätzlich wird Phoenix Sprint Release 6 (oder FLOW3 Alpha 14 bzw. neuer) installiert – eine Anleitung hierfür bieten diverse Blogs [4].
Jetzt geht es ans Kopieren: Das Verzeichnis typo3conf/ext/efempty (TYPO3) in das Verzeichnis Packages/Application/Efempty (FLOW3, großer Anfangsbuchstabe). Sicherlich wird es in absehbarer Zeit Migrationsskripte geben – aber bis es soweit ist, müssen einige Änderungen manuell durchgeführt werden.
<?php declare(ENCODING = 'utf-8'); namespace F3\Efempty\Controller; ... class StartController extends \F3\FLOW3\MVC\Controller\ActionController { ... // Instanzierung $start = new \F3\Efempty\Domain\Model\Start;
Listing 15
Allen Klassen (also allen Dateien im Classes-Verzeichnis) müssen Namespace-Deklarationen vorangestellt werden. Diese beginnen (hier) immer mit F3 und darauf folgen alle Verzeichnisse mit einem „\“ getrennt. Dafür können anschließend alle Klassennamen verkürzt werden auf den eigentlichen Namen. Bei den abgeleiteten Klassen wiederum muss der Pfad mit angegeben werden – anders als in Extbase ebenfalls mit F3 beginnend und die Pfadbestandteile mit „\“ getrennt.
Bei der Domain-Model-Klasse kann sogar ganz auf die Erweiterung der abstrakten Klasse verzichtet werden, hier reicht die entsprechende Annotation (@entity bzw. @valueobject).
<?php declare(ENCODING = 'utf-8'); namespace F3\Efempty\Domain\Model; ... // @entity class StartController extends \F3\FLOW3\MVC\Controller\ActionController { ... }
Listing 16
Nun fehlt noch eine Route für die Extension in der Datei Configuration/Routes.yaml:
name: 'Efempty' uriPattern: 'efempty' defaults: '@package': 'Efempty' '@controller': 'Start' '@action': 'index' '@format': 'html'
Listing 17
Abschließend muss die (nun Package genannte) Extension im System aktiviert werden. Im besten Fall funktioniert das mit dem Kommandozeilentool von FLOW3; alternativ reicht ein entsprechender Eintrag in der Datei Configuration/PackageStates.yaml.
./flow3 package activate Efempty
Listing 18
Bei einem Aufruf via http://IhreDomain/efempty zeigt sich, dass die Extension (Package) jetzt auch unter FLOW3 läuft.
Schöne Übersicht, ein paar Punkte sind sehr hilfreich bei Problemen die bisher echt Nerven gekostet haben. Die verfügbaren Extbase/Fluid-Dokus sind ja insgesamt etwas mager und wiederholen sich auf einem bestimmten Niveau.
Hier sind aber endlich mal ein paar neue Beispiele – vielen Dank!
Das Code Template von Typovision enthält einen Fehler. Das Image Tag hat kein Argument source, sondern src.
Settings-Zugriff im Viewhelper mit Namespaces (getestet in TYPO3 6.1):
/**
* @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
* @inject
*/
protected $configurationManager;
/**
* Injects the configuration manager, retrieves the plugin settings from it
*
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
* @return void
* @override \TYPO3\CMS\Extbase\Mvc\Controller\AbstractController
**/
public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager) {
$this->configurationManager = $configurationManager;
$settings = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS);
$this->settings = $settings;
}