[Routing][FrameworkBundle] Allow using env() in route conditions

This commit is contained in:
Ahmed TAILOULOUTE 2020-02-14 17:46:38 +01:00 committed by Fabien Potencier
parent 8867f57a44
commit b5744601bf
10 changed files with 176 additions and 14 deletions

View File

@ -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
-----

View File

@ -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'));

View File

@ -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',

View File

@ -145,6 +145,7 @@
<service id="console.command.router_match" class="Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand">
<argument type="service" id="router" />
<argument type="tagged_iterator" tag="routing.expression_language_provider" />
<tag name="console.command" command="router:match" />
</service>

View File

@ -86,9 +86,18 @@
<argument></argument> <!-- scheme -->
<argument>%request_listener.http_port%</argument>
<argument>%request_listener.https_port%</argument>
<call method="setParameter">
<argument>_functions</argument>
<argument type="service" id="router.expression_language_provider" />
</call>
</service>
<service id="Symfony\Component\Routing\RequestContext" alias="router.request_context" />
<service id="router.expression_language_provider" class="Symfony\Component\Routing\Matcher\ExpressionLanguageProvider">
<argument type="tagged_locator" tag="routing.expression_language_function" index-by="function" />
<tag name="routing.expression_language_provider" />
</service>
<service id="router.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer">
<tag name="container.service_subscriber" id="router" />
<tag name="kernel.cache_warmer" />

View File

@ -11,8 +11,8 @@
<argument type="service" id="secrets.decryption_key" on-invalid="ignore" />
</service>
<service id="secrets.decryption_key" parent="getenv">
<argument />
<service id="secrets.decryption_key" parent="container.env">
<argument /><!-- the name of the env var to read -->
</service>
<service id="secrets.local_vault" class="Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault">

View File

@ -130,18 +130,19 @@
</service>
<service id="Symfony\Component\String\Slugger\SluggerInterface" alias="slugger" />
<!-- inherit from this service to lazily access env vars -->
<service id="getenv" class="Symfony\Component\String\LazyString" abstract="true">
<factory class="Symfony\Component\String\LazyString" method="fromCallable" />
<argument type="service">
<service class="Closure">
<factory class="Closure" method="fromCallable" />
<argument type="collection">
<argument type="service" id="service_container" />
<argument>getEnv</argument>
</argument>
</service>
<service id="container.getenv" class="Closure">
<factory class="Closure" method="fromCallable" />
<argument type="collection">
<argument type="service" id="service_container" />
<argument>getEnv</argument>
</argument>
<tag name="routing.expression_language_function" function="env" />
</service>
<!-- inherit from this service to lazily access env vars -->
<service id="container.env" class="Symfony\Component\String\LazyString" abstract="true">
<factory class="Symfony\Component\String\LazyString" method="fromCallable" />
<argument type="service" id="container.getenv" />
</service>
</services>
</container>

View File

@ -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
-----

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <ahmed.tailouloute@gmail.com>
*/
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);
}
}

View File

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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'],
];
}
}