8000 [DI] Allow fetching env vars with lookup-dedicated services · symfony/symfony@7aee5b6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7aee5b6

Browse files
[DI] Allow fetching env vars with lookup-dedicated services
1 parent 22f00a2 commit 7aee5b6

File tree

13 files changed

+237
-6
lines changed

13 files changed

+237
-6
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
17+
use Symfony\Component\DependencyInjection\GetEnvInterface;
18+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
19+
20+
/**
21+
* Checks that all env-referenced services exist and implement the related interface.
22+
*
23+
* @author Nicolas Grekas <p@tchwork.com>
24+
*/
25+
class CheckEnvReferencedServicesPass implements CompilerPassInterface
26+
{
27+
public function process(ContainerBuilder $container)
28+
{
29+
$envReferencedServices = $container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $container->getParameterBag()->getEnvReferencedServices() : array();
30+
31+
foreach ($envReferencedServices as $id) {
32+
if (!$container->has($id)) {
33+
throw new ServiceNotFoundException($id);
34+
}
35+
$class = $container->getDefinition($id)->getClass();
36+
if (!is_subclass_of($class, GetEnvInterface::class)) {
37+
if (!class_exists($class, false)) {
38+
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
39+
}
40+
41+
throw new InvalidArgumentException(sprintf('The service "%s" referenced in env parameters must implement "%s".', $id, GetEnvInterface::class));
42+
}
43+
}
44+
}
45+
}

src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class InlineServiceDefinitionsPass implements RepeatablePassInterface
2727
private $compiler;
2828
private $formatter;
2929
private $currentId;
30+
private $envReferencedServices;
3031

3132
/**
3233
* {@inheritdoc}
@@ -46,6 +47,7 @@ public function process(ContainerBuilder $container)
4647
$this->compiler = $container->getCompiler();
4748
$this->formatter = $this->compiler->getLoggingFormatter();
4849
$this->graph = $this->compiler->getServiceReferenceGraph();
50+
$this->envReferencedServices = $container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $container->getParameterBag()->getEnvReferencedServices() : array();
4951

5052
$container->setDefinitions($this->inlineArguments($container, $container->getDefinitions(), true));
5153
}
@@ -107,6 +109,10 @@ private function inlineArguments(ContainerBuilder $container, array $arguments,
107109
*/
108110
private function isInlineableDefinition($id, Definition $definition)
109111
{
112+
if (isset($this->envReferencedServices[$id])) {
113+
return false;
114+
}
115+
110116
if (!$definition->isShared()) {
111117
return true;
112118
}

src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,11 @@ public function __construct()
6464
new AnalyzeServiceReferencesPass(),
6565
new RemoveUnusedDefinitionsPass(),
6666
)),
67+
));
68+
69+
$this->afterRemovingPasses = array(array(
6770
new CheckExceptionOnInvalidReferenceBehaviorPass(),
71+
new CheckEnvReferencedServicesPass(),
6872
));
6973
}
7074

src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Compiler;
1313

1414
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
1516

1617
/**
1718
* Removes unused service definitions from the container.
@@ -40,10 +41,11 @@ public function process(ContainerBuilder $container)
4041
$compiler = $container->getCompiler();
4142
$formatter = $compiler->getLoggingFormatter();
4243
$graph = $compiler->getServiceReferenceGraph();
44+
$envReferencedServices = $container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $container->getParameterBag()->getEnvReferencedServices() : array();
4345

4446
$hasChanged = false;
4547
foreach ($container->getDefinitions() as $id => $definition) {
46-
if ($definition->isPublic()) {
48+
if ($definition->isPublic() || isset($envReferencedServices[$id])) {
4749
continue;
4850
}
4951

src/Symfony/Component/DependencyInjection/Container.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,14 @@ protected function getEnv($name)
419419
if (false !== $env = getenv($name)) {
420420
return $this->envCache[$name] = $env;
421421
}
422+
if (false !== $i = strpos($name, '@')) {
423+
$id = substr($name, 1 + $i);
424+
$service = isset($this->services[$id]) ? $this->services[$id] : (isset($this->methodMap[$id]) ? $this->{$this->methodMap[$id]}() : $this->get($id));
425+
426+
if (null !== $env = $service->getEnv(substr($name, 0, $i))) {
427+
return $this->envCache[$name] = $env;
428+
}
429+
}
422430
if (!$this->hasParameter("env($name)")) {
423431
throw new EnvNotFoundException($name);
424432
}

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,7 @@ private function addDefaultParametersMethod()
10041004
$export = $this->exportParameters(array($value));
10051005
$export = explode('0 => ', substr(rtrim($export, " )\n"), 7, -1), 2);
10061006

1007-
if (preg_match("/\\\$this->(?:getEnv\('\w++'\)|targetDirs\[\d++\])/", $export[1])) {
1007+
if (preg_match("/\\\$this->(?:getEnv\('[.a-zA-Z0-9_\x7f-\xff]++(?:@[.a-zA-Z0-9_\x7f-\xff]++)?'\)|targetDirs\[\d++\])/", $export[1])) {
10081008
$dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]);
10091009
} else {
10101010
$php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\DependencyInjection;
13+
14+
/**
15+
* The GetEnvInterface is implemented by objects that manage environment-like variables.
16+
*
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
*/
19+
interface GetEnvInterface
20+
{
21+
/**
22+
* Returns the value of the given variable as managed by the current instance.
23+
*
24+
* @param string $name The name of the variable
25+
*
26+
* @return mixed The value of the given variable
27+
*/
28+
public function getEnv($name);
29+
}

src/Symfony/Component/DependencyInjection/ParameterBag/EnvPlaceholderParameterBag.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
class EnvPlaceholderParameterBag extends ParameterBag
2121
{
2222
private $envPlaceholders = array();
23+
private $envReferencedServices = array();
2324

2425
/**
2526
* {@inheritdoc}
@@ -34,7 +35,7 @@ public function get($name)
3435
return $placeholder; // return first result
3536
}
3637
}
37-
if (preg_match('/\W/', $env)) {
38+
if (!preg_match('/^([.a-zA-Z0-9_\x7f-\xff]++)(?:@([.a-zA-Z0-9_\x7f-\xff]++))?$/', $env, $match)) {
3839
throw new InvalidArgumentException(sprintf('Invalid %s name: only "word" characters are allowed.', $name));
3940
}
4041

@@ -45,6 +46,11 @@ public function get($name)
4546
throw new RuntimeException(sprintf('The default value of an env() parameter must be scalar, but "%s" given to "%s".', gettype($defaultValue), $name));
4647
}
4748
}
49+
if (isset($match[2])) {
50+
$serviceId = strtolower($match[2]);
51+
$this->envReferencedServices[$serviceId] = $serviceId;
52+
$env = $match[1].'@'.$serviceId;
53+
}
4854

4955
$uniqueName = md5($name.uniqid(mt_rand(), true));
5056
$placeholder = sprintf('env_%s_%s', $env, $uniqueName);
@@ -66,6 +72,16 @@ public function getEnvPlaceholders()
6672
return $this->envPlaceholders;
6773
}
6874

75+
/**
76+
* Returns the list of services referenced in `env()` parameters.
77+
*
78+
* @return string[] The list of services referenced in `env()` parameters
79+
*/
80+
public function getEnvReferencedServices()
81+
{
82+
return $this->envReferencedServices;
83+
}
84+
6985
/**
7086
* Merges the env placeholders of another EnvPlaceholderParameterBag.
7187
*/
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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\DependencyInjection\Tests\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CheckEnvReferencedServicesPass;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\GetEnvInterface;
17+
18+
class CheckEnvReferencedServicePassTest extends \PHPUnit_Framework_TestCase
19+
{
20+
public function testProcess()
21+
{
22+
$container = new ContainerBuilder();
23+
$container->getParameterBag()->get('env(foo@a)');
24+
$container->register('a', GetEnvService::class);
25+
26+
$pass = new CheckEnvReferencedServicesPass();
27+
$pass->process($container);
28+
}
29+
30+
/**
31+
* @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
32+
*/
33+
public function testProcessThrowsExceptionOnInvalidReference()
34+
{
35+
$container = new ContainerBuilder();
36+
$container->getParameterBag()->get('env(foo@a)');
37+
38+
$pass = new CheckEnvReferencedServicesPass();
39+
$pass->process($container);
40+
}
41+
42+
/**
43+
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
44+
*/
45+
public function testProcessThrowsExceptionOnInvalidReferenceFromInlinedDefinition()
46+
{
47+
$container = new ContainerBuilder();
48+
$container->getParameterBag()->get('env(foo@a)');
49+
$container->register('a', \stdClass::class);
50+
51+
$pass = new CheckEnvReferencedServicesPass();
52+
$pass->process($container);
53+
}
54+
}
55+
56+
class GetEnvService implements GetEnvInterface
57+
{
58+
public function getEnv($name)
59+
{
60+
return $name.$name;
61+
}
62+
}

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Config\FileLocator;
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
17+
use Symfony\Component\DependencyInjection\GetEnvInterface;
1718
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
1819
use Symfony\Component\DependencyInjection\Reference;
1920
use Symfony\Component\DependencyInjection\Definition;
@@ -295,7 +296,11 @@ public function testEnvParameter()
295296
$container->compile();
296297
$dumper = new PhpDumper($container);
297298

298-
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services26.php', $dumper->dump(), '->dump() dumps inline definitions which reference service_container');
299+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services26.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_EnvParameters')), '->dump() dumps inline definitions which reference service_container');
300+
301+
require self::$fixturesPath.'/php/services26.php';
302+
$container = new \Symfony_DI_PhpDumper_Test_EnvParameters();
303+
$this->assertSame('BAZ123', $container->getParameter('baz'));
299304
}
300305

301306
/**
@@ -322,3 +327,11 @@ public function testInlinedDefinitionReferencingServiceContainer()
322327
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services13.php', $dumper->dump(), '->dump() dumps inline definitions which reference service_container');
323328
}
324329
}
330+
331+
class EnvResolver implements GetEnvInterface
332+
{
333+
public function getEnv($name)
334+
{
335+
return strtoupper($name).'123';
336+
}
337+
}

0 commit comments

Comments
 (0)
0