/
githubmirror
/
symfony
Обзор
Документация
Войти
/
githubmirror
/
symfony
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
8.2
src/Symfony/Component/Config/Definition/ArrayShapeGenerator.php
156 строк
6 KB
Ousama Ben Younes
[Config] Generate unsealed array shapes for any normalizers
30 июл 2026, 18:04
30 июл 2026, 18:04
4b229ba
Код
Авторство
О чём код?
<?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\Definition; use Symfony\Component\Config\Definition\Builder\ExprBuilder; use Symfony\Component\Config\Loader\ParamConfigurator; /** * @author Alexandre Daubois <alex.daubois@gmail.com> */ final class ArrayShapeGenerator { public static function generate(NodeInterface $node): string { // "*/" anywhere in the shape (a comment, an enum value, a node name) would prematurely // close the surrounding doc block, so the slash is escaped to keep the value readable return str_replace(['*/', "\n"], ['*\/', "\n * "], self::doGeneratePhpDoc($node)); } private static function doGeneratePhpDoc(NodeInterface $node, int $nestingLevel = 1): string { if (!$node instanceof ArrayNode) { $typeString = match (true) { $node instanceof BooleanNode => $node->hasDefaultValue() && null === $node->getDefaultValue() ? 'bool|null' : 'bool', $node instanceof StringNode => 'string', $node instanceof NumericNode => self::handleNumericNode($node), $node instanceof EnumNode => $node->getPermissibleValues('|', false), $node instanceof ScalarNode => 'scalar|null', default => 'mixed', }; if ('mixed' === $typeString) { return $typeString; } if (str_ends_with($typeString, '|null')) { return substr_replace($typeString, '|\\'.ParamConfigurator::class, -5, 0); } return $typeString.'|\\'.ParamConfigurator::class; } if ($node instanceof PrototypedArrayNode) { $isHashmap = (bool) $node->getKeyAttribute(); $arrayShape = ($isHashmap ? 'array<string, ' : 'list<').self::doGeneratePhpDoc($node->getPrototype(), $nestingLevel).'>'; return implode('|', [...self::getNormalizedTypes($node, ['array', 'any']), $arrayShape]); } if (!($children = $node->getChildren()) && !$node->getParent() instanceof PrototypedArrayNode) { return $node->hasDefaultValue() && null === $node->getDefaultValue() ? 'array<mixed>|null' : 'array<mixed>'; } $arrayShape = \sprintf("array{%s\n", self::generateInlinePhpDocForNode($node)); foreach ($children as $child) { $arrayShape .= str_repeat(' ', $nestingLevel).self::dumpNodeKey($child, $node).': '; $arrayShape .= self::doGeneratePhpDoc($child, 1 + $nestingLevel); $arrayShape .= \sprintf(",%s\n", !$child instanceof ArrayNode ? self::generateInlinePhpDocForNode($child) : ''); } if ($node->shouldIgnoreExtraKeys() || \in_array(ExprBuilder::TYPE_ANY, $node->getNormalizedTypes(), true)) { $arrayShape .= str_repeat(' ', $nestingLevel)."...<string, mixed>\n"; } $arrayShape = $arrayShape.str_repeat(' ', $nestingLevel - 1).'}'; return implode('|', [...self::getNormalizedTypes($node, ['array', 'any']), $arrayShape]); } private static function dumpNodeKey(NodeInterface $node, ?ArrayNode $parent = null): 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)."'"; } $optional = !$node->isRequired() || ($parent instanceof ArrayNode && $parent->shouldPerformDeepMerging()); return $name.($optional ? '?' : ''); } private static function handleNumericNode(NumericNode $node): string { // We could use int<%s, %s> but PhpStorm doesn't support it yet // $min = $node->getMin() ?? 'min'; // $max = $node->getMax() ?? 'max'; if ($node instanceof IntegerNode) { return 'int'; } if ($node instanceof FloatNode) { return 'float'; } return 'int|float'; } private static function generateInlinePhpDocForNode(BaseNode $node): string { $comment = ''; if ($node->isDeprecated()) { $comment .= ' // Deprecated: '.$node->getDeprecation($node->getName(), $node->getPath())['message']; } if ($info = $node->getInfo()) { $comment .= ' // '.$info; } if ((!$node instanceof ArrayNode || ($node = $node->getParent()) instanceof PrototypedArrayNode) && $node->hasDefaultValue()) { $comment .= ' // Default: '.json_encode($node->getDefaultValue(), \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRESERVE_ZERO_FRACTION); } return rtrim(preg_replace('/\s+/', ' ', $comment)); } /** * @return list<string> */ private static function getNormalizedTypes(BaseNode $node, array $excluded = []): array { $types = array_diff($node->getNormalizedTypes(), $excluded); if ($node->hasDefaultValue() && null === $node->getDefaultValue()) { $types[] = 'null'; } $types = array_unique($types); if (false !== $backedEnumIndex = array_search(ExprBuilder::TYPE_BACKED_ENUM, $types, true)) { $types[$backedEnumIndex] = '\BackedEnum'; } if (array_intersect($types, [ExprBuilder::TYPE_STRING, '\BackedEnum'])) { $types[] = '\\'.ParamConfigurator::class; } sort($types); return $types; } }