Lines 100.00% 32 / 32
Methods 100.00% 9 / 9
Classes 100.00% 1 / 1
Name Lines Methods CRAP
 supportsNode 100.00% 1 / 1 100.00% 1 / 1 2
 parseForNode 100.00% 6 / 6 100.00% 1 / 1 7
 parseInteger 100.00% 2 / 2 100.00% 1 / 1 2
 parseFloat 100.00% 2 / 2 100.00% 1 / 1 2
 parseInlineList 100.00% 6 / 6 100.00% 1 / 1 4
 inferValue 100.00% 11 / 11 100.00% 1 / 1 10
 isScalarList 100.00% 1 / 1 100.00% 1 / 1 2
 isInlineCollection 100.00% 2 / 2 100.00% 1 / 1 2
 createValueInvalidFailure 100.00% 1 / 1 100.00% 1 / 1 1
21final readonly class EnvironmentValueParser
22{
23    private const array LEAF_NODE_CLASSES = [
24        BooleanNode::class,
25        IntegerNode::class,
26        FloatNode::class,
27        ScalarNode::class,
28        EnumNode::class,
29        VariableNode::class,
30    ];
31
32    public function supportsNode(NodeInterface $node): bool
33    {
34        return \in_array($node::class, self::LEAF_NODE_CLASSES, true) || $this->isScalarList($node);
35    }
36
37    /** @throws ConfigurationException */
38    public function parseForNode(NodeInterface $node, string $name, string $raw): mixed
39    {
40        return match ($node::class) {
41            BooleanNode::class => BooleanSpelling::tryParseBoolean($raw) ?? throw $this->createValueInvalidFailure($name, 'true or false', $raw),
42            IntegerNode::class => $this->parseInteger($name, $raw),
43            FloatNode::class => $this->parseFloat($name, $raw),
44            VariableNode::class => $this->inferValue($raw),
45            default => $this->isScalarList($node) ? $this->parseInlineList($name, $raw) : $raw,
46        };
47    }
48
49    /** @throws ConfigurationException */
50    private function parseInteger(string $name, string $raw): int
51    {
52        $value = trim($raw);
53
54        return preg_match('/^[+-]?\d+$/', $value) === 1 ? (int) $value : throw $this->createValueInvalidFailure($name, 'a whole number', $raw);
55    }
56
57    /** @throws ConfigurationException */
58    private function parseFloat(string $name, string $raw): float
59    {
60        $value = trim($raw);
61
62        return is_numeric($value) ? (float) $value : throw $this->createValueInvalidFailure($name, 'a number', $raw);
63    }
64
65    /**
66     * @return array<array-key, mixed>
67     * @throws ConfigurationException
68     */
69    private function parseInlineList(string $name, string $raw): array
70    {
71        if (!$this->isInlineCollection($raw)) {
72            throw $this->createValueInvalidFailure($name, 'an inline list such as [a, b]', $raw);
73        }
74
75        try {
76            $value = Yaml::parse(trim($raw));
77        } catch (ParseException $e) {
78            throw ConfigurationException::createForReason(ConfigurationError::EnvironmentValueUnparsable, $e, variable: $name);
79        }
80
81        return \is_array($value) ? $value : throw $this->createValueInvalidFailure($name, 'an inline list such as [a, b]', $raw);
82    }
83
84    private function inferValue(string $raw): mixed
85    {
86        $value = trim($raw);
87
88        if ($this->isInlineCollection($value)) {
89            try {
90                return Yaml::parse($value);
91            } catch (ParseException) {
92                return $value;
93            }
94        }
95
96        return match (true) {
97            strtolower($value) === 'true' => true,
98            strtolower($value) === 'false' => false,
99            strtolower($value) === 'null', $value === '~' => null,
100            $value === (string) (int) $value => (int) $value,
101            is_numeric($value) && $value === (string) (float) $value => (float) $value,
102            default => $value,
103        };
104    }
105
106    private function isScalarList(NodeInterface $node): bool
107    {
108        return $node instanceof PrototypedArrayNode && $node->getPrototype() instanceof ScalarNode;
109    }
110
111    private function isInlineCollection(string $raw): bool
112    {
113        $value = trim($raw);
114
115        return str_starts_with($value, '[') || str_starts_with($value, '{');
116    }
117
118    private function createValueInvalidFailure(string $name, string $expected, string $raw): ConfigurationException
119    {
120        return ConfigurationException::createForReason(ConfigurationError::EnvironmentValueInvalid, variable: $name, expected: $expected, raw: $raw);
121    }
122}