Inhaltstypen
29.03.2016

TYPO3 Flow – Applikation mit Dynamischen Attributen 3/3

Dynamische Reports und individuelle Datenabfragen mit dem DQL-Generator

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.

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.

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 dieses Blog-Artikels 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.

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).

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

Diese Templates sind nötig, um den DQL-Generator laufen zu lassen. Ein TYPO3-Fluid-Layout, basierend auf Twitter Bootstrap, wird vorausgesetzt.
CODEReporte anzeigen (Templates/Report/Index.html)
CODEEinen neuen Report generieren (Templates/Report/New.html)
CODEEinen Report bearbeiten (Templates/Report/Edit.html)
CODE Einen Report anzeigen (Templates/Report/Show.html)
CODEHilfetext für Datenquelle (Templates/Report/FromQuery.html)
CODEHilfetext für Verknüpfungen (Templates/Report/JoinQuery.html)
CODEHilfetext für Felder (Templates/Report/SelectQuery.html)
CODEHilfetext 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.

letzte 4 Blog-Beiträge

KI-gestützte Auftragserfassung: Wenn PDF-Bestellungen automatisch ins ERP finden

Viele Unternehmen erfassen Bestellungen aus E-Mails und PDFs noch immer manuell – obwohl genau hier enormes Automatisierungspotenzial liegt. Unser Referenzprojekt mit ASTORtec zeigt, wie KI-gestützte Auftragserfassung unstrukturierte PDF-Bestellungen ausliest, prüft und direkt ans ERP übergibt. Ein...

Hyvä für Magento: Wenn das Frontend endlich kein Bremsklotz mehr ist

Viele Magento-Shops leiden unter einem veralteten, komplexen Frontend, das Performance, Core Web Vitals und Conversion ausbremst. Hyvä setzt hier an und macht Magento mit einem modernen, schlanken Frontend wieder schnell, wartbar und nutzerfreundlich. In diesem Beitrag zeigen wir, wann sich Hyvä...

Webclient-Entwicklung für komplexe Business Software

Moderne Webclients bringen komplexe Business Software direkt in den Browser – schnell, sicher und einfach bedienbar. Am Beispiel des ARTS Webclients für Interact/SER/Doxis zeigt insign, wie bestehende Systeme mit React, sauberer API-Integration und durchdachter Benutzerführung zukunftsfähig gemacht...

Magento zu Shopify: Wann sich der Wechsel lohnt

RAUSCH hat den bisherigen Magento-2-Shop durch eine flexible Shopify-Lösung ersetzt – mit weniger technischem Aufwand, mehr Eigenständigkeit im Marketing und deutlich besseren E-Commerce-Resultaten. Das Referenzprojekt zeigt, wann sich der Wechsel von Magento zu Shopify lohnt und worauf es bei...

diesen Beitrag teilen