diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
index a3478c0b4f..79086dbbb8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
@@ -12,6 +12,7 @@ CHANGELOG
* Deprecated passing a `RouteCollectionBuiler` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead
* The `TemplateController` now accepts context argument
* Deprecated *not* setting the "framework.router.utf8" configuration option as it will default to `true` in Symfony 6.0
+ * Added tag `routing.expression_language_function` to define functions available in route conditions
5.0.0
-----
diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php
index 454767e6a8..1e2fefbbac 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php
@@ -33,12 +33,14 @@ class RouterMatchCommand extends Command
protected static $defaultName = 'router:match';
private $router;
+ private $expressionLanguageProviders;
- public function __construct(RouterInterface $router)
+ public function __construct(RouterInterface $router, iterable $expressionLanguageProviders = [])
{
parent::__construct();
$this->router = $router;
+ $this->expressionLanguageProviders = $expressionLanguageProviders;
}
/**
@@ -87,6 +89,9 @@ EOF
}
$matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context);
+ foreach ($this->expressionLanguageProviders as $provider) {
+ $matcher->addExpressionLanguageProvider($provider);
+ }
$traces = $matcher->getTraces($input->getArgument('path_info'));
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
index 7a966fd214..0027505f25 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
@@ -49,6 +49,7 @@ class UnusedTagsPass implements CompilerPassInterface
'mime.mime_type_guesser',
'monolog.logger',
'proxy',
+ 'routing.expression_language_function',
'routing.expression_language_provider',
'routing.loader',
'routing.route_loader',
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
index 6333f2d3cd..cbd43ac7a6 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
@@ -145,6 +145,7 @@
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml
index 3482321a48..18b3429a72 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml
@@ -86,9 +86,18 @@
%request_listener.http_port%
%request_listener.https_port%
+
+ _functions
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.xml
index 15dbabd437..5c514e3461 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.xml
@@ -11,8 +11,8 @@
-
-
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
index 3c15f10abb..d9035ca7b8 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
@@ -130,18 +130,19 @@
-
-
-
-
-
-
-
-
- getEnv
-
-
+
+
+
+
+ getEnv
+
+
+
+
+
+
+
diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md
index 8c712e0e0b..23d32c3242 100644
--- a/src/Symfony/Component/Routing/CHANGELOG.md
+++ b/src/Symfony/Component/Routing/CHANGELOG.md
@@ -9,6 +9,7 @@ CHANGELOG
* added "priority" option to annotated routes
* added argument `$priority` to `RouteCollection::add()`
* deprecated the `RouteCompiler::REGEX_DELIMITER` constant
+ * added `ExpressionLanguageProvider` to expose extra functions to route conditions
5.0.0
-----
diff --git a/src/Symfony/Component/Routing/Matcher/ExpressionLanguageProvider.php b/src/Symfony/Component/Routing/Matcher/ExpressionLanguageProvider.php
new file mode 100644
index 0000000000..9b1bfe3fb4
--- /dev/null
+++ b/src/Symfony/Component/Routing/Matcher/ExpressionLanguageProvider.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Matcher;
+
+use Symfony\Component\ExpressionLanguage\ExpressionFunction;
+use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
+use Symfony\Contracts\Service\ServiceProviderInterface;
+
+/**
+ * Exposes functions defined in the request context to route conditions.
+ *
+ * @author Ahmed TAILOULOUTE
+ */
+class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
+{
+ private $functions;
+
+ public function __construct(ServiceProviderInterface $functions)
+ {
+ $this->functions = $functions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFunctions()
+ {
+ foreach ($this->functions->getProvidedServices() as $function => $type) {
+ yield new ExpressionFunction(
+ $function,
+ static function (...$args) use ($function) {
+ return sprintf('($context->getParameter(\'_functions\')->get(%s)(%s))', var_export($function, true), implode(', ', $args));
+ },
+ function ($values, ...$args) use ($function) {
+ return $values['context']->getParameter('_functions')->get($function)(...$args);
+ }
+ );
+ }
+ }
+
+ public function get(string $function): callable
+ {
+ return $this->functions->get($function);
+ }
+}
diff --git a/src/Symfony/Component/Routing/Tests/Matcher/ExpressionLanguageProviderTest.php b/src/Symfony/Component/Routing/Tests/Matcher/ExpressionLanguageProviderTest.php
new file mode 100644
index 0000000000..0aa3549b26
--- /dev/null
+++ b/src/Symfony/Component/Routing/Tests/Matcher/ExpressionLanguageProviderTest.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Routing\Tests\Matcher;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\Routing\Matcher\ExpressionLanguageProvider;
+use Symfony\Component\Routing\RequestContext;
+
+class ExpressionLanguageProviderTest extends TestCase
+{
+ private $context;
+ private $expressionLanguage;
+
+ protected function setUp(): void
+ {
+ $functionProvider = new ServiceLocator([
+ 'env' => function () {
+ // function with one arg
+ return function (string $arg) {
+ return [
+ 'APP_ENV' => 'test',
+ 'PHP_VERSION' => '7.2',
+ ][$arg] ?? null;
+ };
+ },
+ 'sum' => function () {
+ // function with multiple args
+ return function ($a, $b) { return $a + $b; };
+ },
+ 'foo' => function () {
+ // function with no arg
+ return function () { return 'bar'; };
+ },
+ ]);
+
+ $this->context = new RequestContext();
+ $this->context->setParameter('_functions', $functionProvider);
+
+ $this->expressionLanguage = new ExpressionLanguage();
+ $this->expressionLanguage->registerProvider(new ExpressionLanguageProvider($functionProvider));
+ }
+
+ /**
+ * @dataProvider compileProvider
+ */
+ public function testCompile(string $expression, string $expected)
+ {
+ $this->assertSame($expected, $this->expressionLanguage->compile($expression));
+ }
+
+ public function compileProvider(): iterable
+ {
+ return [
+ ['env("APP_ENV")', '($context->getParameter(\'_functions\')->get(\'env\')("APP_ENV"))'],
+ ['sum(1, 2)', '($context->getParameter(\'_functions\')->get(\'sum\')(1, 2))'],
+ ['foo()', '($context->getParameter(\'_functions\')->get(\'foo\')())'],
+ ];
+ }
+
+ /**
+ * @dataProvider evaluateProvider
+ */
+ public function testEvaluate(string $expression, $expected)
+ {
+ $this->assertSame($expected, $this->expressionLanguage->evaluate($expression, ['context' => $this->context]));
+ }
+
+ public function evaluateProvider(): iterable
+ {
+ return [
+ ['env("APP_ENV")', 'test'],
+ ['env("PHP_VERSION")', '7.2'],
+ ['env("unknown_env_variable")', null],
+ ['sum(1, 2)', 3],
+ ['foo()', 'bar'],
+ ];
+ }
+}