Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 53df70e

Browse filesBrowse files
committed
feature #35747 [Routing][FrameworkBundle] Allow using env() in route conditions (atailouloute)
This PR was merged into the 5.1-dev branch. Discussion ---------- [Routing][FrameworkBundle] Allow using env() in route conditions | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | | License | MIT | Doc PR | TODO This is a second implementation of #35727, it overcomes the limitation mentioned by nicolas in (#35727 (comment)) The goal of this feature is to be able to use env variables in Route conditions ```php /** * @route("/only-for-dev", condition="env('APP_ENV') === 'dev'") */ public function __invoke() { echo "This will be executed only when APP_ENV = dev"; } ``` it supports also env processors/ loaders ```php /** * @route("/only-for-dev", condition="env('trim:APP_ENV') === 'dev'") */ ```` **TODOs:** - [x] Complete unit tests Commits ------- b574460 [Routing][FrameworkBundle] Allow using env() in route conditions
2 parents 8867f57 + b574460 commit 53df70e
Copy full SHA for 53df70e

File tree

10 files changed

+175
-13
lines changed
Filter options

10 files changed

+175
-13
lines changed

‎src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
* Deprecated passing a `RouteCollectionBuiler` to `MicroKernelTrait::configureRoutes()`, type-hint `RoutingConfigurator` instead
1313
* The `TemplateController` now accepts context argument
1414
* Deprecated *not* setting the "framework.router.utf8" configuration option as it will default to `true` in Symfony 6.0
15+
* Added tag `routing.expression_language_function` to define functions available in route conditions
1516

1617
5.0.0
1718
-----

‎src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Command/RouterMatchCommand.php
+6-1Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ class RouterMatchCommand extends Command
3333
protected static $defaultName = 'router:match';
3434

3535
private $router;
36+
private $expressionLanguageProviders;
3637

37-
public function __construct(RouterInterface $router)
38+
public function __construct(RouterInterface $router, iterable $expressionLanguageProviders = [])
3839
{
3940
parent::__construct();
4041

4142
$this->router = $router;
43+
$this->expressionLanguageProviders = $expressionLanguageProviders;
4244
}
4345

4446
/**
@@ -87,6 +89,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8789
}
8890

8991
$matcher = new TraceableUrlMatcher($this->router->getRouteCollection(), $context);
92+
foreach ($this->expressionLanguageProviders as $provider) {
93+
$matcher->addExpressionLanguageProvider($provider);
94+
}
9095

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

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class UnusedTagsPass implements CompilerPassInterface
4949
'mime.mime_type_guesser',
5050
'monolog.logger',
5151
'proxy',
52+
'routing.expression_language_function',
5253
'routing.expression_language_provider',
5354
'routing.loader',
5455
'routing.route_loader',

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145

146146
<service id="console.command.router_match" class="Symfony\Bundle\FrameworkBundle\Command\RouterMatchCommand">
147147
<argument type="service" id="router" />
148+
<argument type="tagged_iterator" tag="routing.expression_language_provider" />
148149
<tag name="console.command" command="router:match" />
149150
</service>
150151

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,18 @@
8686
<argument></argument> <!-- scheme -->
8787
<argument>%request_listener.http_port%</argument>
8888
<argument>%request_listener.https_port%</argument>
89+
<call method="setParameter">
90+
<argument>_functions</argument>
91+
<argument type="service" id="router.expression_language_provider" />
92+
</call>
8993
</service>
9094
<service id="Symfony\Component\Routing\RequestContext" alias="router.request_context" />
9195

96+
<service id="router.expression_language_provider" class="Symfony\Component\Routing\Matcher\ExpressionLanguageProvider">
97+
<argument type="tagged_locator" tag="routing.expression_language_function" index-by="function" />
98+
<tag name="routing.expression_language_provider" />
99+
</service>
100+
92101
<service id="router.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\RouterCacheWarmer">
93102
<tag name="container.service_subscriber" id="router" />
94103
<tag name="kernel.cache_warmer" />

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/secrets.xml
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
<argument type="service" id="secrets.decryption_key" on-invalid="ignore" />
1212
</service>
1313

14-
<service id="secrets.decryption_key" parent="getenv">
15-
<argument />
14+
<service id="secrets.decryption_key" parent="container.env">
15+
<argument /><!-- the name of the env var to read -->
1616
</service>
1717

1818
<service id="secrets.local_vault" class="Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault">

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
+11-10Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -130,18 +130,19 @@
130130
</service>
131131
<service id="Symfony\Component\String\Slugger\SluggerInterface" alias="slugger" />
132132

133+
<service id="container.getenv" class="Closure">
134+
<factory class="Closure" method="fromCallable" />
135+
<argument type="collection">
136+
<argument type="service" id="service_container" />
137+
<argument>getEnv</argument>
138+
</argument>
139+
<tag name="routing.expression_language_function" function="env" />
140+
</service>
141+
133142
<!-- inherit from this service to lazily access env vars -->
134-
<service id="getenv" class="Symfony\Component\String\LazyString" abstract="true">
143+
<service id="container.env" class="Symfony\Component\String\LazyString" abstract="true">
135144
<factory class="Symfony\Component\String\LazyString" method="fromCallable" />
136-
<argument type="service">
137-
<service class="Closure">
138-
<factory class="Closure" method="fromCallable" />
139-
<argument type="collection">
140-
<argument type="service" id="service_container" />
141-
<argument>getEnv</argument>
142-
</argument>
143-
</service>
144-
</argument>
145+
<argument type="service" id="container.getenv" />
145146
</service>
146147
</services>
147148
</container>

‎src/Symfony/Component/Routing/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Routing/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* added "priority" option to annotated routes
1010
* added argument `$priority` to `RouteCollection::add()`
1111
* deprecated the `RouteCompiler::REGEX_DELIMITER` constant
12+
* added `ExpressionLanguageProvider` to expose extra functions to route conditions
1213

1314
5.0.0
1415
-----
+54Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Routing\Matcher;
13+
14+
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
15+
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
16+
use Symfony\Contracts\Service\ServiceProviderInterface;
17+
18+
/**
19+
* Exposes functions defined in the request context to route conditions.
20+
*
21+
* @author Ahmed TAILOULOUTE <ahmed.tailouloute@gmail.com>
22+
*/
23+
class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
24+
{
25+
private $functions;
26+
27+
public function __construct(ServiceProviderInterface $functions)
28+
{
29+
$this->functions = $functions;
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function getFunctions()
36+
{
37+
foreach ($this->functions->getProvidedServices() as $function => $type) {
38+
yield new ExpressionFunction(
39+
$function,
40+
static function (...$args) use ($function) {
41+
return sprintf('($context->getParameter(\'_functions\')->get(%s)(%s))', var_export($function, true), implode(', ', $args));
42+
},
43+
function ($values, ...$args) use ($function) {
44+
return $values['context']->getParameter('_functions')->get($function)(...$args);
45+
}
46+
);
47+
}
48+
}
49+
50+
public function get(string $function): callable
51+
{
52+
return $this->functions->get($function);
53+
}
54+
}
+89Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Routing\Tests\Matcher;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\ServiceLocator;
16+
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
17+
use Symfony\Component\Routing\Matcher\ExpressionLanguageProvider;
18+
use Symfony\Component\Routing\RequestContext;
19+
20+
class ExpressionLanguageProviderTest extends TestCase
21+
{
22+
private $context;
23+
private $expressionLanguage;
24+
25+
protected function setUp(): void
26+
{
27+
$functionProvider = new ServiceLocator([
28+
'env' => function () {
29+
// function with one arg
30+
return function (string $arg) {
31+
return [
32+
'APP_ENV' => 'test',
33+
'PHP_VERSION' => '7.2',
34+
][$arg] ?? null;
35+
};
36+
},
37+
'sum' => function () {
38+
// function with multiple args
39+
return function ($a, $b) { return $a + $b; };
40+
},
41+
'foo' => function () {
42+
// function with no arg
43+
return function () { return 'bar'; };
44+
},
45+
]);
46+
47+
$this->context = new RequestContext();
48+
$this->context->setParameter('_functions', $functionProvider);
49+
50+
$this->expressionLanguage = new ExpressionLanguage();
51+
$this->expressionLanguage->registerProvider(new ExpressionLanguageProvider($functionProvider));
52+
}
53+
54+
/**
55+
* @dataProvider compileProvider
56+
*/
57+
public function testCompile(string $expression, string $expected)
58+
{
59+
$this->assertSame($expected, $this->expressionLanguage->compile($expression));
60+
}
61+
62+
public function compileProvider(): iterable
63+
{
64+
return [
65+
['env("APP_ENV")', '($context->getParameter(\'_functions\')->get(\'env\')("APP_ENV"))'],
66+
['sum(1, 2)', '($context->getParameter(\'_functions\')->get(\'sum\')(1, 2))'],
67+
['foo()', '($context->getParameter(\'_functions\')->get(\'foo\')())'],
68+
];
69+
}
70+
71+
/**
72+
* @dataProvider evaluateProvider
73+
*/
74+
public function testEvaluate(string $expression, $expected)
75+
{
76+
$this->assertSame($expected, $this->expressionLanguage->evaluate($expression, ['context' => $this->context]));
77+
}
78+
79+
public function evaluateProvider(): iterable
80+
{
81+
return [
82+
['env("APP_ENV")', 'test'],
83+
['env("PHP_VERSION")', '7.2'],
84+
['env("unknown_env_variable")', null],
85+
['sum(1, 2)', 3],
86+
['foo()', 'bar'],
87+
];
88+
}
89+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.