Brokkoli in einer Hand, in der anderen einen Donout

TypoScript Conditions mit der Symfony Expression Language

Für die TYPO3 v9.5 LTS wurde die Symfony Expression Language bei den TypoScript Conditions eingeführt. Wem das zu viele Fragezeichen auf einmal sind, dem erklären wir zunächst, um was es sich bei TypoScript Conditions und der Symfony Expression Language handelt. Anschließend zeigen wir wie ältere Conditions migriert und somit zukunftsfähig gemacht werden.

TypoScript Conditions

TypoScript Conditions kommen wie der Name bereits verrät beim TypoScript zum Einsatz. TypoScript ist keine Skriptsprache. TypoScript dient eher als eine Art API (Schnittstelle), zum konfigurieren des zugrunde liegenden PHP-Codes.

Die TypoScript Conditions (zu Deutsch „Bedingungen“) bieten die Möglichkeit zu überprüfen, ob eine Bedingung erfüllt ist oder nicht. Je nachdem wird weiteres TypoScript verwendet oder nicht.

... TypoScript ...

[ Condition 1 ][ Condition 2 ]

... TypoScript, dass nur verarbeitet wird, wenn die Bedingungen erfüllt wurden ...

[END]

... TypoScript ...

Mit den Conditions kann beispielsweise Folgendes gefragt werden:

  • Ist der aktuelle Nutzer im Backend eingeloggt?
  • Ist heute Freitag?
  • Enthält die URL den Parameter „x=1“?
  • Findet im Gewandhaus zu Leipzig heute eine Veranstaltung statt?
  • Fühle ich mich gut?

Zugegeben sind einige von diesen Fragen nicht für jede Webseite relevant. Es soll lediglich gezeigt werden, dass die Möglichkeiten der TypoScript Conditions kaum Grenzen kennen. Mit der Einführung der Symfony Expression Language werden sie sogar noch mächtiger.

Symfony Expression Language

Symfony ist ein weit verbreitetes PHP-Framework. Es gibt PHP-Entwicklern nützliche Werkzeuge an die Hand. So zum Beispiel auch die Expression Language.

Die Expression Language stellt eine Engine bereit, mit der Ausdrücke kompiliert und ausgewertet werden können. Ein Ausdruck ist ein Einzeiler, der einen Wert zurückgibt. Meistens ist dieser Wert vom Typ Boolean, also entweder Wahr oder Falsch.

Innerhalb von Symfony sieht das dann wie folgt aus.

$expressionLanguage->evaluate('1 < 2'); // wahr

Die Expression Language stellt in diesem Fall ‚1 < 2‘ dar. Hierbei handelt es sich um ein simples Beispiel.

Die Expression Language Syntax ist viel umfangreicher. Möglich sind somit auch:

$expressionLanguage->evaluate('not ("Schuppenkopfrötel" matches "/Paddyreiher/")'); // wahr

$expressionLanguage->evaluate('11 in 1..10'); // falsch

$expressionLanguage->evaluate('calendar.getWeekday() == "Freitag"')); // Am Freitag wahr, sonst falsch

Der Ausdruck matches ermöglicht den Vergleich mit sämtlichen regulären Ausdrücken. Zudem können Eigenschaften und Funktionen von Objekten ausgeführt werden.

Zu beachten ist, dass ein Schrägstrich (\) innerhalb einer Zeichenkette mit 4 weiteren maskiert werden muss (\\\\) und sogar mit 8 (\\\\\\\\) in einem regulären Ausdruck.  Darüber hinaus werden Kontrollsequenzen, wie z.B. ein Zeilenumbruch (\n), normalerweise mit einem Leerzeichen ersetzt. Durch voranstellen eines Weiteren umgekehrten Schrägstrichs wird dies vermieden (\\n).

Die hier aufgeführten Beispiele sind nur ein kleiner Auszug. Einen umfangreichen Überblick gibt es in der Dokumentation.

TypoScript Conditions in TYPO3 v9.5 LTS

Die Symfony Expression Language wurde mit der TYPO3 v9.4 in den Core übernommen und steht somit auch in der Long Term Version 9.5 zur Verfügung. Die alte Syntax gilt als veraltet und wird nur noch bis zur 10er Version unterstützt. Um sein System zukunftsfähig zu halten, gilt es die alten Conditions zu migrieren.

Variablen

Schon vor der TYPO3 9 kamen Conditions zum Einsatz. Hier standen dem Integrator eine Vielzahl von Variablen zur Verfügung, wie z.B. die ID der aktuellen Seite oder der Name des angemeldeten Benutzers. Diese gibt es innerhalb der Symfony Expression Language bei TYPO3 wieder, nur die Syntax ist eine andere. Ein paar Beispiele:

  • applicationContext – Gibt Aufschluss über die aktuelle Umgebung, z.B. „production“ oder „development“.
  • page – Alle Eigenschaften der Seite als Array.
  • {$meineTypoScriptKonstante} – ermöglicht auf den Inhalt der TypoScript-Konstanten zuzugreifen.
  • frontend.user.isLoggedIn – ist wahr, wenn der Nutzer im Frontend angemeldet ist
  • typo3.version – aktuelle TYPO3-Version, z.B. 9.5.0-dev

Funktionen

Um wirklich alle bisherigen Conditions ersetzen zu können, gibt TYPO3 neben den Variablen auch Funktionen an die Hand.

  • date() – Zugriff auf das aktuelle Datum
  • like("Heuhaufen", "*Nadel")  – Schaut, ob die Zeichenkette Nadel im Heuhaufen vorkommt. Das Sternchen (*) wird als Platzhalter für eine beliebige Zeichenkette verwendet.
  • getTSFE() – Ermöglicht den Zugriff auf den TypoScriptFrontendController $GLOBALS[‚TSFE‘]
  • request.getQueryParams() – Ermittelt alle GET-Parameter.
  • site() – Gibt die Eigenschaften der aktuellen Seitenkonfiguration zurück.

Eine Übersicht aller Variablen und Funktionen, gibt es in der Feature-Beschreibung.

Ausdrücke

Es folgen nun Beispiele, wie bisherige Conditions mit der neuen Symfony Expression Language aussehen.

Seitensprache

//bis TYPO3 v9:
[globalVar = GP:L = 1]
 
// ab TYPO3 9.4 - der L-Parameter entfällt, daher Überprüfung der Site-Configuration
[siteLanguage("languageId") == "1"]
// neben der Id können nun auch weitere Eigenschaften abgefragt werden
[siteLanguage("title") == "Deutsch"]

Get- und Post-Parameter

// bis TYPO3 v9.3
[globalVar = GP:tx_myext_myplugin|bla > 0]

// ab TYPO3 v9.4 (GET-Parameter oder POST-Parameter)
[request.getQueryParams()['tx_myext_myplugin']['bla'] > 0] || 
 [request.getParsedBody()['tx_myext_myplugin']['bla'] > 0]

Aktuell gibt es hierzu noch eine Diskussion auf https://forge.typo3.org/issues/88756#change-404466. Im jetzigen Zusantand wirft diese Abfrage eine Warnung, wenn das Array [‚tx_myext_myplugin‘] nicht exisitert. Es wird noch diskutiert, ob diese Abfrage zusätzlich definiert werden soll oder die Überprüfung bereits automatisch passiert. Danke für den Hinweis Clive.

Ein Workaround ist die Funktion traverse zu nutzen (Danke Florian für den Hinweis). Diese ist ab TYPO3 v9 erhältich (s. TYPO3-Doku).

[traverse(request.getQueryParams(), 'tx_myext_myplugin/bla') > 0 ||
  traverse(request.getParsedBody(), 'tx_myext_myplugin/bla') > 0]

Ebene im Seitenbaum

// bis TYPO3 v9.3 
[treeLevel = 0,2] 

// ab TYPO3 v9.4 
[tree.level in [0,2]]

Seite in Rootline

// bis TYPO3 v9.3 
[PIDinRootline = 5,10] 

// ab TYPO3 v9.4 
[5 in tree.rootLineIds || 10 in tree.rootLineIds]

Seiteneigenschaften

// bis TYPO3 v9.3
[page|backend_layout = 1]

// ab TYPO3 v9.4
[page["backend_layout"] == '1']

Domain

// bis TYPO3 v9.3
[globalString = IENV:HTTP_HOST = *.mydomain.com]

// ab TYPO3 v9.4
[like(request.getNormalizedParams().getHttpHost(), '*.mydomain.com')]

Konstanten

// bis TYPO3 v9.3
[globalVar = LIT:1 = {$meineTypoScriptKonstante}]

// ab TYPO3 v9.4
["{$meineTypoScriptKonstante}" == "1"]

Nutzer im Backend eingeloggt

// bis TYPO3 v9.3
[globalVar = TSFE:beUserLogin > 0]

// ab TYPO3 v9.4
[getTSFE().beUserLogin]

Ist heute Freitag?

// bis TYPO3 v9.3
[dayofweek = 5]

// ab TYPO3 v9.4
[date("w") == 5]

Weitere Beispiele

Wie bereits erwähnt sind die Möglichkeiten der Conditions kaum begrenzt. Weiterführende Links mit zusätzlichen Beispielen sind am Ende des Blog-Beitrags zusammengetragen.

Erweitern der Expression Language

Bisher konnten eigene Funktionen via [userFunc = \Vendor\Extension\UserFunc\MyUserFunc('foo')] verwendet werden. Auch die Symfony Expression Language lässt sich beliebig erweitern.

Dazu wird zunächst der eigene Provider in einer aktiven Extension unter Configuration/ExpressionLanguage.php registriert.

<?php

return [
    'typoscript' => [
        \Vendor\Extension\ExpressionLanguage\MyConditionProvider::class,
    ]
];

Die Klasse muss das \TYPO3\CMS\Core\ExpressionLanguage\ProviderInterface umsetzen. Dieses besteht aus den beiden Funktionen getExpressionLanguageProviders() und getExpressionLanguageVariables(). Für die Umsetzung kann von der Klasse \TYPO3\CMS\Core\ExpressionLanguage\AbstractProvider abgleitet werden.

Als Beispiel schauen wir uns den TypoScriptConditionProvider aus dem Core an.

class TypoScriptConditionProvider extends AbstractProvider
{
    public function __construct()
    {
        $typo3 = new \stdClass();
        $typo3->version = TYPO3_version;
        $typo3->branch = TYPO3_branch;
        $typo3->devIpMask = trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
        $this->expressionLanguageVariables = [
            'request' => GeneralUtility::makeInstance(RequestWrapper::class, $GLOBALS['TYPO3_REQUEST'] ?? null),
            'applicationContext' => (string)GeneralUtility::getApplicationContext(),
            'typo3' => $typo3,
        ];
        $this->expressionLanguageProviders = [
            Typo3ConditionFunctionsProvider::class
        ];
    }
}

Symfony und TYPO3

Die Symfony Expression Language ist eines von vielen Vorzügen der neuen TYPO3 9 LTS. Wir freuen uns sehr, dass TYPO3 Teile von Symfony übernimmt, da wir dieses Framework auch Unabhängig von TYPO3 einsetzen. In unseren Individual-Programmierungen setzen wir auf Symfony seit vielen Jahren und haben durchweg positive Erfahrungen gesammelt.

Fragen oder Anmerkungen zum Artikel können Sie wie immer gerne als Kommentar hinterlassen. Möchten Sie selbst eine Webseite mittels TYPO3 oder Symfony realisieren, dann nehmen Sie gerne mit uns Kontakt auf.

  • 28/12/2018

    Kommentar von Flecko

    Hallo,

    ich suche mir gerade meine Änderungen für meine Conditions zusammen und bin über die Seite gestolpert und konnte
    anhand eurer Beispiele viele Probleme lösen, jetzt hänge ich noch an zwei und komme nicht wirklich weiter.

    [treeLevel = 0,1]
    # *** Keine Breadcrumb
    [ELSE]
    # *** Zeige Breadcrumb
    [END]

    # *** Newssingleansicht
    [globalVar = GP:tx_news_pi1|news > 0]
    # *** Wenn wir auf der Newseinzelansicht sind da tue …
    [global]

    Gibt es für diesen Fall überhaupt eine Lösung?

    Vorab schonmal Daumen hoch für den Artikel.

    • 02/01/2019

      Kommentar von Marc

      Hallo Flecko,

      ich habe die Ebene im Seitenbaum (treeLevel) als Beispiel ergänzt. Wie Get-Parameter abgefragt werden, siehst du im Beispiel darüber. Ich hoffe, dass ich dir weiterhelfen konnte.

      Viele Grüße,
      Marc

  • 11/01/2019

    Kommentar von jokumer

    Leider werden mit request.getQueryParams()[‚tx_myext_myplugin‘]
    nur Werte aus GET Anfragen (der aufgerufenen URL) gelesen, nicht jedoch aus POST Anfragen.

    Ältere Bedingungen wie globalVar = GP:tx_myext_myplugin
    berücksichtigten sowohl GET als auch POST Anfragen gleichermaßen.

    Deshalb kann getQueryParams() nicht als Ersatz für globalVar = GP verwendet werden.

  • 12/01/2019

    Kommentar von jokumer

    Statt GET, lassen sich POST Abfragen mit folgender Schreibweise als Bedingung erstellen
    [request.getParsedBody()[‚tx_myext_myplugin‘][‚bla‘] > 0]

    • 14/01/2019

      Kommentar von Marc

      Hallo jokumer,

      vielen Dank für deinen Hinweis. Ich habe deine Anmerkung im Blog-Beitrag ergänzt.

      Viele Grüße,
      Marc

  • 13/08/2019

    Kommentar von Jo Hasenau

    Zeile 4 ‚tx_myext_provider‘ muß ‚typoscript‘ heißen, sonst funktioniert die Registrierung der Condition nicht.
    Ansonsten cooler Artikel.

    • 14/08/2019

      Kommentar von Marc

      Hallo Jo, ist angepasst. Vielen Dank für den Hinweis. Viele Grüße, Marc

  • 21/08/2019

    Kommentar von Clive Beckett

    Hallo,

    vielen Dank für diese Zusammenfassung. Eine Bemerkung zu request.getQueryParams():
    [(request.getQueryParams()[‚tx_myext_myplugin‘])[‚bla‘] > 0]
    Diese Condition führt zwar zum richtigen Ergebnis, flutet aber die Log-Datei mit Fehlermeldungen da sie keine isDefined-Abfrage durchführt: «Unable to get an item on a non-array». Außerdem sind, soweit ich das sehen kann, die runden Klammern unnötig.

    Korrekt muss es heißen:
    [request.getQueryParams()[‚tx_myext_myplugin‘] && request.getQueryParams()[‚tx_myext_myplugin‘][‚bla‘] > 0]

    Quelle: https://forge.typo3.org/issues/88756#change-404466

    LG: Clive.

    • 22/08/2019

      Kommentar von Marc

      Hallo Clive,

      vielen Dank für dein Feedback. Den Hinweis habe ich im Text untergebracht. Ich würde mich freuen, wenn die Abfrage nicht zusätzlich erfolgen muss. Schauen wir mal, wohin die Entwicklung geht.

      Viele Grüße,
      Marc

  • 18/11/2019

    Kommentar von Florian

    Vielen Dank für diesen tollen Artikel, hat mir bereits einige Probleme gelöst.

    Nun bin ich aber über eine Condition gestolpert, für welche ich bisher noch keine Lösung gefunden habe.
    Gibt es eine Möglichkeit mit den neuen Conditions Cookies auszulesen wie dies vorher mit [globalVar = _COOKIE|tx_cookies_accepted=1] möglich war?

    Grüsse, Florian

    • 19/11/2019

      Kommentar von Marc

      Hi Florian,

      in dem Link zur Doku https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/9.4/Feature-85829-ImplementSymfonyExpressionLanguageForTypoScriptConditions.html findest du request.getCookieParams()[‚foo‘] == 1. Ich hoffe, dass ich dir weiterhelfen konnte.

      Viele Grüße,
      Marc

  • 29/04/2020

    Kommentar von Vincent

    Hallo,
    ich versuche gerade die Conditions für ein neues Projekt unter 9.5 anzupassen. Leider greift die neue Syntax für einen Wert aus den Constants bei mir nicht.

    constants.ts
    styles.content.textmedia.layoutKey = lazysizes

    setup.ts

    ALT (funktioniert)
    [globalVar = LIT:lazysizes = {$styles.content.textmedia.layoutKey}]

    NEU (funktioniert nicht)
    [{$styles.content.textmedia.layoutKey} == „lazysizes“] oder
    [{$styles.content.textmedia.layoutKey} == ‚lazysizes‘] oder
    [{$styles.content.textmedia.layoutKey} == lazysizes]

    Hat jemand einen Tipp, wie es geht? Danke!

    • 06/07/2020

      Kommentar von Marc

      Hallo Vincent,

      [{$meineTypoScriptKonstante} == ‚lazysizes‘] sollte funktionieren. Schau mal im TypoScript Object Browser, ob deine Konstante auch eingebunden wird.

      Viele Grüße,
      Marc

  • 15/06/2020

    Kommentar von Tobias Kasprak

    Hallo und vielen Dank für die Info`s.
    ich versuche zur Zeit unsere gesamte Seite anhand von sys_category zu vertaggen, damit auf unterschiedlichen Seiten unterschiedliche Werbung ausgespielt werden kann.
    Die Seiten der jeweiligen Kategorien bekomme ich ausgelesen, aber wie lautet denn der Condition Befehl um auf diesen Seite einzelne Elemente (Die Werbeblöcke) auf der Seite anzusprechen?
    Weiß da jemand Bescheid?
    Danke

    • 19/06/2020

      Kommentar von Marc

      Hallo Tobias,

      evtl. kannst du deine Frage nochmal auf stackoverflow posten. Da lässt sich das einfacher diskutieren als an dieser Stelle.

      Viele Grüße,
      Marc

  • 14/09/2020

    Kommentar von Andreas

    Hallo Marc,

    danke für Dein Tutorial! Ich habe die Kommentare gelesen und es sieht so aus, als hätte ich das selbe Problem wie Vincent, ich bekomme diese Condition nicht zum Laufen (ist scheinbar immer false):

    [{$project.header.type}==’slider‘]

    In Constants steht:

    project.header.type = slider

    Irgend eine Idee?

    LG

    Andreas

    • 21/09/2020

      Kommentar von Marc

      Hallo Andreas,

      bei einem Vergleich von Strings muss auch die Konstante in Anführungszeichen gesetzt werden, also [„{$project.header.type}“==“slider“]. Die Erläuterung findest du hier https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/9.4/Feature-85829-ImplementSymfonyExpressionLanguageForTypoScriptConditions.html >>if constant is a string put constant in quotes: [„{$foo.bar}“ == „4711“]<<. Viele Grüße, Marc

  • 30/11/2020

    Kommentar von Florian

    Danke für den coolen update blog.
    Aber nochmals kurz zu:
    [globalVar = GP:tx_myext_myplugin|bla > 0]

    Die Condition führt nicht zu einem Warning, sondern zu einem Error, was die Logs schnell überlaufen lässt.

    Der akzeptabelste Weg ist „traverse“ zu verwenden:
    [traverse(request.getQueryParams(), ‚my_ext/foo‘) > 0]

    siehe:
    https://forge.typo3.org/issues/89176

    Post Requests müssen aber noch immer extra behandelt werden.

    • 03/12/2020

      Kommentar von Marc

      Hallo Florian,

      danke für den Hinweis. Ich habe den Artikel an der entsprechenden Stelle aktualisiert.

      Viele Grüße,
      Marc

Kommentar hinzufügen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert