Skip to content

[DI] Allow processing env vars #23901

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
Sep 7, 2017
Merged

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Aug 16, 2017

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:

#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')))).

@nicolas-grekas nicolas-grekas added this to the 3.4 milestone Aug 16, 2017
@nicolas-grekas nicolas-grekas changed the title [DI] Allow processing env vars - handles int/float/file/array/base64 + "container.env_provider"-tagged services [DI] Allow processing env vars - handles int/float/file/json/base64 + "container.env_provider"-tagged services Aug 16, 2017
@marfillaster
Copy link
Contributor

👍 I totally approve this solution. The chained processing is just genius.

*
* @throws RuntimeException on error
*/
public function getEnv($prefix, $name, \Closure $getEnv);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)?

Copy link
Member Author

@nicolas-grekas nicolas-grekas Aug 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

namespace parsing already happens in Container::getEnv.
What you see here is forwarding, which is something not all providers are required to do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never mind, thats simply $value = $getEnv($name) currently. Basically the trick is to not forget return $getEnv($name) for defaulting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)")];
Copy link
Member

@yceruto yceruto Aug 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we fix #23527 (comment) somehow?

Copy link
Member Author

@nicolas-grekas nicolas-grekas Aug 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yceruto here we are, with a new resolve handler to the rescue.

@mnapoli
Copy link
Contributor

mnapoli commented Aug 16, 2017

Is %env(json:base64:file:FOO)% really more understandable than json_decode(base64_decode(file_get_content(getenv('FOO'))))?
This is yet another custom syntax that Symfony users will need to learn, whereas plain old PHP would do the same with a few more characters. Maybe these kind of problems could be solved with #22407/#23834 and allowing to write some entries as PHP closures? This is a more complex solution though ("easy to say"), but the end result would be maybe much simpler for users?

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Aug 16, 2017

@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. %env(FOO)% is the syntax that we already use to be able to declare that intention of compiling down to dynamic calls. The namespaced env vars only build on top of this to add more processing capabilities to env, nothing more. It's not only about syntactic sugar.

@mnapoli
Copy link
Contributor

mnapoli commented Aug 16, 2017

👍 sorry :)

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the service class existence is not resolved at compile time?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as done commonly in other passes: when it's not required at compile time, we usually don't validate that kind of things.

@nicolas-grekas nicolas-grekas changed the title [DI] Allow processing env vars - handles int/float/file/json/base64 + "container.env_provider"-tagged services [DI] Allow processing env vars Aug 16, 2017
@@ -43,6 +43,7 @@ public function __construct()
100 => array(
$resolveClassPass = new ResolveClassPass(),
new ResolveInstanceofConditionalsPass(),
new RegisterEnvProvidersPass(),
Copy link
Member Author

@nicolas-grekas nicolas-grekas Aug 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

@Pierstoval
Copy link
Contributor

I cannot agree more on this PR 👍

@ro0NL
Copy link
Contributor

ro0NL commented Aug 17, 2017

The str_ireplace seems to trigger an unexpected Array to string conversion

$_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'));
{#136
  +"real": "runtime"
  +"real_inline": "compiletime"
}

This looks bad 🤔


What im worried about is the current default strategy. With this feature things may work unexpected due the exact lookup. setParameter('env(REAL)', ...) will not be used if combined with any prefix, you need to expose all variants :(

Also related; if in above example you leave out the default ("compiletime"); you'll get

The service "teatime" has a dependency on a non-existent parameter "env(real)"

Yet we did a real lookup already.. so what's the deal here?


Otherwise it seems to work just great :)

@nicolas-grekas
Copy link
Member Author

@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 compile(true) mode, which I'm going to fix in another PR on 3.3.

@kbond
Copy link
Member

kbond commented Aug 17, 2017

What is the reason for no bool prefix?

@nicolas-grekas
Copy link
Member Author

@kbond no reason, fixed.
Now with a new const filter also.

@@ -4,6 +4,7 @@ CHANGELOG
3.4.0
-----

* added `EnvProviderInterface` and corresponding "container.env_provider" tag for processing env vars
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be even more precise, it's processors for env vars. So, could be EnvVarProcessorInterface.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


private static function phpize($value)
{
if (!class_exists(XmlUtils::class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a smell that this class should be renamed, or at least some part of it extracted into a more generic class.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a blocker here, and no better idea :)

$i = strpos($name, ':');

if ('file' === $prefix) {
if (0 < $i && strspn($name, '0123456789') === $i) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does support for a length really useful? What is the use case? Apart from the unit test that uses /dev/urandom?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@nicolas-grekas nicolas-grekas force-pushed the env-provider branch 5 times, most recently from 26c28eb to 019a582 Compare September 6, 2017 21:02
@@ -36,6 +36,7 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\EnvProviderInterface;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be renamed as well

@fabpot
Copy link
Member

fabpot commented Sep 6, 2017

Still some provider usages (like variable names for instance).

@fabpot
Copy link
Member

fabpot commented Sep 7, 2017

Thank you @nicolas-grekas.

@fabpot fabpot merged commit 1f92e45 into symfony:3.4 Sep 7, 2017
fabpot added a commit that referenced this pull request Sep 7, 2017
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
@nicolas-grekas nicolas-grekas deleted the env-provider branch September 10, 2017 16:11
@sroze
Copy link
Contributor

sroze commented Sep 12, 2017

Great job @nicolas-grekas 👏

This was referenced Oct 18, 2017
sayjun0505 added a commit to sayjun0505/sym_proj that referenced this pull request Apr 16, 2023
…ameter (yceruto)

This PR was merged into the master branch.

Discussion
----------

Workaround to use DATABASE_URL with %kernel.project_dir% parameter

Setting `DATABASE_URL=sqlite:///var/data/blog.sqlite` as default, causes this known error through `index.php`:
```
An exception occurred in driver: SQLSTATE[HY000] [14] unable to open database file
```
So the right relative path in this case should be `sqlite:///../var/data/blog.sqlite` BUT then it wouldn't work for `bin/console` entries. Then, relative paths shouldn't be the right way but absolute paths.

The right thing is that soon (in 3.4 & 4.0) we can use environment variables with configuration parameters symfony/symfony#23901, so the final configuration would be the one proposed. For now a workaround to make both scenarios work `index.php` and `bin/console`.

Commits
-------

e45b5d5 Workaround to use DATABASE_URL with %kernel.project_dir% parameter
spider-yamet added a commit to spider-yamet/sym_proj that referenced this pull request Apr 16, 2023
…ameter (yceruto)

This PR was merged into the master branch.

Discussion
----------

Workaround to use DATABASE_URL with %kernel.project_dir% parameter

Setting `DATABASE_URL=sqlite:///var/data/blog.sqlite` as default, causes this known error through `index.php`:
```
An exception occurred in driver: SQLSTATE[HY000] [14] unable to open database file
```
So the right relative path in this case should be `sqlite:///../var/data/blog.sqlite` BUT then it wouldn't work for `bin/console` entries. Then, relative paths shouldn't be the right way but absolute paths.

The right thing is that soon (in 3.4 & 4.0) we can use environment variables with configuration parameters symfony/symfony#23901, so the final configuration would be the one proposed. For now a workaround to make both scenarios work `index.php` and `bin/console`.

Commits
-------

e45b5d5 Workaround to use DATABASE_URL with %kernel.project_dir% parameter
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.