[DI] Allow processing env vars #23901
Conversation
|
ab01c8d
to
d75dcae
d75dcae
to
f3144e1
* | ||
* @throws RuntimeException on error | ||
*/ | ||
public function getEnv($prefix, $name, \Closure $getEnv); |
ro0NL
Aug 16, 2017
Contributor
preferably the namespace parsing happens in Container::getEnv
no? Basically you need to do the same check each time implementing it. What about getEnv($prefix, $name, $value, \Closure $getEnv = null)
?
preferably the namespace parsing happens in Container::getEnv
no? Basically you need to do the same check each time implementing it. What about getEnv($prefix, $name, $value, \Closure $getEnv = null)
?
nicolas-grekas
Aug 16, 2017
•
Author
Member
namespace parsing already happens in Container::getEnv
.
What you see here is forwarding, which is something not all providers are required to do.
namespace parsing already happens in Container::getEnv
.
What you see here is forwarding, which is something not all providers are required to do.
ro0NL
Aug 16, 2017
Contributor
never mind, thats simply $value = $getEnv($name)
currently. Basically the trick is to not forget return $getEnv($name)
for defaulting.
never mind, thats simply $value = $getEnv($name)
currently. Basically the trick is to not forget return $getEnv($name)
for defaulting.
ro0NL
Aug 16, 2017
Contributor
and i missed your comment in between.. but you're right the current implem is fine.
and i missed your comment in between.. but you're right the current implem is fine.
$resolved = sprintf($format, $env); | ||
} elseif ($placeholder === $resolved = $bag->escapeValue($this->getEnv($env))) { | ||
$resolved = $bag->all()[strtolower("env($env)")]; |
Is |
@mnapoli I think you're forgetting about how things work. Despite your claim in #22407, a PHP DSL would not help at all reading from the env. The reason is compilation of the container. The DSL is read once, at compile time. |
|
if (isset($attr['prefix'])) { | ||
$providers[$attr['prefix']] = new Reference($id); | ||
} elseif (!$r = $container->getReflectionClass($class = $container->getDefinition($id)->getClass())) { | ||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); |
Pierstoval
Aug 16, 2017
Contributor
Does this mean that the service class existence is not resolved at compile time?
Does this mean that the service class existence is not resolved at compile time?
nicolas-grekas
Aug 16, 2017
Author
Member
Yes, as done commonly in other passes: when it's not required at compile time, we usually don't validate that kind of things.
Yes, as done commonly in other passes: when it's not required at compile time, we usually don't validate that kind of things.
f3144e1
to
62678b9
62678b9
to
2baa573
@@ -43,6 +43,7 @@ public function __construct() | |||
100 => array( | |||
$resolveClassPass = new ResolveClassPass(), | |||
new ResolveInstanceofConditionalsPass(), | |||
new RegisterEnvProvidersPass(), |
nicolas-grekas
Aug 16, 2017
•
Author
Member
registered before optimization passes so that env values can be used at compile time (typically using $container->resolveEnvPlaceholders()
.) This makes env providers a bit less flexible (they can't easily be manipulated by DI extensions) but that's consistent with the provided feature to me (env vars should be light to fetch.)
registered before optimization passes so that env values can be used at compile time (typically using $container->resolveEnvPlaceholders()
.) This makes env providers a bit less flexible (they can't easily be manipulated by DI extensions) but that's consistent with the provided feature to me (env vars should be light to fetch.)
2baa573
to
751e5f8
I cannot agree more on this PR |
The $_ENV['REAL'] = '{"real":true}';
$c = new ContainerBuilder();
$c->setParameter('json_real', '%env(json:REAL)%');
$c->register('foo', 'stdClass')->setProperties(array(
'json_real' => '%json_real%',
));
$c->compile(true); Somehow real and default envs get mixed up, probably due reference generation in EnvBag::get() $_ENV['REAL'] = 'runtime';
$c = new ContainerBuilder();
$c->setParameter('env(REAL)', 'compiletime');
$c->setParameter('real', '%env(REAL)%');
$c->register('teatime', 'stdClass')->setProperties(array(
'real' => '%real%',
'real_inline' => '%env(REAL)%',
));
$c->compile(true);
dump($c->get('teatime'));
This looks bad What im worried about is the current default strategy. With this feature things may work unexpected due the exact lookup. Also related; if in above example you leave out the default ("compiletime"); you'll get
Yet we did a real lookup already.. so what's the deal here? Otherwise it seems to work just great :) |
@ro0NL thanks for the detailed report. Good news, both (the 3 in fact) your examples apply to 3.3 also, which means you found bugs. Nothing related to this PR specifically. Those happen only in |
What is the reason for no |
@kbond no reason, fixed. |
751e5f8
to
6e10746
Awesome, can't wait to get this merged :) |
*/ | ||
class RegisterEnvProvidersPass implements CompilerPassInterface | ||
{ | ||
private static $allowedTypes = array('array', 'bool', 'float', 'int', 'string'); |
dunglas
Sep 5, 2017
Member
Why not /* private */ const ALLOWED_TYPES
?
Why not /* private */ const ALLOWED_TYPES
?
nicolas-grekas
Sep 5, 2017
Author
Member
because that wouldn't be really private, whereas a private static has no such downside
because that wouldn't be really private, whereas a private static has no such downside
} | ||
|
||
if ($providers) { | ||
if ($container->getParameterBag() instanceof EnvPlaceholderParameterBag) { |
dunglas
Sep 5, 2017
•
Member
Can be stored in a var to prevent an extra function call.
Can be stored in a var to prevent an extra function call.
} | ||
} | ||
|
||
private static function validateProvidedTypes($types, $class) |
dunglas
Sep 5, 2017
Member
What's the benefit of making this function static, as it is already private?
What's the benefit of making this function static, as it is already private?
nicolas-grekas
Sep 5, 2017
Author
Member
none, but another reviewer asked for it, and there is neither no benefit not doing it static :)
none, but another reviewer asked for it, and there is neither no benefit not doing it static :)
} | ||
|
||
if ('bool' === $prefix) { | ||
return (bool) XmlUtils::phpize($env); |
dunglas
Sep 5, 2017
Member
Shouldn't symfony/config
become a hard dependency?
Shouldn't symfony/config
become a hard dependency?
nicolas-grekas
Sep 5, 2017
Author
Member
a exception is now thrown if Config is missing
a exception is now thrown if Config is missing
24dce14
to
575eae8
@@ -4,6 +4,7 @@ CHANGELOG | |||
3.4.0 | |||
----- | |||
|
|||
* added `EnvProviderInterface` and corresponding "container.env_provider" tag for processing env vars |
fabpot
Sep 6, 2017
Member
I don't like the "provider" name here as implementations do not provide anything. They process env vars, they filter them (Twig's name). So, I would use something like EnvProcessorInterface
and container.env_processor
.
I don't like the "provider" name here as implementations do not provide anything. They process env vars, they filter them (Twig's name). So, I would use something like EnvProcessorInterface
and container.env_processor
.
fabpot
Sep 6, 2017
Member
To be even more precise, it's processors for env vars. So, could be EnvVarProcessorInterface
.
To be even more precise, it's processors for env vars. So, could be EnvVarProcessorInterface
.
nicolas-grekas
Sep 6, 2017
Author
Member
done
done
|
||
private static function phpize($value) | ||
{ | ||
if (!class_exists(XmlUtils::class)) { |
fabpot
Sep 6, 2017
Member
Probably a smell that this class should be renamed, or at least some part of it extracted into a more generic class.
Probably a smell that this class should be renamed, or at least some part of it extracted into a more generic class.
nicolas-grekas
Sep 6, 2017
Author
Member
not a blocker here, and no better idea :)
not a blocker here, and no better idea :)
$i = strpos($name, ':'); | ||
|
||
if ('file' === $prefix) { | ||
if (0 < $i && strspn($name, '0123456789') === $i) { |
fabpot
Sep 6, 2017
Member
Does support for a length really useful? What is the use case? Apart from the unit test that uses /dev/urandom
?
Does support for a length really useful? What is the use case? Apart from the unit test that uses /dev/urandom
?
nicolas-grekas
Sep 6, 2017
Author
Member
removed
removed
26c28eb
to
019a582
@@ -36,6 +36,7 @@ | |||
use Symfony\Component\DependencyInjection\ContainerBuilder; | |||
use Symfony\Component\DependencyInjection\ContainerInterface; | |||
use Symfony\Component\DependencyInjection\Definition; | |||
use Symfony\Component\DependencyInjection\EnvProviderInterface; |
fabpot
Sep 6, 2017
Member
Should be EnvVarProcessorInterface
(same below)
Should be EnvVarProcessorInterface
(same below)
@@ -283,6 +284,8 @@ public function load(array $configs, ContainerBuilder $container) | |||
->addTag('console.command'); | |||
$container->registerForAutoconfiguration(ResourceCheckerInterface::class) | |||
->addTag('config_cache.resource_checker'); | |||
$container->registerForAutoconfiguration(EnvProviderInterface::class) | |||
->addTag('container.env_provider'); |
fabpot
Sep 6, 2017
Member
should be renamed as well
should be renamed as well
Still some |
019a582
to
2499a76
2499a76
to
1f92e45
Thank you @nicolas-grekas. |
This PR was merged into the 3.4 branch. Discussion ---------- [DI] Allow processing env vars | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | see description | License | MIT | Doc PR | - This PR is an updated version of #20276 ~~(it embeds #23899 for now.)~~ It superscedes/closes: - [DI] Add support for secrets #23621 ping @dunglas - Runtime container parameter not found event filter #23669 ping @marfillaster - [DependencyInjection] [DX] Support for JSON string environment variables #23823 ping @Pierstoval - add support for composite environment variables #17689 ping @greg0ire - [DI] ENV parameters at runtime with PHP 7 strict types not working properly #20434 ping @sandrokeil - Issue when using a SQLite database and the DATABASE_URL env var #23527 ping @javiereguiluz #22151 is another story, so not fixed here. The way it works is via `%env(foo:BAR)%` prefixes, where "foo" can be bound to any services you'd like. By default, the following prefixes are supported: - `bool`, `int`, `float`, `string`, `base64` - `const` (for referencing PHP constants) - `json` (supporting only json **arrays** for type predictability) - `file` (eg for access to secrets stored in files.) - `resolve` (for processing parameters inside env vars.) New prefixes can be added by implementing the new `EnvProviderInterface`, and tagging with `container.env_provider` (see `Rot13EnvProvider` in tests.) Prefixes can be combined to chain processing, eg. `%env(json:base64:file:FOO)%` will be roughly equivalent to `json_decode(base64_decode(file_get_content(getenv('FOO'))))`. Commits ------- 1f92e45 [DI] Allow processing env vars
Great job @nicolas-grekas |
This PR is an updated version of #20276
(it embeds #23899 for now.)It superscedes/closes:
#22151 is another story, so not fixed here.
The way it works is via
%env(foo:BAR)%
prefixes, where "foo" can be bound to any services you'd like.By default, the following prefixes are supported:
bool
,int
,float
,string
,base64
const
(for referencing PHP constants)json
(supporting only json arrays for type predictability)file
(eg for access to secrets stored in files.)resolve
(for processing parameters inside env vars.)New prefixes can be added by implementing the new
EnvProviderInterface
, and tagging withcontainer.env_provider
(seeRot13EnvProvider
in tests.)Prefixes can be combined to chain processing, eg.
%env(json:base64:file:FOO)%
will be roughly equivalent tojson_decode(base64_decode(file_get_content(getenv('FOO'))))
.