Symfony expression language

Symfony expression language (SEL) is used by TYPO3 in a couple of places. The most well-known ones are TypoScript conditions. The TypoScript and TSconfig references list available variables and functions of these contexts. But the TYPO3 Core API allows enriching expressions with additional functionality, which is what this chapter is about.

Main API

The TYPO3 Core API provides a relatively slim API in front of the Symfony expression language: Symfony expressions are used in different contexts (TypoScript conditions, the EXT:form framework, maybe more).

The class \TYPO3\CMS\Core\ExpressionLanguage\Resolver is used to prepare the expression language processor based on a given context (identified by a string, for example "typoscript"), and loads registered available variables and functions for this context.

The System > Configuration module provides a list of all registered Symfony expression language providers.

Evaluation of single expressions is then initiated calling $myResolver->evaluate(). While TypoScript casts the return value to bool, Symfony expression evaluation can potentially return mixed.

Registering new provider

There has to be a provider, no matter whether variables or functions will be provided. A provider is registered in the extension file Configuration/ExpressionLanguage.php. This will register the defined CustomTypoScriptConditionProvider PHP class as provider within the context typoscript.

EXT:some_extension/Configuration/ExpressionLanguage.php
return [
    'typoscript' => [
        \MyVendor\SomeExtension\ExpressionLanguage\CustomTypoScriptConditionProvider::class,
    ]
];
Copied!

Implementing a provider

The provider is a PHP class like /Classes/ExpressionLanguage/CustomTypoScriptConditionProvider.php, depending on the formerly registered PHP class name:

EXT:some_extension/Classes/ExpressionLanguage/CustomTypoScriptConditionProvider.php
namespace MyVendor\SomeExtension\ExpressionLanguage;

use TYPO3\CMS\Core\ExpressionLanguage\AbstractProvider;

class CustomTypoScriptConditionProvider extends AbstractProvider
{
    public function __construct()
    {
    }
}
Copied!

Additional variables

Additional variables can be provided by the registered provider class. In practice, adding additional variables is used rather seldom: To access state, they tend to use $GLOBALS, which in general is not a good idea. Instead, consuming code should provide available variables by handing them over to the Resolver constructor already. The example below adds a new variable variableA with value valueB:

EXT:some_extension/Classes/ExpressionLanguage/CustomTypoScriptConditionProvider.php
class CustomTypoScriptConditionProvider extends AbstractProvider
{
    public function __construct()
    {
        $this->expressionLanguageVariables = [
            'variableA' => 'valueB',
        ];
    }
}
Copied!

Additional functions

Additional functions can be provided with another class that has to be registered in the provider:

EXT:some_extension/Classes/ExpressionLanguage/CustomTypoScriptConditionProvider.php
class CustomTypoScriptConditionProvider extends AbstractProvider
{
    public function __construct()
    {
        $this->expressionLanguageProviders = [
            CustomConditionFunctionsProvider::class,
        ];
    }
}
Copied!

The (artificial) implementation below calls some external URL based on given variables:

EXT:some_extension/Classes/ExpressionLanguage/CustomConditionFunctionsProvider.php
namespace Vendor\SomeExtension\TypoScript;

use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;

class CustomConditionFunctionsProvider implements ExpressionFunctionProviderInterface
{
    public function getFunctions()
    {
        return [
            $this->getWebserviceFunction(),
        ];
    }

    protected function getWebserviceFunction(): ExpressionFunction
    {
        return new ExpressionFunction(
            'webservice',
            static fn () => null, // Not implemented, we only use the evaluator
            static function ($arguments, $endpoint, $uid) {
                return GeneralUtility::getUrl(
                    'https://example.org/endpoint/'
                    . $endpoint
                    . '/'
                    . $uid
                );
            }
        );
    }
}
Copied!

A usage example in TypoScript could be this:

EXT:some_extension/Configuration/TypoScript/setup.typoscript
[webservice('pages', 10)]
    page.10 >
    page.10 = TEXT
    page.10.value = Matched
[GLOBAL]

# Or compare the result of the function to a string
[webservice('pages', 10) === 'Expected page title']
    page.10 >
    page.10 = TEXT
    page.10.value = Matched
[GLOBAL]

# if there are no parameters, your own conditions still need brackets
[conditionWithoutParameters()]
    # do something
[GLOBAL]
Copied!