Files
haushalt/backend/vendor/symfony/config/Definition/ArrayShapeGenerator.php
2026-03-24 00:04:55 +01:00

153 lines
5.4 KiB
PHP

<?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\Loader\ParamConfigurator;
/**
* @author Alexandre Daubois <alex.daubois@gmail.com>
*/
final class ArrayShapeGenerator
{
public static function generate(NodeInterface $node): string
{
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(), 1 + $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).': ';
if ($child instanceof PrototypedArrayNode) {
$isHashmap = (bool) $child->getKeyAttribute();
$childArrayType = ($isHashmap ? 'array<string, ' : 'list<').self::doGeneratePhpDoc($child->getPrototype(), 1 + $nestingLevel).'>';
$arrayShape .= $child->hasDefaultValue() && null === $child->getDefaultValue() ? $childArrayType.'|null' : $childArrayType;
} else {
$arrayShape .= self::doGeneratePhpDoc($child, 1 + $nestingLevel);
}
$arrayShape .= \sprintf(",%s\n", !$child instanceof ArrayNode ? self::generateInlinePhpDocForNode($child) : '');
}
if ($node->shouldIgnoreExtraKeys()) {
$arrayShape .= str_repeat(' ', $nestingLevel)."...<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);
sort($types);
return $types;
}
}