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 214e266

Browse filesBrowse files
[FrameworkBundle] Generate configuration functions
1 parent b28e597 commit 214e266
Copy full SHA for 214e266

29 files changed

+1222
-17
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CHANGELOG
1313
* Add `framework.validation.disable_translation` option
1414
* Add support for signal plain name in the `messenger.stop_worker_on_signals` configuration
1515
* Deprecate the `framework.validation.cache` option
16+
* Add configuration functions generation
1617

1718
7.2
1819
---

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ConfigBuilderCacheWarmer.php
+17-8Lines 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

‎src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer;
1515
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
1616
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
17+
use Symfony\Component\Config\Builder\ConfigFunctionAwareBuilderGeneratorInterface;
1718
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
1819
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
1920
use Symfony\Component\Config\Definition\ConfigurationInterface;
@@ -31,6 +32,9 @@
3132
use Symfony\Component\HttpKernel\Kernel;
3233
use Symfony\Component\HttpKernel\KernelInterface;
3334

35+
/**
36+
* @runTestsInSeparateProcesses because the loaded-state of the "config()" function is global
37+
*/
3438
class ConfigBuilderCacheWarmerTest extends TestCase
3539
{
3640
private string $varDir;
@@ -182,6 +186,11 @@ public function getCharset(): string
182186
$warmer->warmUp($kernel->getCacheDir(), $kernel->getBuildDir());
183187

184188
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php');
189+
190+
if (interface_exists(ConfigFunctionAwareBuilderGeneratorInterface::class)) {
191+
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/config.php');
192+
self::assertTrue(\function_exists('\Symfony\Config\framework'), 'the framework() function should be generated and loaded');
193+
}
185194
}
186195

187196
public function testExtensionAddedInKernel()
@@ -222,6 +231,11 @@ public function getAlias(): string
222231

223232
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php');
224233
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/AppConfig.php');
234+
235+
if (interface_exists(ConfigFunctionAwareBuilderGeneratorInterface::class)) {
236+
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/config.php');
237+
self::assertTrue(\function_exists('\Symfony\Config\framework'), 'the framework() function should be generated and loaded');
238+
}
225239
}
226240

227241
public function testKernelAsExtension()
@@ -267,6 +281,11 @@ public function getConfigTreeBuilder(): TreeBuilder
267281

268282
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php');
269283
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/KernelConfig.php');
284+
285+
if (interface_exists(ConfigFunctionAwareBuilderGeneratorInterface::class)) {
286+
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/config.php');
287+
self::assertTrue(\function_exists('\Symfony\Config\framework'), 'the framework() function should be generated and loaded');
288+
}
270289
}
271290

272291
public function testExtensionsExtendedInBuildMethods()
@@ -333,6 +352,11 @@ public function addConfiguration(NodeDefinition $node): void
333352
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig.php');
334353
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig/FormLoginConfig.php');
335354
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig/TokenConfig.php');
355+
356+
if (interface_exists(ConfigFunctionAwareBuilderGeneratorInterface::class)) {
357+
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/config.php');
358+
self::assertTrue(\function_exists('\Symfony\Config\framework'), 'the framework() function should be generated and loaded');
359+
}
336360
}
337361
}
338362

+160Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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+
36+
public static function generate(ArrayNode $node): string
37+
{
38+
return self::prependPhpDocWithStar(self::doGeneratePhpDoc($node));
39+
}
40+
41+
private static function doGeneratePhpDoc(NodeInterface $node, int $nestingLevel = 1): string
42+
{
43+
if (!$node instanceof ArrayNode) {
44+
return $node->getName();
45+
}
46+
47+
if ($node instanceof PrototypedArrayNode) {
48+
$isHashmap = (bool) $node->getKeyAttribute();
49+
50+
$prototype = $node->getPrototype();
51+
if ($prototype instanceof ArrayNode) {
52+
return 'array<'.($isHashmap ? 'string, ' : '').self::doGeneratePhpDoc($prototype, $nestingLevel).'>';
53+
}
54+
55+
return 'array<'.($isHashmap ? 'string, ' : '').self::handleScalarNode($prototype).'>';
56+
}
57+
58+
if (!($children = $node->getChildren()) && !$node->getParent() instanceof PrototypedArrayNode) {
59+
return 'array<array-key, mixed>';
60+
}
61+
62+
$arrayShape = \sprintf("array{%s\n", self::generateInlinePhpDocForNode($node));
63+
64+
/** @var NodeInterface $child */
65+
foreach ($children as $child) {
66+
$arrayShape .= str_repeat(' ', $nestingLevel * 4).self::dumpNodeKey($child).': ';
67+
68+
if ($child instanceof PrototypedArrayNode) {
69+
$isHashmap = (bool) $child->getKeyAttribute();
70+
71+
$arrayShape .= 'array<'.($isHashmap ? 'string, ' : '').self::handleNode($child->getPrototype(), $nestingLevel, self::FORMAT_PHPDOC).'>';
72+
} else {
73+
$arrayShape .= self::handleNode($child, $nestingLevel, self::FORMAT_PHPDOC);
74+
}
75+
76+
$arrayShape .= \sprintf(",%s\n", !$child instanceof ArrayNode ? self::generateInlinePhpDocForNode($child) : '');
77+
}
78+
79+
return $arrayShape.str_repeat(' ', 4 * ($nestingLevel - 1)).'}';
80+
}
81+
82+
private static function dumpNodeKey(NodeInterface $node): string
83+
{
84+
$name = $node->getName();
85+
$quoted = str_starts_with($name, '@')
86+
|| \in_array(strtolower($name), ['int', 'float', 'bool', 'null', 'scalar'], true)
87+
|| strpbrk($name, '\'"');
88+
89+
if ($quoted) {
90+
$name = "'".addslashes($name)."'";
91+
}
92+
93+
return $name.($node->isRequired() ? '' : '?');
94+
}
95+
96+
private static function handleNumericNode(NumericNode $node): string
97+
{
98+
$min = $node->getMin() ?? 'min';
99+
$max = $node->getMax() ?? 'max';
100+
101+
if ($node instanceof IntegerNode) {
102+
return \sprintf('int<%s, %s>', $min, $max);
103+
} elseif ($node instanceof FloatNode) {
104+
return 'float';
105+
}
106+
107+
return \sprintf('int<%s, %s>|float', $min, $max);
108+
}
109+
110+
private static function prependPhpDocWithStar(string $shape): string
111+
{
112+
return str_replace("\n", "\n * ", $shape);
113+
}
114+
115+
private static function generateInlinePhpDocForNode(BaseNode $node): string
116+
{
117+
$hasContent = false;
118+
$comment = ' // ';
119+
120+
if ($node->hasDefaultValue() || $node->getInfo() || $node->isDeprecated()) {
121+
if ($node->isDeprecated()) {
122+
$hasContent = true;
123+
$comment .= 'Deprecated: '.$node->getDeprecation($node->getName(), $node->getPath())['message'].' ';
124+
}
125+
126+
if ($info = $node->getInfo()) {
127+
$hasContent = true;
128+
$comment .= $info.' ';
129+
}
130+
131+
if ($node->hasDefaultValue() && !\is_array($defaultValue = $node->getDefaultValue())) {
132+
$hasContent = true;
133+
$comment .= 'Default: '.json_encode($defaultValue, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRESERVE_ZERO_FRACTION);
134+
}
135+
}
136+
137+
return $hasContent ? rtrim($comment) : '';
138+
}
139+
140+
private static function handleNode(NodeInterface $node, int $nestingLevel, string $format): string
141+
{
142+
if ($node instanceof ArrayNode) {
143+
return self::doGeneratePhpDoc($node, 1 + $nestingLevel);
144+
}
145+
146+
return self::handleScalarNode($node);
147+
}
148+
149+
private static function handleScalarNode(NodeInterface $node): string
150+
{
151+
return match (true) {
152+
$node instanceof BooleanNode => 'bool',
153+
$node instanceof StringNode => 'string',
154+
$node instanceof NumericNode => self::handleNumericNode($node),
155+
$node instanceof EnumNode => $node->getPermissibleValues('|'),
156+
$node instanceof ScalarNode => 'string|int|float|bool',
157+
$node instanceof VariableNode => 'mixed',
158+
};
159+
}
160+
}

0 commit comments

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