8000 [FrameworkBundle] Add the `config()` function · symfony/symfony@1d3af0c · GitHub
[go: up one dir, main page]

Skip to content

Commit 1d3af0c

Browse files
[FrameworkBundle] Add the config() function
1 parent 3b5f623 commit 1d3af0c

25 files changed

+1402
-14
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines 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

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

0 commit comments

Comments
 (0)
0