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\
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
$my
. 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/
.
This will register the defined Custom
PHP class as
provider within the context typoscript
.
return [
'typoscript' => [
\MyVendor\SomeExtension\ExpressionLanguage\CustomTypoScriptConditionProvider::class,
]
];
Implementing a provider
The provider is a PHP class like /Classes/
,
depending on the formerly registered PHP class name:
namespace MyVendor\SomeExtension\ExpressionLanguage;
use TYPO3\CMS\Core\ExpressionLanguage\AbstractProvider;
class CustomTypoScriptConditionProvider extends AbstractProvider
{
public function __construct()
{
}
}
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 variable
with value value
:
class CustomTypoScriptConditionProvider extends AbstractProvider
{
public function __construct()
{
$this->expressionLanguageVariables = [
'variableA' => 'valueB',
];
}
}
Additional functions
Additional functions can be provided with another class that has to be registered in the provider:
class CustomTypoScriptConditionProvider extends AbstractProvider
{
public function __construct()
{
$this->expressionLanguageProviders = [
CustomConditionFunctionsProvider::class,
];
}
}
The (artificial) implementation below calls some external URL based on given variables:
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
);
}
);
}
}
A usage example in TypoScript could be this:
[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]