Inhaltstypen
Pages
Posts
29.03.2016

TYPO3 Flow – Applikation mit Dynamischen Attributen 3/3

DQL-Generator

Die in TYPO3 Flow verwendete Datenbankabstraktion namens Doctrine, erlaubt das Absetzen von Queries mittels der eigens entwickelten Sprache DQL. Hierbei werden im Unterschied zu herkömmlichen Query-Sprachen nicht die Datenbank und deren Tabellen, sondern die Objekte in der Applikation abgefragt. Es handelt sich also um eine Abstraktion der herkömmlichen Abfragesprachen.

In diesem Teil wird es vor allem PHP-Codes zu lesen geben, welche im Anschluss jeweils kurz erklärt werden. Dies bedeutet nicht, dass wir auf jede Kleinigkeit eingehen werden, da dies erstens einen ellenlangen Beitrag zur Folge hätte und zweitens davon ausgegangen wird, dass der Leser PHP- und TYPO3 Flow-Know-how mitbringt.

  • TYPO3 Flow
  • Vergleich der Frameworks

DQL in TYPO3 Flow

Der Einsatz von DQL in Flow ist einfach und schnell erklärt:

<?php /** @var \Doctrine\ORM\Query $query */ $query = $this->objectManager->get("Doctrine\Common\Persistence\ObjectManager")->createQuery("SELECT product FROM MyCompany\MyApplication\Domain\Model\Product product");
$this->results = new \Doctrine\Common\Collections\ArrayCollection($query->execute());

Es muss also zuerst ein DQL-Query-Objekt erstellt werden mit der entsprechenden Abfrage. Diese wird dann ausgeführt mit $query->execute, und in diesem Fall einer ArrayCollection hinzugefügt.

Weitere Informationen zu DQL-Syntax und -Funktionen finden Sie hier:

Entsprechendes Fachwissen wird hier aber nicht vorausgesetzt.

  • DQL

Konzept

Ein DQL-Generator ist im Prinzip keine Anwendung, welche nur mit dynamischen Attributen Sinn macht, sondern kann genau so gut auch ohne diese funktionieren. Sie ist dann sogar um einiges einfacher zu programmieren. Im Zuge dieser Blog-Reihe gehen wir aber verständlicherweise von dynamischen Attributen aus. Ausserdem ist es nur beschränkt nötig, dynamische Reporte zu generieren, wenn die Klassenattribute selber nicht dynamisch sind.

Wir wollen also eine Seite, wo unsere Benutzer folgende Eingaben machen können:

  1. Name des Reports
  2. Datenquelle (entspricht einer oder mehreren Model-Klasse/n)
  3. Verknüpfungen (JOIN-Operationen)
  4. Anzuzeigende Felder der Datenquelle und deren Verknüpfungen
  5. Bedingungen/Filter
  6. Sortierung
DQL-Generator im Praxistool

Nun wird das Ganze ein bisschen komplizierter…

Der Name des Reports stellt kein Problem dar, diesen können wir wie gewohnt mit Flow auf dem Objekt speichern. Danach wird es bereits komplizierter: Woher wissen wir, welche Datenquellen verfügbar sind? Wir werden dazu die Reflection Services von Flow benutzen (aber noch ein bisschen erweitern).

Top-7-Tools-for-Analyzing-and-Parsing-Your-PHP-Code

Dasselbe gilt für die anderen Werte, welche wir benötigen. Zusätzlich gibt es aber noch Syntaxelemente, welche wir statisch einfügen müssen. Dabei handelt es sich um Elemente wie “ist grösser als” oder “ist gleich” und so weiter. Ausserdem kann es bei den Bedingungen Freitext-Werte geben, welche ebenfalls behandelt werden müssen.
Gleichermassen gibt es unzählige weitere Spezialfälle wie Datumsfelder, was aber aus Gründen der Übersicht ebenfalls nicht hier behandelt wird.

Wie also sollen wir all dies dem User anzeigen, ohne diesen gänzlich zu verwirren?

Die Idee ist, dass wir bei der Auswahl der Datenquelle nicht den für den Anwender unschönen Wert “\MyCompany\MyApplication\Domain\Model\Product”, sondern nur “Produkt” anzeigen. Dasselbe gilt natürlich für alle Attribute. Für Dynamische können wir die im letzten Teil besprochenen DataFields verwenden, da dort der Name des Attributs für die Ausgabe gespeichert ist. Für alle Anderen und für die Datenquellen benutzen wir ein Translation-File und die Übersetzungsfunktionalität von TYPO3 Flow.

Und auch weiterhin werden wir den Kunden natürlich unterstützen.

Ausserdem wollen wir, dass der Nutzer weiss, was er eingeben kann. So können wir mit Autovervollständigung (z.B. typeahead.js von Twitter) und Hilfefenstern (z.B. in Form von Modals) ihn dabei unterstützen. Nach der Eingabe werden die validierten Werte nicht als Freitext, sondern als so genannte Tags gespeichert (siehe z.B. https://github.com/bootstrap-tagsinput/bootstrap-tagsinput). So können Tippfehler oder versehentliches Löschen von einzelnen Buchstaben verhindert werden.

Trotzdem: Uns muss bewusst sein, dass ein gewisses Verständnis und Technikaffinität vorhanden sein muss, um diesen Generator zu bedienen.

Model

Wir brauchen für den DQL-Generator zwei Klassen:

  • Report – Speichert die Informationen zum Abfrage-Report und einen Link zu einem DqlStatement-Objekt
  • DqlStatement – Alle DQL-Queries und Informationen dazu werden auf diesem Objekt gespeichert

Die Trennung ist nicht zwingend erforderlich, da es sich hierbei um eine 1:1-Beziehung handelt. Trotzdem ist es der Übersicht halber sinnvoll.

CODE

https://gist.github.com/randm-ch/fa731a0a5d8fd3c422ff (Report.php)
Hier werden bei der Methode getResults() auch die Ergebnisse des Reports aus der Datenbank gelesen.
Da es möglich ist, dass gewisse Attributnamen bei mehreren Objekten vorkommen, wird bei der Abfrage jeweils eine Nummer angehängt. Diese wird bei der Anzeige mittels der Methode getPropertyNames() wieder entfernt.

CODE

https://gist.github.com/randm-ch/d95f35aa1f04d1ed745c (DqlStatement.php)
Die generate()-Methode ist hier der Zentrale Punkt. Sie erstellt die DQL-Statements aus den Formularwerten. Ausserdem werden in dieser Klasse die statischen Werte wie LEFT JOIN oder IS NULL definiert.

Controller

CODE

https://gist.github.com/randm-ch/66e7bcfada8d85608fcc (ReportController.php)
Im Controller müssen zweierlei Dinge erledigt werden:

  1. CRUD der Reports
  2. Laden der Zusatzinformationen (Helper-Modal-Informationen, Vorschläge für Auto-Suggestion)

Ausserdem gibt es hier zwei Helper-Methoden:

  1. getAllJoinableTags() – Liefert die Namen aller Klassen, mit den aktuell ausgewählten Datenquellen verknüpft werden können
  2. getAllTags() – Liefert alle Attribute der Datenquellen und deren Verknüpfungen (mit Pfad, z.B. product.category.name)

Utilities

Dem neugierigen Leser ist im Controller-Code aufgefallen: Eine weitere Klasse wird dringend benötigt. Richtig,  \MyCompany\MyApplication\Utility\DataClassReflection wird als Nächstes behandelt.

CODE

https://gist.github.com/randm-ch/adcacf8b9390aa563b38 (DataClassReflection.php)

In getForbiddenProperties() können Sie definieren, welche Attribute dem Benutzer nicht zur Auswahl stehen. Die drei im Code Implementierten sind mehr oder weniger Pflicht, ansonsten könnten Infinite-Loops erzeugt werden.

Ausserdem müssen wir, um den Code zum funktionieren zu bewegen, die TagsinputTag-Klasse entwickeln. Hierbei handelt es sich um einen Wrapper, welcher einen Tagsinput-Tag beschreibt:

CODE

https://gist.github.com/randm-ch/b535a380caa1a21d5d7e (TagsinputTag.php)
Ein Tag hat jeweils einen Typ, möglich sind folgende:

  • Freitext (für Bedinungen)
  • Attribut (einer Klasse, z.B. user.email)
  • Verknüpfung (JOIN Operationen)
  • Bedingungsoperation (gleich, ungleich, grösser/kleiner als, …)
  • Bedingungsverbindung (und/oder)
  • Funktion (DQL-Funktionen wie CURRENT_TIMESTAMP(), IS NULL etc.)
  • Sortierung aufsteigend
  • Sortierung absteigend

Der Typ definiert, in welchem Input-Feld der Tag eingesetzt werden kann und welche Farbe er hat. Mehr dazu weiter unten in der View-Beschreibung.

Translation

In der TagsinputTag-Klasse wird wiederum ein TagsinputTagInjector verwendet. Dieser wird benötigt, um die Übersetzungsfunktionalität von TYPO3 Flow zu verwenden, damit wir dem User nicht unsere Klassennamen und Attributnamen (normalerweise in Englisch) anzeigen müssen, sondern übersetzte Strings.

CODE

https://gist.github.com/randm-ch/297cef9159b49c59d2b1 (TagsinputTagInjector.php)

View

Templates

Diese Templates sind nötig, um den DQL-Generator laufen zu lassen. Ein TYPO3-Fluid-Layout, basierend auf Twitter Bootstrap, wird vorausgesetzt.
CODE Reporte anzeigen (Templates/Report/Index.html)
CODE Einen neuen Report generieren (Templates/Report/New.html)
CODE Einen Report bearbeiten (Templates/Report/Edit.html)
CODE Einen Report anzeigen (Templates/Report/Show.html)
CODE Hilfetext für Datenquelle (Templates/Report/FromQuery.html)
CODE Hilfetext für Verknüpfungen (Templates/Report/JoinQuery.html)
CODE Hilfetext für Felder (Templates/Report/SelectQuery.html)
CODE Hilfetext für Bedingungen (Templates/Report/WhereQuery.html)

JavaScript

jQuery wird vorausgesetzt, um die Tagsinput-Library verwenden zu können.

CODE

https://gist.github.com/randm-ch/55b2b069c07fc62d7caf (Scripts/tagsinput.js)
Dieses Script aktiviert die Autosuggest-Funktion, erstellt ein Tag – mit entsprechendem Typ, wie oben beschrieben – und fügt dieses in das entsprechende Input-Feld

Abschliessend

Was haben wir erreicht?

  • Attribute von Datenobjekten können dynamisch erweitert werden
  • Diese Daten können ausgelesen und geschrieben werden
  • Wir können anhand dieser Daten Reports erstellen

Damit sind alle Arbeiten abgeschlossen und der Generator kann nun verwendet werden.