Große Datenmengen mit Extbase verarbeiten

Extbase ist an sich eine schöne Sache: Man kann mit relativ geringem Aufwand auch komplexe Anwendungen schreiben. Ein großes Problem ist jedoch die Effizienz. Das vollständige Auslesen von Objekten aus einer Datenbank in ein Array, um diese nachfolgend zu bearbeiten, ist speicherintensiv und merklich langsam. Möchte man einfach eine lange Liste an Logs ausgeben, fällt das besonders auf. Ich möchte hier einen eleganten Weg zeigen, wie man möglichst direkt jede Datenbankzeile in die Ausgabe schreibt.

Das Ziel

Wir möchten eine Liste von Logs im Browser anzeigen. Da Sortierung und Seiteneinteilung von Javascript übernommen werden, möchten wir direkt 1000 Zeilen der Datenbanktabelle in die HTML-Tabelle übertragen. Die Tabelle soll dabei möglichst schnell ausgeliefert werden.

Der Ablaufplan


  
    
    
    
      
    
    
      
    
    
      
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
      
      
    
    
      
      
    
    
      
      
    
    
      
      
    
    
      
      
    
    
      
      
    
    
      
      
    
    
      
      
    
    
      
      
    
    
      
      
    
    
      
      
    
    
  
  
  
    
      
        image/svg+xml
        
        
      
    
  
  
    
    Controller
    
    Fluid-Template
    
    ViewHelper
    
    IteratorRepository
    
    
    Table Data
    
    1
    
    2
    
    4
    
    5
    
      
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    
    
    
    
    
    
    Table Row
    
    next()
    
    3
  

Nichts zu sehen? Zur PNG-Version.

Hinweis: Die Darstellung ist nicht ganz korrekt, da der Controller die gerenderten Daten des Templates zurückbekommt und diese in Richtung Ausgabe weiterleitet. Vielen Dank an Alex für diesen Hinweis.

Der Controller

Der Controller verliert in diesem Ansatz die Aufgabe, irgendwelche Daten für die Log-Tabelle zu besorgen. Er muss lediglich das entsprechende Template aufrufen und ggf. andere Jobs erledigen, die neben der Tabelle auf der Seite zu sehen sind.

Das Fluid-Template

Das Template gestaltet das Drum-Herum und ruft darin für die Log-Tabelle einen View-Helper auf. Zudem kann man als Inhalt des View-Helper-Tags ein Template vorgeben, in welchem der Helper nur Variablen ersetzen braucht. Hierfür muss man den allgemeinen Teil von dem trennen, der mit jeder Zeile wiederholt werden muss. Ein simpler Ansatz wäre etwa:


	
			
IDInhalt
%1$d%1$s

Das obige Variablenformat lässt sich dann direkt mit PHPs Sprintf-Funktion befüllen.

Der View-Helper

Dies ist die einzige Stelle, an der wie die kompletten Log-Daten zwischenspeichern. Dafür aber gleich im fertigen Ausgabeformat, so dass wir sie nicht noch einmal durcharbeiten müssen. Hierfür nehmen wir eine Singleton-Instanz des Iterator-Repositorys, iterieren über die einzelnen Zeilen der Datenbanktabelle und befüllen hiermit jeweils unser Template. Am Ende geben wir den so zusammengebauten Ausgabestring zurück.

Das Iterator-Repository

Das Repository implementiert das PHP-Iterator-Interface. Dies erlaubt es dem View-Helper, das Repository in einer Foreach-Schleife auszulesen. Der Rest ist relativ simpel: Beim Aufruf von rewind() und dem Konstruktor wird ein Query abgesetzt, das Result speichern wir in einem Klassenattribut, um es in anderen Methoden nutzen zu können.

$this->result = $GLOBALS['TYPO3_DB']->SELECTquery(/*...*/);

Hierbei sollte gleich darauf geachtet werden, dass möglichst viel Logik direkt in SQL erledigt wird. So kann man beispielsweise Datumsformate direkt im Query definieren.
Ein Aufruf von next() setzt ein Row-Attribut mittels

$this->row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($this->result);

Zu guter Letzt muss ein current() noch $this->row oder eine angepasste Variante davon zurückgeben.

Fazit

Die vorgegebenen Code-Schnipsel dienen der Verdeutlichung und sind nicht vollständig. Wenn man dem Ansatz jedoch folgt, kann man sehr direkt auf der Datenbank arbeiten, ohne die offizielle Vorgehensweise von Extbase wirklich zu verletzen.

6 Kommentare

  1. Hi Sebastian,

    du hast da einen sehr interesanten Ansatz.
    Ich stand letztens auch vor dem selben Problem, habe es aber etwas anders gelöst.
    Der Flaschenhals bei Extbase ist zur Zeit noch der Object-Mapper. Ich hoffe das es spätestens mit dem Einsatz von Doctrine besser wird.
    In meinem Fall habe ich einfach die Datenabfrage im Repository per setStatement() durchgeführt und das Ergebnis als Array zurückgegeben. Das Array kannst du dann an den View übergeben und damit machen was du willst.
    Wenn man jetzt das Ergebnis paginieren will ist das auch kein Problem. Normalerweise sollte der MySQL Server ja alle Queries cachen.
    In meinem Fall frage ich so ca. 8.000 Datensätze ab welche wiederrum ca. 16.000 Verknüpungen haben. Die Abfrage dauert meistens so um die 300 – 500 ms.

    Deine Methode ist sicherlich besser wenn sich die Datensätze schnell ändern – wie z.b. bei Logs. In meinem Fall sind die Daten eher statisch.

    Der Nachteil deiner Methode ist, dass sie nicht barrierefrei ist – und darauf muss ich achten. Außerdem würde ich nicht mehr die alten Typo3 DB Funktionen nutzen. Die musst Du später sonst eh wieder ändern.

    So und zum Schluss noch etwas klugscheißen: Das Fluid Template liefert nicht die Ausgabe zurück. Das macht der Controller via return $this->view->render();

    Aber ansonsten eine schöne Idee das Iterator Interface mit dem Repository zu verwenden. Vielen Dank für den Artikel.

    Grüße
    Alex

  2. Hallo Alex,

    danke für deine Hinweise und für deinen Ansatz.

    Ist schon richtig, mein Ansatz ist nicht sonderlich flexibel. Wie schon gesagt, es geht ja hauptsächlich um große Datensätze die einfach irgendwie ausgegeben werden sollen. Und hier wollte ich nach Möglichkeit nur ein einziges mal über alle Zeilen iterieren.

    Aber inwiefern schränkt das die Barrierefreiheit ein?

    Danke für den Hinweis mit dem Template bzw. Controller. Das werde ich da bei Gelegenheit noch mal ergänzen.

    Mit den Datenbankfunktionen kenne ich mich bei Extbase ehrlich gesagt gar nicht aus. Wäre mir auch noch nicht aufgefallen, dass es andere Möglichkeiten gibt. Außer natürlich die in den Repositorys integrierte. Muss ich mir mal angucken, wie die das da machen.

    Vielen Dank für diese Anregungen.

  3. Hi Sebastian,

    du kannst SQL so direkt im Repository ausführen:

    $query = $this->createQuery();
    $sql = "SELECT * FROM .....";
    $ergebnis = $query->statement($sql)->execute();

    Die Statement Methode ist offziell allerdings absichtlich schlecht dokumentiert. Ist ja auch mehr eine Notlösung…

    Das mit der Barrierefreiheit bezog sich auf:

    Da Sortierung und Seiteneinteilung von Javascript übernommen werden …

    Grüße
    Alex

  4. Hi Sebastian,

    nachdem ich mir die Sache mit dem Iterator-Interface beim weihnachtlichen dekorieren unserer Hauses noch mal überdacht habe, ist mir noch viel besseres eingefallen.

    Da Extbase wie gesagt langsam wird, wenn man viele Objekte mappt könnte man es eigentlich genauso machen wie du es gemacht hast.
    Man müsste halt nur die richtigen Repository Methoden nutzen und bei jedem next() eine neue Repository abfrage starten die den jeweiligen nächsten Datensatz zurück liefert.
    Da Extbase nun immer nur ein Objekt mappen muss, sollte es deutlich schneller gehen, zumindest wenn man durch wenige Objekte iteriert.

    Das normale Verfahren ist ja:
    1. Erst alle Objekte abfragem (Was bei vielen Objekten lange dauert)
    2. Alle Objekte an View übergeben und mit Paginierungs-Widget durchlaufen.

    Beim verwenden des Iterator-Interfaces würden nur soviele Objekte gemappt werden müssen, wie wirklich im View dargfestellt werden müssten.

    Danke für den Ansatz, werde ich morgen gleich mal ausprobieren.

    Grüße
    Alex

  5. Hey Alex, tönt spannend, hast Du mal eine Extension in der Du das umgesetzt hast bei der ich mir das mal im Kontext selber zu Gemüte führen kann? Vielen Dank!

Kommentare sind geschlossen.