Zum Inhalt springen

Einleitung

Im ersten Teil der Serie haben wir eine Site Configuration eingerichtet. Diesmal werden wir uns den Extbase Plugin Enhancer genauer anschauen. Als Beispiel dient uns die populäre News-Extension. Doch zuvor klären wir noch kurz, was für eine Aufgabe die Routing Enhancer haben und welche Arten es gibt.

Die Routing Enhancer im Allgemeinen

Bei Seiten nimmt uns TYPO3 die Arbeit bereits ab – mit einer vorhandenen Site Configuration werden für alle Unterseiten "sprechende URLs" erstellt.

Für den Query String (GET-Parameter) in der URL – wie tx_news_pi1[news] oder tx_felogin_pi1 – müssen wir aber selbst tätig werden. Ähnlich wie bei RealURL benötigen wir also eine Konfiguration, um diese Parameter zu umschreiben. Hier kommen die Routing Enhancer ins Spiel – und die dazugehörigen Aspects, auf die ich ebenfalls eingehen werde.

Welche Routing Enhancer gibt es in TYPO3 v9?

  1. Simple Enhancer (type: Simple):
    Ermöglicht das Zuordnen (Mappen) mehrerer Parameter zu Argumenten, um diese später in der URL zu verwenden.
  2. Plugin Enhancer (type: Plugin):
    Dieser Enhancer ist für pibase-Plugins zuständig.
  3. Extbase Plugin Enhancer (type: Extbase):
    Wie der Name schon sagt, lassen sich damit URLs speziell für Extbase-Extensions umschreiben. Er ist eine Erweiterung des "normalen" Plugin Enhancers.
  4. PageTypeEnhancer (type: PageType):
    Dieser spezielle Enhancer behandelt ausschließlich den typeNum einer Seite (&type=123). Er wird immer am Ende einer URL ergänzt. Er kann dazu verwendet werden, alle URLs mit einem Slash / oder .html zu ergänzen oder einen type als rss.xml zu umschreiben.

Für jede Aufgabe wird jeweils ein neuer Routing Enhancer konfiguriert. Also z.B.

  • ein Extbase Plugin Enhancer für EXT:news,
  • ein weiterer Extbase Plugin Enhancer für EXT:tt_address sowie
  • ein Plugin Enhancer für EXT:felogin.

Der Extbase Plugin Enhancer für die News-Extension

Jetzt widmen wir uns endlich der Praxis. Zuerst zeige ich euch die fertige, funktionierende Konfiguration. Anschließend sehen wir uns die einzelnen Bestandteile genauer an.

In meinem Beispiel existiert der folgende, reduzierte Seitenbaum:

UIDNameTypURL SegmentInhalt
7routingroot/Leer, unwichtig für Tutorial
8news: listStandard/news-listNormale News-Listenansicht
9news: recordsOrdner/news-list/news-recordsNews-Datensätze und System-Kategorien
10news: detailStandard/news-list/news-detailNormale News-Detailansicht
11news: category listStandard/categoryNews-Listenansicht mit Kategoriemodus (OR), News-Kategoriemenü (Filter)

Neben der normalen News-Listenansicht besitzt das Beispiel noch eine zweite Listenansicht mit der Möglichkeit, nach Kategorien zu filtern. Die URL-Segmente der Seiten habe ich bewusst nicht angepasst; nur die Seite mit dem Kategoriemenü habe ich vereinfacht.

Die komplette Site Configuration

Die fertige config.yaml beinhaltet die Pflichtangaben zu Domain, Sprachen etc. Wir konzentrieren uns im Folgenden auf den Bereich unter routeEnhancers.

rootPageId: 7
base: 'https://mydomain.de/'
baseVariants:
  -
    base: 'http://mydomain.test/'
    condition: 'applicationContext == "Development"'
languages:
  -
    title: English
    enabled: true
    languageId: '0'
    base: /
    typo3Language: default
    locale: en_US.UTF-8
    iso-639-1: en
    navigationTitle: English
    hreflang: en-US
    direction: ''
    flag: en-us-gb
errorHandling: {  }
routes: {  }
routeEnhancers:
  News:
    type: Extbase
    limitToPages:
      # News-Listenansicht - benötigt für die Pagination:
      - 8
      # News-Detailansicht - benötigt für das allgemeine Routing:
      - 10
      # Newskategorie-Listenansicht:
      - 11
    extension: News
    plugin: Pi1
    routes:
      - routePath: '/page/{page}'
        _controller: 'News::list'
        _arguments:
          page: '@widget_0/currentPage'
      - routePath: '/{news_title}'
        _controller: 'News::detail'
        _arguments:
          news_title: news
      - routePath: '/{category_name}'
        _controller: 'News::list'
        _arguments:
          category_name: overwriteDemand/categories
    defaultController: 'News::list'
    defaults:
      page: '0'
    requirements:
      news_title: '^[a-zA-Z0-9].*$'
      page: \d+
    aspects:
      news_title:
        type: PersistedAliasMapper
        tableName: tx_news_domain_model_news
        routeFieldName: path_segment
      page:
        type: StaticRangeMapper
        start: '1'
        end: '100'
      category_name:
        type: PersistedAliasMapper
        tableName: sys_category
        # Warnung: Verwendet 'title' niemals in der Praxis!
        # Die Begründung sowie die saubere Lösung erläutere ich weiter unten!
        routeFieldName: title

Die einzelnen Routing-Bestandteile, nach Zeile nummeriert

Ich beschränke mich in diesem Tutorial auf die hier verwendeten Optionen. Im umfassenden Changelog-Eintrag zu den Routing Enhancers findet ihr weitere Erläuterungen. Zum Beispiel könnten die Optionen extension und plugin weggelassen und durch einen namespace ersetzt werden.

22) routeEnhancers:

Unter diesem Bereich werden alle Routing Enhancers aufgelistet. Er selbst darf nur einmal existieren.

23) News:

Jeder von uns konfigurierte Enhancer erhält einen frei wählbaren Namen, der nur einmal vorkommen darf. Er muss nicht etwa den Namen der Extension tragen, auch "Blog" oder "Vanilleeis" ginge.

24) type: Extbase

Wir verwenden für die News-Extension den Extbase Plugin Enhancer.

25) limitToPages:

Die Verwendung eines Enhancers lässt sich auf bestimmte Seiten begrenzen. Es handelt sich zwar nicht um ein Pflichtfeld, wird aber aus Performance-Gründen empfohlen.

Bei News benötigen wir das Routing vor allem für die Detailansicht. Aber: denkt auch an die Umschreibung der Listenansicht, sobald die Paginierung zum Einsatz kommt. Oder die Filterung von Kategorien. Weiter unten wird dies erläutert.

32) extension: News

Der Name der zu umschreibenden Extension. Achtung: der Name muss in UpperCamelCase umgewandelt werden! Beispiel: aus dem Extension-Key blog_example wird BlogExample.

33) plugin: Pi1

Der pluginName der Extension. Ihr seid unsicher, wie der exakte Name eines Plugins lautet? Schaut im Extension-Quelltext nach, wie es mit registerPlugin in TYPO3 registriert wurde. Im Falle von EXT:news ist es einfach "Pi1".

34) routes:

Jetzt kommen wir zum Kern der Sache! Für jede Ansicht, die wir mit "sprechenden URLs" umschreiben möchten, legen wir einen routePath an.

In meinem Beispiel benötige ich je einen routePath für:

  1. die Listenansicht, sobald das Widget zur Pagination verwendet wird (Seite 1 von x);
  2. die Detailansicht aller Newsbeiträge;
  3. eine (hier separate) Listenansicht, in der ich nach Kategorien filtern möchte.

Statt den kryptischen URLs

  1. mydomain.test/news-list?tx_news_pi1[@widget_0][currentPage]=2&cHash=
  2. mydomain.test/news-list/news-detail?tx_news_pi1[action]=detail&tx_news_pi1[controller]=News&tx_news_pi1[news]=3&cHash=
  3. mydomain.test/category?tx_news_pi1[overwriteDemand][categories]=3&cHash=

möchte ich in meinem Beispiel die URL-Pfade

  1. mydomain.test/news-list/page/2
  2. mydomain.test/news-list/news-detail/my-news-article
  3. mydomain.test/category/my-category

erhalten.

Wenn man diese Vorgaben einmal mit den konfigurierten routePaths vergleicht, wird das Vorgehen hoffentlich klarer. Denn die GET-Parameter der obigen URLs finden sich in den Argumenten meiner routePaths wieder:

      - routePath: '/page/{page}'
        _controller: 'News::list'
        _arguments:
          page: '@widget_0/currentPage'
      - routePath: '/{news_title}'
        _controller: 'News::detail'
        _arguments:
          news_title: news
      - routePath: '/{category_name}'
        _controller: 'News::list'
        _arguments:
          category_name: overwriteDemand/categories

Zuerst legen wir den gewünschten Pfad fest. Woher die Daten der Platzhalter {page}, {news_title} und {category_name} stammen? Sie werden in _arguments übergeben. Dies alleine würde aber nur zu URLs wie "mydomain.test/news-list/news-detail/3?cHash=" führen. Daher werden die Argumente unter aspects nach unseren Wünschen konfiguriert.

Außerdem benötigen wir eine Kombination des verwendeten Controller sowie der aktuellen Action, z.B. News::detail. Die hier möglichen Kombinationen einer Extension findet ihr in der ext_localconf.php innerhalb des Abschnitts configurePlugin.

Überspringen wir nun erst einmal ein paar Zeilen und widmen uns den Aspects.

53) aspects:

Hier können die Argumente der routePaths verändert oder z.B. auf Datenbankfelder gemappt werden.

Es gibt eine ganze Reihe von Aspects-Typen. Jeder Typ kann in allen Routing Enhancern verwendet werden. Der von euch konfigurierte Aspect gilt immer nur innerhalb des jeweiligen Routing Enhancers, nie global.

Sehen wir uns das Argument {news_title} an: es enthält zuerst einmal die UID des aktuellen Newsbeitrags. Mit dem Typ PersistedAliasMapper greifen wir auf die Datenbanktabelle mit den Newsbeiträgen zu und ersetzen diese UID mit dem aktuellen Inhalt aus dem Feld path_segment.
In Feld path_segment wird der Titel des Newsbeitrags von der News-Extension automatisch für die Verwendung in URLs optimiert. Sprich: Leerzeichen werden durch Bindestriche ersetzt, Sonderzeichen entfernt, alles in Kleinbuchstaben umgewandelt.

Das bringt uns zum Aspect von {category_name}. Hier wird der normale Titel als routeFieldName verwendet, was zu unschönen URLs führen wird, falls der Titel einer Kategorie aus mehreren Worten besteht:

mydomain.test/category/My%20Category

Wenn ihr die News-Extension in der Version 7.1.0 oder neuer verwendet, könnt ihr dieses Problem vermeiden: hier wurden die System-Kategorien und die News-Tags beide um ein eigenes Slug-Feld erweitert. Verwendet ihr das Feld slug als routeFieldName, erhaltet ihr ebenfalls optimierte URL-Pfade.

Bleibt der StaticRangeMapper für das Argument {page} (die Pagination der Listenansicht). Wir begrenzen hier die Anzahl der Seiten auf 100. Ohne diese Begrenzung wären die Vorgaben für das Routing zu unpräzise und der cHash-Parameter würde in der URL verbleiben.
Hinweis: Der StaticRangeMapper ist intern auf maximal 1000 Elemente begrenzt, um Brute-Force-Angriffe zu verhindern.

In unserem Beispiel verwenden wir nur zwei Aspect-Typen. Weitere Beispiele könnt ihr wieder im Changelog finden.

Behandeln wir nun noch die verbleibenden Optionen des Beispiels.

47) defaultController

Der defaultController wird verwendet, falls die konfigurierten routes einmal nicht greifen sollten.

48) defaults:

In diesem Bereich wird festgelegt, welche GET-Parameter optional sind. Wenn diese Parameter bei der Generierung des Links weggelassen werden, erhalten sie stattdessen den hier angegebenen Default-Wert und ein Platzhalter ist nicht notwendig.

50) requirements:

Bei den Anforderungen wird mittels regulärer Ausdrücke festgelegt, welche Art von Datentypen in den GET-Parametern erlaubt sind. Wenn in eurer URL ein cHash-Parameter übrig bleibt, waren eure Anforderungen zu locker.

Für die Seiten-Pagination der Listenansicht {page} geben wir mit \d+ vor, dass eine oder mehrere Ziffern verwendet werden sollen. Beim {news_title} dürfen Groß- und Kleinbuchstaben sowie Ziffern vorkommen.

Fazit

Ich hoffe, diese kleine Einführung hat euch geholfen. Die Konfiguration könnte beliebig erweitert werden – beispielsweise um die News-Tags oder das Datumsmenü.

Sämtliche Optionen des Routings aufzuführen, hätte den Rahmen jedes Tutorials gesprengt. Deshalb: lest euch auf alle Fälle auch die dazugehörigen Changelog-Einträge über Routing Enhancers and Aspects sowie den PageTypeEnhancer durch.

Weitere Tutorials zu diesem Thema könnten folgen. In der Zwischenzeit: testet die Möglichkeiten, die das Routing euch bietet!