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

[DependencyInjection] Add Enum Env Var Processor #46564

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
Jun 27, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -1722,7 +1722,7 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c
}

if ($config['decryption_env_var']) {
if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $config['decryption_env_var'])) {
if (!preg_match('/^(?:[-.\w\\\\]*+:)*+\w++$/', $config['decryption_env_var'])) {
jack-worman marked this conversation as resolved.
Show resolved Hide resolved
throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var']));
}

Expand Down
1 change: 1 addition & 0 deletions 1 src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CHANGELOG
---

* Add argument `&$asGhostObject` to LazyProxy's `DumperInterface` to allow using ghost objects for lazy loading services
* Add `enum` env var processor

6.1
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*/
class RegisterEnvVarProcessorsPass implements CompilerPassInterface
{
private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string'];
private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string', \BackedEnum::class];

public function process(ContainerBuilder $container)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1539,7 +1539,7 @@ private function addDefaultParametersMethod(): string
$export = $this->exportParameters([$value], '', 12, $hasEnum);
$export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2);

if ($hasEnum || preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) {
if ($hasEnum || preg_match("/\\\$this->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w++'\)|targetDir\.'')/", $export[1])) {
$dynamicPhp[$key] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
} else {
$php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
Expand Down Expand Up @@ -1952,7 +1952,7 @@ private function dumpParameter(string $name): string
return $dumpedValue;
}

if (!preg_match("/\\\$this->(?:getEnv\('(?:[-.\w]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) {
if (!preg_match("/\\\$this->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w++'\)|targetDir\.'')/", $dumpedValue)) {
return sprintf('$this->parameters[%s]', $this->doExport($name));
}
}
Expand Down
30 changes: 26 additions & 4 deletions 30 src/Symfony/Component/DependencyInjection/EnvVarProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
class EnvVarProcessor implements EnvVarProcessorInterface
{
private ContainerInterface $container;
/** @var \Traversable<EnvVarLoaderInterface> */
private \Traversable $loaders;
private array $loadedVars = [];

/**
* @param EnvVarLoaderInterface[] $loaders
* @param \Traversable<EnvVarLoaderInterface>|null $loaders
*/
public function __construct(ContainerInterface $container, \Traversable $loaders = null)
{
Expand Down Expand Up @@ -56,6 +57,7 @@ public static function getProvidedTypes(): array
'string' => 'string',
'trim' => 'string',
'require' => 'bool|int|float|string|array',
'enum' => \BackedEnum::class,
];
}

Expand Down Expand Up @@ -86,6 +88,26 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed
return $array[$key];
}

if ('enum' === $prefix) {
if (false === $i) {
throw new RuntimeException(sprintf('Invalid env "enum:%s": a "%s" class-string should be provided.', $name, \BackedEnum::class));
}

$next = substr($name, $i + 1);
$backedEnumClassName = substr($name, 0, $i);
$backedEnumValue = $getEnv($next);

if (!\is_string($backedEnumValue) && !\is_int($backedEnumValue)) {
throw new RuntimeException(sprintf('Resolved value of "%s" did not result in a string or int value.', $next));
}

if (!is_subclass_of($backedEnumClassName, \BackedEnum::class)) {
throw new RuntimeException(sprintf('"%s" is not a "%s".', $backedEnumClassName, \BackedEnum::class));
}

return $backedEnumClassName::tryFrom($backedEnumValue) ?? throw new RuntimeException(sprintf('Enum value "%s" is not backed by "%s".', $backedEnumValue, $backedEnumClassName));
}

if ('default' === $prefix) {
if (false === $i) {
throw new RuntimeException(sprintf('Invalid env "default:%s": a fallback parameter should be provided.', $name));
Expand All @@ -112,7 +134,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed
}

if ('file' === $prefix || 'require' === $prefix) {
if (!is_scalar($file = $getEnv($name))) {
if (!\is_scalar($file = $getEnv($name))) {
throw new RuntimeException(sprintf('Invalid file name: env var "%s" is non-scalar.', $name));
}
if (!is_file($file)) {
Expand Down Expand Up @@ -184,7 +206,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed
return null;
}

if (!is_scalar($env)) {
if (!\is_scalar($env)) {
throw new RuntimeException(sprintf('Non-scalar env var "%s" cannot be cast to "%s".', $name, $prefix));
}

Expand Down Expand Up @@ -283,7 +305,7 @@ public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed
$value = $this->container->getParameter($match[1]);
}

if (!is_scalar($value)) {
if (!\is_scalar($value)) {
throw new RuntimeException(sprintf('Parameter "%s" found when resolving env var "%s" must be scalar, "%s" given.', $match[1], $name, get_debug_type($value)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,16 @@ public function require(): static

return $this;
}

/**
* @param class-string<\BackedEnum> $backedEnumClassName
*
* @return $this
jack-worman marked this conversation as resolved.
Show resolved Hide resolved
*/
public function enum(string $backedEnumClassName): static
{
array_unshift($this->stack, 'enum', $backedEnumClassName);

return $this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function get(string $name): array|bool|string|int|float|null
return $placeholder; // return first result
}
}
if (!preg_match('/^(?:[-.\w]*+:)*+\w++$/', $env)) {
if (!preg_match('/^(?:[-.\w\\\\]*+:)*+\w++$/', $env)) {
throw new InvalidArgumentException(sprintf('Invalid %s name: only "word" characters are allowed.', $name));
}
if ($this->has($name) && null !== ($defaultValue = parent::get($name)) && !\is_string($defaultValue)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function testSimpleProcessor()
'string' => ['string'],
'trim' => ['string'],
'require' => ['bool', 'int', 'float', 'string', 'array'],
'enum' => [\BackedEnum::class],
];

$this->assertSame($expected, $container->getParameterBag()->getProvidedTypes());
Expand All @@ -65,7 +66,7 @@ public function testNoProcessor()
public function testBadProcessor()
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid type "foo" returned by "Symfony\Component\DependencyInjection\Tests\Compiler\BadProcessor::getProvidedTypes()", expected one of "array", "bool", "float", "int", "string".');
$this->expectExceptionMessage('Invalid type "foo" returned by "Symfony\Component\DependencyInjection\Tests\Compiler\BadProcessor::getProvidedTypes()", expected one of "array", "bool", "float", "int", "string", "BackedEnum".');
$container = new ContainerBuilder();
$container->register('foo', BadProcessor::class)->addTag('container.env_var_processor');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Tests\Fixtures\IntBackedEnum;
use Symfony\Component\DependencyInjection\Tests\Fixtures\StringBackedEnum;

class EnvVarProcessorTest extends TestCase
{
Expand Down Expand Up @@ -464,6 +466,78 @@ public function testGetEnvKeyChained()
}));
}

/**
* @dataProvider provideGetEnvEnum
*/
public function testGetEnvEnum(\BackedEnum $backedEnum)
{
$processor = new EnvVarProcessor(new Container());

$result = $processor->getEnv('enum', $backedEnum::class.':foo', function (string $name) use ($backedEnum) {
$this->assertSame('foo', $name);

return $backedEnum->value;
});

$this->assertSame($backedEnum, $result);
}

public function provideGetEnvEnum(): iterable
{
return [
[StringBackedEnum::Bar],
[IntBackedEnum::Nine],
];
}

public function testGetEnvEnumInvalidEnum()
{
$processor = new EnvVarProcessor(new Container());

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Invalid env "enum:foo": a "BackedEnum" class-string should be provided.');

$processor->getEnv('enum', 'foo', function () {
$this->fail('Should not get here');
});
}

public function testGetEnvEnumInvalidResolvedValue()
{
$processor = new EnvVarProcessor(new Container());

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Resolved value of "foo" did not result in a string or int value.');

$processor->getEnv('enum', StringBackedEnum::class.':foo', function () {
return null;
});
}

public function testGetEnvEnumInvalidArg()
{
$processor = new EnvVarProcessor(new Container());

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('"bogus" is not a "BackedEnum".');

$processor->getEnv('enum', 'bogus:foo', function () {
return '';
});
}

public function testGetEnvEnumInvalidBackedValue()
{
$processor = new EnvVarProcessor(new Container());

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Enum value "bogus" is not backed by "'.StringBackedEnum::class.'".');

$processor->getEnv('enum', StringBackedEnum::class.':foo', function () {
return 'bogus';
});
}

/**
* @dataProvider validNullables
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?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\DependencyInjection\Tests\Fixtures;

enum IntBackedEnum: int
{
case Nine = 9;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?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\DependencyInjection\Tests\Fixtures;

enum StringBackedEnum: string
{
case Bar = 'bar';
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Loader\Configurator\EnvConfigurator;
use Symfony\Component\DependencyInjection\Tests\Fixtures\StringBackedEnum;

final class EnvConfiguratorTest extends TestCase
{
Expand All @@ -24,13 +25,14 @@ public function test(string $expected, EnvConfigurator $envConfigurator)
$this->assertSame($expected, (string) $envConfigurator);
}

public function provide()
public function provide(): iterable
{
yield ['%env(FOO)%', new EnvConfigurator('FOO')];
yield ['%env(string:FOO)%', new EnvConfigurator('string:FOO')];
yield ['%env(string:FOO)%', (new EnvConfigurator('FOO'))->string()];
yield ['%env(key:path:url:FOO)%', (new EnvConfigurator('FOO'))->url()->key('path')];
yield ['%env(default:fallback:bar:arg1:FOO)%', (new EnvConfigurator('FOO'))->custom('bar', 'arg1')->default('fallback')];
yield ['%env(my_processor:my_argument:FOO)%', (new EnvConfigurator('FOO'))->myProcessor('my_argument')];
yield ['%env(enum:'.StringBackedEnum::class.':FOO)%', (new EnvConfigurator('FOO'))->enum(StringBackedEnum::class)];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Loader\Configurator\EnvConfigurator;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Tests\Fixtures\StringBackedEnum;

class EnvPlaceholderParameterBagTest extends TestCase
{
public function testEnumEnvVarProcessorPassesRegex()
{
$bag = new EnvPlaceholderParameterBag();
$name = \trim((new EnvConfigurator('FOO'))->enum(StringBackedEnum::class), '%');
$this->assertIsString($bag->get($name));
}

public function testGetThrowsInvalidArgumentExceptionIfEnvNameContainsNonWordCharacters()
{
$this->expectException(InvalidArgumentException::class);
$bag = new EnvPlaceholderParameterBag();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid env(%foo%) name: only "word" characters are allowed.');
$bag->get('env(%foo%)');
}

Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.