[Config] Builder: Remove typehints and allow for EnvConfigurator
This commit is contained in:
parent
84a514c4b1
commit
59b79d35a7
@ -32,6 +32,7 @@ class ClassBuilder
|
||||
/** @var Method[] */
|
||||
private $methods = [];
|
||||
private $require = [];
|
||||
private $use = [];
|
||||
private $implements = [];
|
||||
|
||||
public function __construct(string $namespace, string $name)
|
||||
@ -66,6 +67,10 @@ class ClassBuilder
|
||||
}
|
||||
$require .= sprintf('require_once __DIR__.\DIRECTORY_SEPARATOR.\'%s\';', implode('\'.\DIRECTORY_SEPARATOR.\'', $path))."\n";
|
||||
}
|
||||
$use = '';
|
||||
foreach (array_keys($this->use) as $statement) {
|
||||
$use .= sprintf('use %s;', $statement)."\n";
|
||||
}
|
||||
|
||||
$implements = [] === $this->implements ? '' : 'implements '.implode(', ', $this->implements);
|
||||
$body = '';
|
||||
@ -84,6 +89,7 @@ class ClassBuilder
|
||||
namespace NAMESPACE;
|
||||
|
||||
REQUIRE
|
||||
USE
|
||||
|
||||
/**
|
||||
* This class is automatically generated to help creating config.
|
||||
@ -94,17 +100,22 @@ class CLASS IMPLEMENTS
|
||||
{
|
||||
BODY
|
||||
}
|
||||
', ['NAMESPACE' => $this->namespace, 'REQUIRE' => $require, 'CLASS' => $this->getName(), 'IMPLEMENTS' => $implements, 'BODY' => $body]);
|
||||
', ['NAMESPACE' => $this->namespace, 'REQUIRE' => $require, 'USE' => $use, 'CLASS' => $this->getName(), 'IMPLEMENTS' => $implements, 'BODY' => $body]);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function addRequire(self $class)
|
||||
public function addRequire(self $class): void
|
||||
{
|
||||
$this->require[] = $class;
|
||||
}
|
||||
|
||||
public function addImplements(string $interface)
|
||||
public function addUse(string $class): void
|
||||
{
|
||||
$this->use[$class] = true;
|
||||
}
|
||||
|
||||
public function addImplements(string $interface): void
|
||||
{
|
||||
$this->implements[] = '\\'.ltrim($interface, '\\');
|
||||
}
|
||||
@ -148,7 +159,7 @@ BODY
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function getFqcn()
|
||||
public function getFqcn(): string
|
||||
{
|
||||
return '\\'.$this->namespace.'\\'.$this->name;
|
||||
}
|
||||
|
@ -15,12 +15,14 @@ use Symfony\Component\Config\Definition\ArrayNode;
|
||||
use Symfony\Component\Config\Definition\BooleanNode;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
use Symfony\Component\Config\Definition\EnumNode;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\Config\Definition\FloatNode;
|
||||
use Symfony\Component\Config\Definition\IntegerNode;
|
||||
use Symfony\Component\Config\Definition\NodeInterface;
|
||||
use Symfony\Component\Config\Definition\PrototypedArrayNode;
|
||||
use Symfony\Component\Config\Definition\ScalarNode;
|
||||
use Symfony\Component\Config\Definition\VariableNode;
|
||||
use Symfony\Component\Config\Loader\ParamConfigurator;
|
||||
|
||||
/**
|
||||
* Generate ConfigBuilders to help create valid config.
|
||||
@ -83,7 +85,7 @@ public function NAME(): string
|
||||
return $directory.\DIRECTORY_SEPARATOR.$class->getFilename();
|
||||
}
|
||||
|
||||
private function writeClasses()
|
||||
private function writeClasses(): void
|
||||
{
|
||||
foreach ($this->classes as $class) {
|
||||
$this->buildConstructor($class);
|
||||
@ -95,7 +97,7 @@ public function NAME(): string
|
||||
$this->classes = [];
|
||||
}
|
||||
|
||||
private function buildNode(NodeInterface $node, ClassBuilder $class, string $namespace)
|
||||
private function buildNode(NodeInterface $node, ClassBuilder $class, string $namespace): void
|
||||
{
|
||||
if (!$node instanceof ArrayNode) {
|
||||
throw new \LogicException('The node was expected to be an ArrayNode. This Configuration includes an edge case not supported yet.');
|
||||
@ -121,7 +123,7 @@ public function NAME(): string
|
||||
}
|
||||
}
|
||||
|
||||
private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $namespace)
|
||||
private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $namespace): void
|
||||
{
|
||||
$childClass = new ClassBuilder($namespace, $node->getName());
|
||||
$class->addRequire($childClass);
|
||||
@ -134,20 +136,22 @@ public function NAME(array $value = []): CLASS
|
||||
if (null === $this->PROPERTY) {
|
||||
$this->PROPERTY = new CLASS($value);
|
||||
} elseif ([] !== $value) {
|
||||
throw new \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException(sprintf(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'));
|
||||
throw new InvalidConfigurationException(sprintf(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'));
|
||||
}
|
||||
|
||||
return $this->PROPERTY;
|
||||
}';
|
||||
$class->addUse(InvalidConfigurationException::class);
|
||||
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn()]);
|
||||
|
||||
$this->buildNode($node, $childClass, $this->getSubNamespace($childClass));
|
||||
}
|
||||
|
||||
private function handleVariableNode(VariableNode $node, ClassBuilder $class)
|
||||
private function handleVariableNode(VariableNode $node, ClassBuilder $class): void
|
||||
{
|
||||
$comment = $this->getComment($node);
|
||||
$property = $class->addProperty($node->getName());
|
||||
$class->addUse(ParamConfigurator::class);
|
||||
|
||||
$body = '
|
||||
/**
|
||||
@ -162,7 +166,7 @@ public function NAME($valueDEFAULT): self
|
||||
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment, 'DEFAULT' => $node->hasDefaultValue() ? ' = '.var_export($node->getDefaultValue(), true) : '']);
|
||||
}
|
||||
|
||||
private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuilder $class, string $namespace)
|
||||
private function handlePrototypedArrayNode(PrototypedArrayNode $node, ClassBuilder $class, string $namespace): void
|
||||
{
|
||||
$name = $this->getSingularName($node);
|
||||
$prototype = $node->getPrototype();
|
||||
@ -170,15 +174,16 @@ public function NAME($valueDEFAULT): self
|
||||
|
||||
$parameterType = $this->getParameterType($prototype);
|
||||
if (null !== $parameterType || $prototype instanceof ScalarNode) {
|
||||
$class->addUse(ParamConfigurator::class);
|
||||
$property = $class->addProperty($node->getName());
|
||||
if (null === $key = $node->getKeyAttribute()) {
|
||||
// This is an array of values; don't use singular name
|
||||
$body = '
|
||||
/**
|
||||
* @param list<TYPE> $value
|
||||
* @param ParamConfigurator|list<TYPE|ParamConfigurator> $value
|
||||
* @return $this
|
||||
*/
|
||||
public function NAME(array $value): self
|
||||
public function NAME($value): self
|
||||
{
|
||||
$this->PROPERTY = $value;
|
||||
|
||||
@ -189,16 +194,17 @@ public function NAME(array $value): self
|
||||
} else {
|
||||
$body = '
|
||||
/**
|
||||
* @param ParamConfigurator|TYPE $value
|
||||
* @return $this
|
||||
*/
|
||||
public function NAME(string $VAR, TYPE$VALUE): self
|
||||
public function NAME(string $VAR, $VALUE): self
|
||||
{
|
||||
$this->PROPERTY[$VAR] = $VALUE;
|
||||
|
||||
return $this;
|
||||
}';
|
||||
|
||||
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? '' : $parameterType.' ', 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
|
||||
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? 'mixed' : $parameterType, 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
|
||||
}
|
||||
|
||||
return;
|
||||
@ -227,31 +233,33 @@ public function NAME(string $VAR, array $VALUE = []): CLASS
|
||||
return $this->PROPERTY[$VAR];
|
||||
}
|
||||
|
||||
throw new \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException(sprintf(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'));
|
||||
throw new InvalidConfigurationException(sprintf(\'The node created by "NAME()" has already been initialized. You cannot pass values the second time you call NAME().\'));
|
||||
}';
|
||||
$class->addUse(InvalidConfigurationException::class);
|
||||
$class->addMethod($methodName, $body, ['PROPERTY' => $property->getName(), 'CLASS' => $childClass->getFqcn(), 'VAR' => '' === $key ? 'key' : $key, 'VALUE' => 'value' === $key ? 'data' : 'value']);
|
||||
}
|
||||
|
||||
$this->buildNode($prototype, $childClass, $namespace.'\\'.$childClass->getName());
|
||||
}
|
||||
|
||||
private function handleScalarNode(ScalarNode $node, ClassBuilder $class)
|
||||
private function handleScalarNode(ScalarNode $node, ClassBuilder $class): void
|
||||
{
|
||||
$comment = $this->getComment($node);
|
||||
$property = $class->addProperty($node->getName());
|
||||
$class->addUse(ParamConfigurator::class);
|
||||
|
||||
$body = '
|
||||
/**
|
||||
COMMENT * @return $this
|
||||
*/
|
||||
public function NAME(TYPE$value): self
|
||||
public function NAME($value): self
|
||||
{
|
||||
$this->PROPERTY = $value;
|
||||
|
||||
return $this;
|
||||
}';
|
||||
$parameterType = $this->getParameterType($node) ?? '';
|
||||
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'TYPE' => '' === $parameterType ? '' : $parameterType.' ', 'COMMENT' => $comment]);
|
||||
|
||||
$class->addMethod($node->getName(), $body, ['PROPERTY' => $property->getName(), 'COMMENT' => $comment]);
|
||||
}
|
||||
|
||||
private function getParameterType(NodeInterface $node): ?string
|
||||
@ -301,9 +309,15 @@ public function NAME(TYPE$value): self
|
||||
}
|
||||
|
||||
if ($node instanceof EnumNode) {
|
||||
$comment .= sprintf(' * @param %s $value', implode('|', array_map(function ($a) {
|
||||
$comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_map(function ($a) {
|
||||
return var_export($a, true);
|
||||
}, $node->getValues()))).\PHP_EOL;
|
||||
} else {
|
||||
$parameterType = $this->getParameterType($node);
|
||||
if (null === $parameterType || '' === $parameterType) {
|
||||
$parameterType = 'mixed';
|
||||
}
|
||||
$comment .= ' * @param ParamConfigurator|'.$parameterType.' $value'.\PHP_EOL;
|
||||
}
|
||||
|
||||
if ($node->isDeprecated()) {
|
||||
@ -387,9 +401,10 @@ public function NAME(): array
|
||||
|
||||
$body .= '
|
||||
if ($value !== []) {
|
||||
throw new \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__) . implode(\', \', array_keys($value)));
|
||||
throw new InvalidConfigurationException(sprintf(\'The following keys are not supported by "%s": \', __CLASS__) . implode(\', \', array_keys($value)));
|
||||
}';
|
||||
|
||||
$class->addUse(InvalidConfigurationException::class);
|
||||
$class->addMethod('__construct', '
|
||||
public function __construct(array $value = [])
|
||||
{
|
||||
|
32
src/Symfony/Component/Config/Loader/ParamConfigurator.php
Normal file
32
src/Symfony/Component/Config/Loader/ParamConfigurator.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?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\Config\Loader;
|
||||
|
||||
/**
|
||||
* Placeholder for a parameter.
|
||||
*
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
class ParamConfigurator
|
||||
{
|
||||
private $name;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '%'.$this->name.'%';
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
use Symfony\Config\PlaceholdersConfig;
|
||||
|
||||
return static function (PlaceholdersConfig $config) {
|
||||
$config->enabled(env('FOO_ENABLED')->bool());
|
||||
$config->favoriteFloat(param('eulers_number'));
|
||||
$config->goodIntegers(env('MY_INTEGERS')->json());
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'enabled' => '%env(bool:FOO_ENABLED)%',
|
||||
'favorite_float' => '%eulers_number%',
|
||||
'good_integers' => '%env(json:MY_INTEGERS)%',
|
||||
];
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Config\Tests\Builder\Fixtures;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
class Placeholders implements ConfigurationInterface
|
||||
{
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$tb = new TreeBuilder('placeholders');
|
||||
$rootNode = $tb->getRootNode();
|
||||
$rootNode
|
||||
->children()
|
||||
->booleanNode('enabled')->defaultFalse()->end()
|
||||
->floatNode('favorite_float')->end()
|
||||
->arrayNode('good_integers')
|
||||
->integerPrototype()->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
return $tb;
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ use Symfony\Component\Config\Builder\ConfigBuilderInterface;
|
||||
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
|
||||
use Symfony\Component\Config\Tests\Builder\Fixtures\AddToList;
|
||||
use Symfony\Component\Config\Tests\Builder\Fixtures\NodeInitialValues;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\AbstractConfigurator;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
use Symfony\Config\AddToListConfig;
|
||||
|
||||
/**
|
||||
@ -30,6 +32,14 @@ class GeneratedConfigTest extends TestCase
|
||||
foreach ($array as $name => $alias) {
|
||||
yield $name => [$name, $alias];
|
||||
}
|
||||
|
||||
/*
|
||||
* Force load ContainerConfigurator to make env(), param() etc available
|
||||
* and also check if symfony/dependency-injection is installed
|
||||
*/
|
||||
if (class_exists(ContainerConfigurator::class)) {
|
||||
yield 'Placeholders' => ['Placeholders', 'placeholders'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,7 +55,11 @@ class GeneratedConfigTest extends TestCase
|
||||
|
||||
$this->assertInstanceOf(ConfigBuilderInterface::class, $configBuilder);
|
||||
$this->assertSame($alias, $configBuilder->getExtensionAlias());
|
||||
$this->assertSame($expectedOutput, $configBuilder->toArray());
|
||||
$output = $configBuilder->toArray();
|
||||
if (class_exists(AbstractConfigurator::class)) {
|
||||
$output = AbstractConfigurator::processValue($output);
|
||||
}
|
||||
$this->assertSame($expectedOutput, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\Config\Loader\ParamConfigurator;
|
||||
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
|
||||
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
@ -83,7 +84,7 @@ abstract class AbstractConfigurator
|
||||
return $def;
|
||||
}
|
||||
|
||||
if ($value instanceof EnvConfigurator) {
|
||||
if ($value instanceof ParamConfigurator) {
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
use Symfony\Component\Config\Loader\ParamConfigurator;
|
||||
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||
@ -96,9 +97,9 @@ class ContainerConfigurator extends AbstractConfigurator
|
||||
/**
|
||||
* Creates a parameter.
|
||||
*/
|
||||
function param(string $name): string
|
||||
function param(string $name): ParamConfigurator
|
||||
{
|
||||
return '%'.$name.'%';
|
||||
return new ParamConfigurator($name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,9 @@
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||
|
||||
class EnvConfigurator
|
||||
use Symfony\Component\Config\Loader\ParamConfigurator;
|
||||
|
||||
class EnvConfigurator extends ParamConfigurator
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
@ -23,10 +25,7 @@ class EnvConfigurator
|
||||
$this->stack = explode(':', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return '%env('.implode(':', $this->stack).')%';
|
||||
}
|
||||
|
@ -128,6 +128,9 @@ class PhpFileLoader extends FileLoader
|
||||
}
|
||||
}
|
||||
|
||||
// Force load ContainerConfigurator to make env(), param() etc available.
|
||||
class_exists(ContainerConfigurator::class);
|
||||
|
||||
$callback(...$arguments);
|
||||
|
||||
/** @var ConfigBuilderInterface $configBuilder */
|
||||
|
Reference in New Issue
Block a user