10000 [FrameworkBundle] Add the `config()` function · symfony/symfony@4a674ab · GitHub
[go: up one dir, main page]

Skip to content

Commit 4a674ab

Browse files
[FrameworkBundle] Add the config() function
1 parent c98cfb6 commit 4a674ab

25 files changed

+1403
-14
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add JsonEncoder services and configuration
1010
* Add new `framework.property_info.with_constructor_extractor` option to allow enabling or disabling the constructor extractor integration
1111
* Deprecate the `--show-arguments` option of the `container:debug` command, as arguments are now always shown
12+
* Add the `Symfony\Config\config()` function
1213

1314
7.2
1415
---

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

Lines changed: 17 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,31 @@ 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+
$alias = lcfirst(str_replace('_', '', ucwords($extension->getAlias(), '_')));
79+
$configurations[$alias] = $configuration;
80+
7281
try {
73-
$this->dumpExtension($extension, $generator);
82+
$generator->build($configurations[$alias]);
7483
} catch (\Exception $e) {
7584
$this->logger?->warning('Failed to generate ConfigBuilder for extension {extensionClass}: '.$e->getMessage(), ['exception' => $e, 'extensionClass' => $extension::class]);
7685
}
7786
}
7887

88+
if ($generator instanceof ConfigFunctionAwareBuilderGeneratorInterface && $configurations) {
89+
$generator->buildConfigFunction($configurations)();
90+
}
91+
7992
// No need to preload anything
8093
return [];
8194
}
8295

83-
private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGeneratorInterface $generator): void
96+
private function getConfigurationFromExtension(ExtensionInterface $extension): ?ConfigurationInterface
8497
{
8598
$configuration = null;
8699
if ($extension instanceof ConfigurationInterface) {
@@ -90,11 +103,7 @@ private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGener
90103
$configuration = $extension->getConfiguration([], new ContainerBuilder($container instanceof Container ? new ContainerBag($container) : new ParameterBag()));
91104
}
92105

93-
if (!$configuration) {
94-
return;
95-
}
96-
97-
$generator->build($configuration);
106+
return $configuration;
98107
}
99108

100109
public function isOptional(): bool
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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\ScalarNode;
24+
use Symfony\Component\Config\Definition\StringNode;
25+
use Symfony\Component\Config\Definition\VariableNode;
26+
27+
/**
28+
* @author Alexandre Daubois <alex.daubois@gmail.com>
29+
*
30+
* @internal
31+
*/
32+
final class ArrayShapeGenerator
33+
{
34+
public const FORMAT_PHPDOC = 'phpdoc';
35+
public const FORMAT_JETBRAINS_ATTRIBUTE = 'jetbrains_attribute';
36+
37+
/**
38+
* @param self::FORMAT_* $format
39+
*/
40+
public static function generate(ArrayNode $node, string $format): string
41+
{
42+
if (self::FORMAT_PHPDOC === $format) {
43+
return self::prependPhpDocWithStar(self::doGeneratePhpDoc($node));
44+
}
45+
46+
if (self::FORMAT_JETBRAINS_ATTRIBUTE === $format) {
47+
return self::doGenerateJetBrainsArrayShape($node);
48+
}
49+
50+
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));
51+
}
52+
53+
private static function doGeneratePhpDoc(NodeInterface $node, int $nestingLevel = 1): string
54+
{
55+
if (!$node instanceof ArrayNode) {
56+
return $node->getName();
57+
}
58+
59+
if (!$children = $node->getChildren()) {
60+
return 'array<array-key, mixed>';
61+
}
62+
63+
if ($node instanceof PrototypedArrayNode) {
64+
$prototype = $node->getPrototype();
65+
if ($prototype instanceof ArrayNode) {
66+
return 'array<'.self::doGeneratePhpDoc($prototype, 1 + $nestingLevel).'>';
67+
}
68+
69+
return 'array<'.self::handleNodeType($prototype).'>';
70+
}
71+
72+
$arrayShape = "array{\n";
73+
74+
$handleNode = function (NodeInterface $node, int $nestingLevel): string {
75+
if ($node instanceof ArrayNode) {
76+
return self::doGeneratePhpDoc($node, 1 + $nestingLevel);
77+
} else {
78+
return self::handleNodeType($node);
79+
}
80+
};
81+
82+
/** @var NodeInterface $child */
83+
foreach ($children as $child) {
84+
$arrayShape .= str_repeat(' ', $nestingLevel * 4).self::dumpNodeKey($child).': ';
85+
86+
if ($child instanceof PrototypedArrayNode) {
87+
$arrayShape .= 'array<'.$handleNode($child->getPrototype(), $nestingLevel).'>';
88+
} else {
89+
$arrayShape .= $handleNode($child, $nestingLevel);
90+
}
91+
92+
$arrayShape .= ",\n";
93+
}
94+
95+
return $arrayShape.str_repeat(' ', 4 * ($nestingLevel - 1)).'}';
96+
}
97+
98+
private static function doGenerateJetBrainsArrayShape(NodeInterface $node, int $nestingLevel = 1): string
99+
{
100+
if (!$node instanceof ArrayNode) {
101+
return $node->getName();
102+
}
103+
104+
$children = $node->getChildren();
105+
106+
$shape = '';
107+
if (1 === $nestingLevel) {
108+
$shape = '#[ArrayShape(';
109+
} elseif (!$children) {
110+
return "'array<array-key, mixed>'";
111+
}
112+
113+
if ($node instanceof PrototypedArrayNode) {
114+
$prototype = $node->getPrototype();
115+
if ($prototype instanceof ArrayNode) {
116+
return 'array<'.self::doGeneratePhpDoc($prototype, 1 + $nestingLevel).'>';
117+
}
118+
119+
return 'array<'.self::handleNodeType($prototype).'>';
120+
}
121+
122+
$inlineDoc = self::generateInlinePhpDocForNode($node);
123+
$shape .= sprintf("[%s\n", $inlineDoc);
124+
125+
$handleNode = function (NodeInterface $node, int $nestingLevel): string {
126+
if ($node instanceof ArrayNode) {
127+
return self::doGenerateJetBrainsArrayShape($node, 1 + $nestingLevel);
128+
}
129+
130+
return "'".self::handleNodeType($node)."'";
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 .= ','.(str_ends_with($shape, ' ]') ? '' : self::generateInlinePhpDocForNode($child))."\n";
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+
10000 }
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, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRESERVE_ZERO_FRACTION).' ';
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+
};
214+
}
215+
}

0 commit comments

Comments
 (0)
0