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

Commit 1f9c0b0

Browse filesBrowse files
[FrameworkBundle] Add the config() function
1 parent 3b5f623 commit 1f9c0b0
Copy full SHA for 1f9c0b0

25 files changed

+1247
-14
lines changed

‎src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.3
5+
---
6+
7+
* Add the `Symfony\Config\config()` function
8+
49
7.2
510
---
611

‎src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php
+16-8Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\Config\Builder\ConfigBuilderGenerator;
16-
use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface;
16+
use Symfony\Component\Config\Builder\ConfigFunctionAwareBuilderGeneratorInterface;
1717
use Symfony\Component\Config\Definition\ConfigurationInterface;
1818
use Symfony\Component\DependencyInjection\Container;
1919
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -29,6 +29,7 @@
2929
* Generate all config builders.
3030
*
3131
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
32+
* @author Alexandre Daubois <alex.daubois@gmail.com>
3233
*
3334
* @final since Symfony 7.1
3435
*/
@@ -68,19 +69,30 @@ public function warmUp(string $cacheDir, ?string $buildDir = null): array
6869
}
6970
}
7071

72+
$configurations = [];
7173
foreach ($extensions as $extension) {
74+
if (null === $configuration = $this->getConfigurationFromExtension($extension)) {
75+
continue;
76+
}
77+
78+
$configurations[$extension->getAlias()] = $configuration;
79+
7280
try {
73-
$this->dumpExtension($extension, $generator);
81+
$generator->build($configurations[$extension->getAlias()]);
7482
} catch (\Exception $e) {
7583
$this->logger?->warning('Failed to generate ConfigBuilder for extension {extensionClass}: '.$e->getMessage(), ['exception' => $e, 'extensionClass' => $extension::class]);
7684
}
7785
}
7886

87+
if (class_exists(ConfigFunctionAwareBuilderGeneratorInterface::class) && $configurations) {
88+
$generator->buildConfigFunction($configurations);
89+
}
90+
7991
// No need to preload anything
8092
return [];
8193
}
8294

83-
private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGeneratorInterface $generator): void
95+
private function getConfigurationFromExtension(ExtensionInterface $extension): ?ConfigurationInterface
8496
{
8597
$configuration = null;
8698
if ($extension instanceof ConfigurationInterface) {
@@ -90,11 +102,7 @@ private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGener
90102
$configuration = $extension->getConfiguration([], new ContainerBuilder($container instanceof Container ? new ContainerBag($container) : new ParameterBag()));
91103
}
92104

93-
if (!$configuration) {
94-
return;
95-
}
96-
97-
$generator->build($configuration);
105+
return $configuration;
98106
}
99107

100108
public function isOptional(): bool
+180Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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\Config\Builder;
13+
14+
use Symfony\Component\Config\Definition\ArrayNode;
15+
use Symfony\Component\Config\Definition\BaseNode;
16+
use Symfony\Component\Config\Definition\BooleanNode;
17+
use Symfony\Component\Config\Definition\EnumNode;
18+
use Symfony\Component\Config\Definition\FloatNode;
19+
use Symfony\Component\Config\Definition\IntegerNode;
20+
use Symfony\Component\Config\Definition\NodeInterface;
21+
use Symfony\Component\Config\Definition\NumericNode;
22+
use Symfony\Component\Config\Definition\ScalarNode;
23+
use Symfony\Component\Config\Definition\StringNode;
24+
use Symfony\Component\Config\Definition\VariableNode;
25+
26+
/**
27+
* @author Alexandre Daubois <alex.daubois@gmail.com>
28+
*
29+
* @internal
30+
*/
31+
final class ArrayShapeGenerator
32+
{
33+
public const FORMAT_PHPDOC = 'phpdoc';
34+
public const FORMAT_JETBRAINS_ATTRIBUTE = 'jetbrains_attribute';
35+
36+
/**
37+
* @param self::FORMAT_* $format
38+
*/
39+
public static function generate(ArrayNode $node, string $format): string
40+
{
41+
if (self::FORMAT_PHPDOC === $format) {
42+
return static::prependPhpDocWithStar(static::doGeneratePhpDoc($node));
43+
}
44+
45+
if (self::FORMAT_JETBRAINS_ATTRIBUTE === $format) {
46+
return static::doGenerateJetBrainsArrayShape($node);
47+
}
48+
49+
throw new \LogicException(\sprintf('Unsupported format to generate array shape. Expected one of "%s", got "%s".', implode('", "', [self::FORMAT_PHPDOC, self::FORMAT_JETBRAINS_ATTRIBUTE]), $format));
50+
}
51+
52+
private static function doGeneratePhpDoc(NodeInterface $node, int $nestingLevel = 1): string
53+
{
54+
if (!$node instanceof ArrayNode) {
55+
return $node->getName();
56+
}
57+
58+
$arrayShape = 'array';
59+
60+
if (!$children = $node->getChildren()) {
61+
return $arrayShape.'<array-key, mixed>';
62+
}
63+
64+
$arrayShape .= '{'.\PHP_EOL;
65+
66+
/** @var NodeInterface $child */
67+
foreach ($children as $child) {
68+
$arrayShape .= str_repeat(' ', $nestingLevel * 4).static::dumpNodeKey($child).': ';
69+
70+
if ($child instanceof ArrayNode) {
71+
$arrayShape .= static::doGeneratePhpDoc($child, 1 + $nestingLevel);
72+
} else {
73+
$arrayShape .= static::handleNodeType($child);
74+
}
75+
76+
$arrayShape .= ','.\PHP_EOL;
77+
}
78+
79+
return $arrayShape.str_repeat(' ', 4 * ($nestingLevel - 1)).'}';
80+
}
81+
82+
private static function doGenerateJetBrainsArrayShape(NodeInterface $node, int $nestingLevel = 1): string
83+
{
84+
if (!$node instanceof ArrayNode) {
85+
return $node->getName();
86+
}
87+
88+
$children = $node->getChildren();
89+
90+
$shape = '';
91+
if (1 === $nestingLevel) {
92+
$shape = '#[ArrayShape(';
93+
} elseif (!$children) {
94+
return "'array<array-key, mixed>'";
95+
}
96+
97+
$shape .= '['.\PHP_EOL;
98+
99+
/** @var BaseNode $child */
100+
foreach ($children as $child) {
101+
$shape .= \sprintf("%s'%s' => ", str_repeat(' ', 4 * $nestingLevel), $child->getName());
102+
if ($child instanceof ArrayNode) {
103+
$shape .= static::doGenerateJetBrainsArrayShape($child, 1 + $nestingLevel);
104+
} else {
105+
$shape .= "'".static::handleNodeType($child)."'";
106+
}
107+
108+
$shape .= ','.static::generateInlinePhpDocForNode($child).\PHP_EOL;
109+
}
110+
111+
$shape .= str_repeat(' ', 4 * ($nestingLevel - 1)).']';
112+
113+
return $shape.(1 === $nestingLevel ? ')]' : '');
114+
}
115+
116+
private static function dumpNodeKey(NodeInterface $node): string
117+
{
118+
return $node->getName().($node->isRequired() ? '' : '?');
119+
}
120+
121+
private static function handleNumericNode(NumericNode $node): string
122+
{
123+
if ($node instanceof IntegerNode) {
124+
$type = 'int<%s, %s>';
125+
} elseif ($node instanceof FloatNode) {
126+
$type = 'float<%s, %s>';
127+
} else {
128+
$type = 'int<%s, %s>|float<%1$s, %2$s>';
129+
}
130+
131+
$min = $node->getMin() ?? 'min';
132+
$max = $node->getMax() ?? 'max';
133+
134+
return \sprintf($type, $min, $max);
135+
}
136+
137+
private static function prependPhpDocWithStar(string $shape): string
138+
{
139+
return str_replace("\n", "\n * ", $shape);
140+
}
141+
142+
private static function generateInlinePhpDocForNode(BaseNode $node): string
143+
{
144+
$hasContent = false;
145+
$comment = ' /* ';
146+
147+
if ($node->hasDefaultValue() || $node->getInfo() || $node->isDeprecated()) {
148+
if ($node->isDeprecated()) {
149+
$hasContent = true;
150+
$comment .= 'Deprecated: '.$node->getDeprecation($node->getName(), $node->getPath())['message'].' ';
151+
}
152+
153+
if ($info = $node->getInfo()) {
154+
$hasContent = true;
155+
$comment .= $info.' ';
156+
}
157+
158+
if ($node->hasDefaultValue() && !\is_array($defaultValue = $node->getDefaultValue())) {
159+
$hasContent = true;
160+
$comment .= 'Default: '.json_encode($defaultValue).'. ';
161+
}
162+
163+
$comment .= '*/';
164+
}
165+
166+
return $hasContent ? $comment : '';
167+
}
168+
169+
private static function handleNodeType(NodeInterface $node): string
170+
{
171+
return match (true) {
172+
$node instanceof BooleanNode => 'bool',
173+
$node instanceof StringNode => 'string',
174+
$node instanceof NumericNode => static::handleNumericNode($node),
175+
$node instanceof EnumNode => $node->getPermissibleValues('|'),
176+
$node instanceof ScalarNode => 'string|int|float|bool',
177+
$node instanceof VariableNode => 'mixed',
178+
};
179+
}
180+
}

‎src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Config/Builder/ConfigBuilderGenerator.php
+64-5Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*
3232
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
3333
*/
34-
class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface
34+
class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface, ConfigFunctionAwareBuilderGeneratorInterface
3535
{
3636
/**
3737
* @var ClassBuilder[]
@@ -55,16 +55,17 @@ public function build(ConfigurationInterface $configuration): \Closure
5555

5656
$path = $this->getFullPath($rootClass);
5757
if (!is_file($path)) {
58-
// Generate the class if the file not exists
59-
$this->classes[] = $rootClass;
58+
// Generate the class if the file doesn't exist
6059
$this->buildNode($rootNode, $rootClass, $this->getSubNamespace($rootClass));
60+
$this->buildConfigureOption($rootNode, $rootClass);
6161
$rootClass->addImplements(ConfigBuilderInterface::class);
6262
$rootClass->addMethod('getExtensionAlias', '
6363
public function NAME(): string
6464
{
6565
return \'ALIAS\';
6666
}', ['ALIAS' => $rootNode->getPath()]);
6767

68+
$this->writeRootClass($rootClass);
6869
$this->writeClasses();
6970
}
7071

@@ -76,6 +77,11 @@ public function NAME(): string
7677
};
7778
}
7879

80+
public function buildConfigFunction(array $configurations): \Closure
81+
{
82+
return (new ConfigFunctionGenerator($this->outputDir))->build($configurations);
83+
}
84+
7985
private function getFullPath(ClassBuilder $class): string
8086
{
8187
$directory = $this->outputDir.\DIRECTORY_SEPARATOR.$class->getDirectory();
@@ -86,6 +92,18 @@ private function getFullPath(ClassBuilder $class): string
8692
return $directory.\DIRECTORY_SEPARATOR.$class->getFilename();
8793
}
8894

95+
private function writeRootClass(ClassBuilder $rootClass): void
96+
{
97+
$this->buildConstructor($rootClass);
98+
$this->buildToArray($rootClass, true);
99+
if ($rootClass->getProperties()) {
100+
$rootClass->addProperty('_usedProperties', null, '[]');
101+
}
102+
$this->buildSetExtraKey($rootClass);
103+
104+
file_put_contents($this->getFullPath($rootClass), $rootClass->build());
105+
}
106+
89107
private function writeClasses(): void
90108
{
91109
foreach ($this->classes as $class) {
@@ -102,6 +120,29 @@ private function writeClasses(): void
102120
$this->classes = [];
103121
}
104122

123+
private function buildConfigureOption(ArrayNode $node, ClassBuilder $class): void
124+
{
125+
$class->addUse('JetBrains\PhpStorm\ArrayShape');
126+
$class->addProperty('configOutput', defaultValue: '[]');
127+
128+
$body = '
129+
/*
130+
* @param PHPDOC_ARRAY_SHAPE $config
131+
*/
132+
public function NAME(
133+
// config:
134+
JETBRAINS_ATTRIBUTE PARAM_TYPE $config = []): void
135+
{
136+
$this->configOutput = $config;
137+
}';
138+
139+
$class->addMethod('configure', $body, [
140+
'PARAM_TYPE' => 'array',
141+
'PHPDOC_ARRAY_SHAPE' => ArrayShapeGenerator::generate($node, ArrayShapeGenerator::FORMAT_PHPDOC),
142+
'JETBRAINS_ATTRIBUTE' => str_replace("\n", "\n ", ArrayShapeGenerator::generate($node, ArrayShapeGenerator::FORMAT_JETBRAINS_ATTRIBUTE)),
143+
]);
144+
}
145+
105146
private function buildNode(NodeInterface $node, ClassBuilder $class, string $namespace): void
106147
{
107148
if (!$node instanceof ArrayNode) {
@@ -469,10 +510,24 @@ private function getSingularName(PrototypedArrayNode $node): string
469510
return $name;
470511
}
471512

472-
private function buildToArray(ClassBuilder $class): void
513+
private function buildToArray(ClassBuilder $class, bool $rootClass = false): void
473514
{
474-
$body = '$output = [];';
515+
$body = '';
516+
if ($rootClass) {
517+
$body = 'if ($this->configOutput) {
518+
return $this->configOutput;
519+
}
520+
521+
';
522+
}
523+
524+
$body .= '$output = [];';
525+
475526
foreach ($class->getProperties() as $p) {
527+
if ('configOutput' === $p->getName()) {
528+
continue;
529+
}
530+
476531
$code = '$this->PROPERTY';
477532
if (null !== $p->getType()) {
478533
if ($p->isArray()) {
@@ -509,6 +564,10 @@ private function buildConstructor(ClassBuilder $class): void
509564
{
510565
$body = '';
511566
foreach ($class->getProperties() as $p) {
567+
if ('configOutput' === $p->getName()) {
568+
continue;
569+
}
570+
512571
$code = '$value[\'ORG_NAME\']';
513572
if (null !== $p->getType()) {
514573
if ($p->isArray()) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.