For coders TYPO3 Tech Corner

Dropdown-Menü ohne Last - mittels Multilevel-Cache

Dropdown-Menü ohne Last - mittels Multilevel-Cache

Im Artikel "Menü: Vergleich der Techniken" hatten wir verschiedene Arten und Varianten verglichen, um ein Mega-Menü zu bauen. Die Variante des TypoScript-basierten Menüs mit Cache auf mehreren Ebenen hatten wir aus Platzgründen aber bisher nur zahlenmäßig und mit groben Konzept vorgestellt. Jetzt gehen wir Schritt für Schritt durchs Menü und optimieren es mit Caching auf Ebene 1 und 2.

Hinweis:
Alle Snippets sind der Übersicht halber vereinfacht und gekürzt.
Der vollständige Code findet sich im Git-Repository des Test-Projektes (Variante 5).

Ausgangbasis

Als Basis der Überlegungen diente das Menü aus meinem alten Artikel "Mega-Menü mit Cache optimieren". In vereinfachter Form (und noch ohne Cache) sieht es so aus:

10 = COA 10 { 10 = HMENU 10 { special = list special.value = 3 1 = TMENU 2 = TMENU } 20 < .10 20.special.value = 189 30 < .10 30.special.value = 4 }

Schon damals fiel ein Punkt negativ auf: "Einen kleinen Tod müssen wir sterben: wir legen die Hauptnavigationspunkte per Seiten-ID fest.". Im konkreten Projekt, für das die Navigation diesmal optimiert werden musste, konnte dieser Nachteil nicht hingenommen werden. Denn die Hauptseiten konnten durch die Redaktion auch mehr oder weniger werden.

HMENU ist mit Cache, TMENU nicht

Hintergrund der Aufteilung in einzelne HMENU-Objekte war, dass diese eine cache-Eigenschaft haben, während TMENU-Objekte diese nicht haben. Dem TMENU wurde in TYPO3 v12 auch der interne (ineffiziente) Cache genommen (#98964).

Wir brauchten also ein flexible Lösung, um einzelne HMENUs zu bekommen.

Da gab's doch mal.... doNotLinkIt

Nach einiger Überlegung kam die passende Idee: ein HMENU, das seine Menüpunkte der Ebene 1 aber auch wiederum als HMENU generiert.

Wer früher™ schon Menüs mit TypoScript gebaut hat, der erinnert sich vielleicht noch an doNotLinkIt als Eigenschaft des TMENUITEM. Damit lässt sich das "normale" Verhalten der Menügenerierung aushebeln, und man kann individuelle Dinge zum Anzeigen der Menüpunkte bauen (Ein Beispiel wäre z.B. eine Menüpunkt, der auch ein Bild aus den Seiteneigenschaften mit anzeigt).

Das nutzen wir, um hier - satt eines Links zum Menüpunkt - gleich via stdWrap.cObject ein ganzes HMENU einzubauen. Und die Einstiegsseite für das untergeordnete HMENU bekommen wir aus den "Feldern" des aktuellen Objektes, also dem TMENUITEM:

10 = COA 10 { 10 = HMENU 10 { 1 = TMENU 1 { NO { doNotLinkIt = 1 stdWrap.cObject = COA stdWrap.cObject { 20 = HMENU 20 { special = list special.value.field = uid 1 = TMENU 1 { //... } 2 = TMENU 2 { //... } } } } } } }

Nachdem in unserem Projekt der Cache auf der ersten Ebene nicht ausreichte, benötigten wir die HMENUs auch noch eine Ebene darunter. Nach den obigen Überlegungen ist das dann nur noch Fleißarbeit:

10 = COA 10 { 10 = HMENU 10 { 1 = TMENU 1 { NO { doNotLinkIt = 1 stdWrap.cObject = COA stdWrap.cObject { 20 = HMENU 20 { special = list special.value.field = uid 1 = TMENU 1 { expAll = 1 } 2 = TMENU 2 { NO { doNotLinkIt = 1 stdWrap.cObject = COA stdWrap.cObject { 20 = HMENU 20 { special = list special.value.field = uid 1 = TMENU 1 { //... } 2 = TMENU 2 { //... } } } } } } } } } } }

Cache: Level 1

Je früher wir cachen können, desto besser. Am besten wäre also, wenn wir einen Hauptmenüpunkt samt seiner Unterpunkte gar nicht neu bauen müssten, sondern komplett aus dem Cache bekommen könnten.

Wie schon im alten Artikel erklärt, setzen wir daher einen Cache-Key im HMENU für die Menüs unterhalb der Hauptseiten (also die ersten mit special = list):

10 { 1.NO.stdWrap.cObject.20.cache { key { cObject = TEXT cObject { value.field = uid wrap = mainnav-lvl1-|_{siteLanguage:languageId} } insertData = 1 } } }

Cache: Level 2

Neu ist jetzt die Möglichkeit, dass wir dasselbe auch für die Ebene darunter machen können, weil dort jetzt auch HMENUs sind:

10 { 1.NO.stdWrap.cObject.20.2 { NO { stdWrap { cObject { 20 { cache { key { cObject = TEXT cObject { value.field = uid wrap = mainnav-lvl2-|_{siteLanguage:languageId} } insertData = 1 } } } } } } } }

Was ist mit dem aktivem Pfad?

Wer aufgepasst hat, dem ist nicht entgangen, dass wir bisher die Untermenüs rein anhand ihres Entrypoints cachen - ohne zu berücksichtigen, ob eine Seite im Menü aktiv ist.

Speichern wir uns also die Rootline der aktuellen Seite wieder in ein Register, und überschreiben falls nötig den Cache-Key. Nötig wird dies immer, falls der Entrypoint in der Rootline der aktuell aufgerufenen Seite liegt:

10 { 5 = LOAD_REGISTER 5.currentRootlinePids.cObject = HMENU 5.currentRootlinePids.cObject { special = rootline special.range = 0|-1 1 = TMENU 1 { NO { doNotLinkIt = 1 stdWrap.cObject = TEXT stdWrap.cObject.field = uid wrapItemAndSub = |, |*| |, |*| | } } } 10 { // Level 1 1.NO.stdWrap.cObject.20.cache { key.cObject.override { data = page:uid wrap = |_cur if { value.data = register:currentRootlinePids isInList.field = uid } } } // Level 2 1.NO.stdWrap.cObject.20.2 { NO { stdWrap.cObject.20.cache { key.cObject.override { data = page:uid wrap = |_cur if { value.data = register:currentRootlinePids isInList.field = uid } } } } } } }

95% weniger Datenbank-Transaktionen

Mit dieser TypoScript-Lösung könnten wir 95% der SQL-Queries einsparen - die einzige bessere Lösung aus unserem Vergleich (die aber auch Programmier-Knowhow voraussetzt) schaffte es mit viel PHP-Code auf 99% Einsparung.

Nochmals die Zahlen im Vergleich:

| Seite | Fluid-basiert | TS-basiert, 2 Caches | mit b13/menus (gepimpt) | |------------|---------------|----------------------|-------------------------| | Home | 6094 | 5206 | 7436 | | Impressum | 1259 | 56 | 18 | | Page 1 | 1251 | 116 | 11 | | Page 2 | 1251 | 113 | 11 | | Page 2.1 | 1250 | 116 | 11 |

TypoScript - mächtig, wie eh und je

TypoScript gibt es seit Ewigkeiten. Und, ja, nicht jedes Konstrukt darin ist eingängig. Für manches braucht es Erfahrung (und manchmal auch Schmerz), um es zu verstehen oder richtig anwenden zu können. An diesem Beispiel sieht man aber, dass TypoScript auch heute noch seine Daseinberechtigung (und gewisse Vorteile) haben kann - wenngleich es etwas sperriger erscheinen mag.

Der nicht zu unterschätzende Punkt ist: man braucht keine Programmierkenntnisse. Es ist doch fast schon genial, dass ein Integrator (der nach der TYPO3 Rollendefinition kein Programmierer ist) rein mit TypoScript das Menü so stark optimieren kann.

Nach fast 20 Jahren mit TypoScript bin ich immer wieder überrascht und begeistert, was mit etwas Konfiguration alles machbar ist. Daher bin ich definitiv im #teamTypoScript.

Zu gerne hätte ich diese Menü-Variante in unserem Menü-Vergleich ganz oben auf dem Siegertreppchen gesehen... :-(

Zurück

Kennst du das: Immer nur schnell schnell?

Wie wäre es einmal mit Zeit und Respekt für Codequalität? Arbeiten im Team? Automatisierte Tests?

Komm zu uns