8000 [Routing][FrameworkBundle] Allow using env() in route conditions by atailouloute · Pull Request #35747 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ class RouterMatchCommand extends Command
protected static $defaultName = 'router:match' 8000 ;

private $router;
private $expressionLanguageProviders;

public function __construct(RouterInterface $router)
public function __construct(RouterInterface $router, iterable $expressionLanguageProviders = [])
{
parent::__construct();

$this->router = $router;
$this->expressionLanguageProviders = $expressionLanguageProviders;
}

/**
Expand Down Expand Up @@ -87,6 +89,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}

$matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context);
foreach ($this->expressionLanguageProviders as $provider) {
$matcher->addExpressionLanguageProvider($provider);
}

$traces = $matcher->getTraces($input->getArgument('path_info'));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
21 changes: 11 additions & 10 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,19 @@
</service>
<service id="Symfony\Component\String\Slugger\SluggerInterface" alias="slugger" />

<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="getenv" class="Symfony\Component\String\LazyString" abstract="true">
<service id="container.env" 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>
</argument>
<argument type="service" id="container.getenv" />
</service>
</services>
</container>
1 change: 1 addition & 0 deletions src/Symfony/Component/Routing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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'],
];
}
}
0