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

[FrameworkBundle] Generate Config class #58771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 7.3
Choose a base branch
Loading
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 1 src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ CHANGELOG
* Set `framework.rate_limiter.limiters.*.lock_factory` to `auto` by default
* Deprecate `RateLimiterFactory` autowiring aliases, use `RateLimiterFactoryInterface` instead
* Allow configuring compound rate limiters
* Add configuration class and config traits generation

7.2
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

use Psr\Log\LoggerInterface;
use Symfony\Component\Config\Builder\ConfigBuilderGenerator;
use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface;
use Symfony\Component\Config\Builder\ConfigClassAwareBuilderGeneratorInterface;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand All @@ -29,6 +29,7 @@
* Generate all config builders.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Alexandre Daubois <alex.daubois@gmail.com>
*
* @final since Symfony 7.1
*/
Expand Down Expand Up @@ -68,19 +69,31 @@ public function warmUp(string $cacheDir, ?string $buildDir = null): array
}
}

$configurations = [];
foreach ($extensions as $extension) {
if (null === $configuration = $this->getConfigurationFromExtension($extension)) {
continue;
}

$alias = lcfirst(str_replace('_', '', ucwords($extension->getAlias(), '_')));
alexandre-daubois marked this conversation as resolved.
Show resolved Hide resolved
$configurations[$alias] = $configuration;

try {
$this->dumpExtension($extension, $generator);
$generator->build($configurations[$alias]);
} catch (\Exception $e) {
$this->logger?->warning('Failed to generate ConfigBuilder for extension {extensionClass}: '.$e->getMessage(), ['exception' => $e, 'extensionClass' => $extension::class]);
}
}

if ($generator instanceof ConfigClassAwareBuilderGeneratorInterface && $configurations) {
$generator->buildConfigClassAndTraits($configurations);
}

// No need to preload anything
return [];
}

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

if (!$configuration) {
return;
}

$generator->build($configuration);
return $configuration;
}

public function isOptional(): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Component\Config\Builder\ConfigClassAwareBuilderGeneratorInterface;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
Expand Down Expand Up @@ -182,6 +183,11 @@ public function getCharset(): string
$warmer->warmUp($kernel->getCacheDir(), $kernel->getBuildDir());

self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php');

if (interface_exists(ConfigClassAwareBuilderGeneratorInterface::class)) {
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkTrait.php');
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Config.php');
}
}

public function testExtensionAddedInKernel()
Expand Down Expand Up @@ -222,6 +228,11 @@ public function getAlias(): string

self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php');
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/AppConfig.php');

if (interface_exists(ConfigClassAwareBuilderGeneratorInterface::class)) {
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkTrait.php');
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Config.php');
}
}

public function testKernelAsExtension()
Expand Down Expand Up @@ -267,6 +278,11 @@ public function getConfigTreeBuilder(): TreeBuilder

self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php');
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/KernelConfig.php');

if (interface_exists(ConfigClassAwareBuilderGeneratorInterface::class)) {
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkTrait.php');
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Config.php');
}
}

public function testExtensionsExtendedInBuildMethods()
Expand Down Expand Up @@ -333,6 +349,11 @@ public function addConfiguration(NodeDefinition $node): void
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig.php');
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig/FormLoginConfig.php');
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig/TokenConfig.php');

if (interface_exists(ConfigClassAwareBuilderGeneratorInterface::class)) {
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkTrait.php');
self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Config.php');
}
}
}

Expand Down
153 changes: 153 additions & 0 deletions 153 src/Symfony/Component/Config/Builder/ArrayShapeGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?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\Builder;

use Symfony\Component\Config\Definition\ArrayNode;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\BooleanNode;
use Symfony\Component\Config\Definition\EnumNode;
use Symfony\Component\Config\Definition\FloatNode;
use Symfony\Component\Config\Definition\IntegerNode;
use Symfony\Component\Config\Definition\NodeInterface;
use Symfony\Component\Config\Definition\NumericNode;
use Symfony\Component\Config\Definition\PrototypedArrayNode;
use Symfony\Component\Config\Definition\ScalarNode;
use Symfony\Component\Config\Definition\StringNode;
use Symfony\Component\Config\Definition\VariableNode;

/**
* @author Alexandre Daubois <alex.daubois@gmail.com>
*
* @internal
*/
final class ArrayShapeGenerator
{
public static function generate(ArrayNode $node): string
{
return self::prependPhpDocWithStar(self::doGeneratePhpDoc($node));
}

private static function doGeneratePhpDoc(NodeInterface $node, int $nestingLevel = 1): string
{
if (!$node instanceof ArrayNode) {
return $node->getName();
}

if ($node instanceof PrototypedArrayNode) {
$isHashmap = (bool) $node->getKeyAttribute();

$prototype = $node->getPrototype();
if ($prototype instanceof ArrayNode) {
return 'array<'.($isHashmap ? 'string, ' : '').self::doGeneratePhpDoc($prototype, $nestingLevel).'>';
}

return 'array<'.($isHashmap ? 'string, ' : '').self::handleScalarNode($prototype).'>';
}

if (!($children = $node->getChildren()) && !$node->getParent() instanceof PrototypedArrayNode) {
return 'array<array-key, mixed>';
}

$arrayShape = \sprintf("array{%s\n", self::generateInlinePhpDocForNode($node));

/** @var NodeInterface $child */
foreach ($children as $child) {
$arrayShape .= str_repeat(' ', $nestingLevel * 4).self::dumpNodeKey($child).': ';

if ($child instanceof PrototypedArrayNode) {
fabpot marked this conversation as resolved.
Show resolved Hide resolved
$isHashmap = (bool) $child->getKeyAttribute();

$arrayShape .= 'array<'.($isHashmap ? 'string, ' : '').self::handleNode($child->getPrototype(), $nestingLevel).'>';
} else {
$arrayShape .= self::handleNode($child, $nestingLevel);
}

$arrayShape .= \sprintf(",%s\n", !$child instanceof ArrayNode ? self::generateInlinePhpDocForNode($child) : '');
}

return $arrayShape.str_repeat(' ', 4 * ($nestingLevel - 1)).'}';
}

private static function dumpNodeKey(NodeInterface $node): string
{
$name = $node->getName();
$quoted = str_starts_with($name, '@')
|| \in_array(strtolower($name), ['int', 'float', 'bool', 'null', 'scalar'], true)
|| strpbrk($name, '\'"');

if ($quoted) {
$name = "'".addslashes($name)."'";
}

return $name.($node->isRequired() ? '' : '?');
}

private static function handleNumericNode(NumericNode $node): string
{
$min = $node->getMin() ?? 'min';
$max = $node->getMax() ?? 'max';

if ($node instanceof IntegerNode) {
return \sprintf('int<%s, %s>', $min, $max);
} elseif ($node instanceof FloatNode) {
return 'float';
}

return \sprintf('int<%s, %s>|float', $min, $max);
}

private static function prependPhpDocWithStar(string $shape): string
{
return str_replace("\n", "\n * ", $shape);
}

private static function generateInlinePhpDocForNode(BaseNode $node): string
{
$comment = '';
if ($node->hasDefaultValue() || $node->getInfo() || $node->isDeprecated()) {
if ($node->isDeprecated()) {
$comment .= 'Deprecated: '.$node->getDeprecation($node->getName(), $node->getPath())['message'].' ';
}

if ($info = $node->getInfo()) {
$comment .= $info.' ';
}

if ($node->hasDefaultValue() && !\is_array($defaultValue = $node->getDefaultValue())) {
$comment .= 'Default: '.json_encode($defaultValue, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRESERVE_ZERO_FRACTION);
}
}

return $comment ? ' // '.rtrim(preg_replace('/\s+/', ' ', $comment)) : '';
}

private static function handleNode(NodeInterface $node, int $nestingLevel): string
{
if ($node instanceof ArrayNode) {
return self::doGeneratePhpDoc($node, 1 + $nestingLevel);
}

return self::handleScalarNode($node);
}

private static function handleScalarNode(NodeInterface $node): string
{
return match (true) {
$node instanceof BooleanNode => 'bool',
$node instanceof StringNode => 'string',
$node instanceof NumericNode => self::handleNumericNode($node),
$node instanceof EnumNode => $node->getPermissibleValues('|'),
$node instanceof ScalarNode => 'string|int|float|bool',
$node instanceof VariableNode => 'mixed',
};
}
}
9 changes: 9 additions & 0 deletions 9 src/Symfony/Component/Config/Builder/ClassBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class ClassBuilder
private array $use = [];
private array $implements = [];
private bool $allowExtraKeys = false;
private array $traits = [];

public function __construct(
private string $namespace,
Expand Down Expand Up @@ -72,6 +73,9 @@ public function build(): string

$implements = [] === $this->implements ? '' : 'implements '.implode(', ', $this->implements);
$body = '';
foreach ($this->traits as $trait) {
$body .= ' use '.$trait.";\n";
}
foreach ($this->properties as $property) {
$body .= ' '.$property->getContent()."\n";
}
Expand Down Expand Up @@ -107,6 +111,11 @@ public function addUse(string $class): void
$this->use[$class] = true;
}

public function addTrait(string $trait): void
{
$this->traits[] = '\\'.ltrim($trait, '\\');
}

public function addImplements(string $interface): void
{
$this->implements[] = '\\'.ltrim($interface, '\\');
Expand Down
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.