Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.78% covered (success)
97.78%
44 / 45
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Parser
97.78% covered (success)
97.78%
44 / 45
80.00% covered (warning)
80.00%
4 / 5
23
0.00% covered (danger)
0.00%
0 / 1
 parse
n/a
0 / 0
n/a
0 / 0
0
 parseOrThrow
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 checkConfig
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 addChild
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getValue
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
12
 ksortRecursive
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php declare(strict_types=1);
2/*
3 * This file is part of Aplus Framework Config Library.
4 *
5 * (c) Natan Felles <natanfelles@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10namespace Framework\Config\Parsers;
11
12use Closure;
13use JetBrains\PhpStorm\Pure;
14use SensitiveParameter;
15
16/**
17 * Class Parser.
18 *
19 * @package config
20 */
21abstract class Parser
22{
23    /**
24     * Parse the config output.
25     *
26     * @param mixed $config
27     *
28     * @throws ParserException If config is invalid or data can not be parsed
29     *
30     * @return array<int|string,mixed> The parsed configs
31     */
32    abstract public static function parse(mixed $config) : array;
33
34    /**
35     * @param Closure $function
36     *
37     * @throws ParserException If config data can not be parsed
38     *
39     * @return array<int|string,mixed>
40     */
41    protected static function parseOrThrow(Closure $function) : array
42    {
43        \set_error_handler(static function ($severity, $message, $file, $line) : void {
44            $message = static::class . ': ' . $message;
45            throw new ParserException($message, $severity, $severity, $file, $line);
46        });
47        $result = $function();
48        \restore_error_handler();
49        return $result;
50    }
51
52    /**
53     * Check for config issues.
54     *
55     * @param mixed $config The parser configuration
56     *
57     * @throws ParserException If config is invalid
58     */
59    protected static function checkConfig(#[SensitiveParameter] mixed $config) : void
60    {
61        if ( ! \is_string($config)) {
62            throw new ParserException(static::class . ' config must be a string');
63        }
64        $file = \realpath($config);
65        if ($file === false || ! \is_file($file)) {
66            throw new ParserException('File not found: ' . $config);
67        }
68        if ( ! \is_readable($file)) {
69            throw new ParserException('File is not readable: ' . $config);
70        }
71    }
72
73    /**
74     * Recursively adds childs to an array tree.
75     *
76     * @param array<int|string,mixed> $parent The main array, where the childs will be added
77     * @param array<int|string,mixed> $childs Childs to add
78     * @param mixed $value The last child value
79     */
80    protected static function addChild(array &$parent, array $childs, mixed $value) : void
81    {
82        $key = \array_shift($childs);
83        $parent[$key] = [];
84        if ($childs === []) {
85            $parent[$key] = $value;
86            return;
87        }
88        static::addChild($parent[$key], $childs, $value);
89    }
90
91    /**
92     * Interprets a string value and returns it with a PHP type.
93     *
94     * @param string $value The input value
95     *
96     * @return array<int|string,mixed>|bool|float|int|string|null The output value
97     */
98    #[Pure]
99    protected static function getValue(string $value) : array|bool|int|float|string|null
100    {
101        $value = \trim($value);
102        $lowerValue = \strtolower($value);
103        if ($lowerValue === 'true') {
104            return true;
105        }
106        if ($lowerValue === 'false') {
107            return false;
108        }
109        if ($lowerValue === 'null') {
110            return null;
111        }
112        if (\is_numeric($value) && $value >= \PHP_INT_MIN && $value <= \PHP_INT_MAX) {
113            return \str_contains($value, '.') ? (float) $value : (int) $value;
114        }
115        if (\str_starts_with($value, '"') && \str_ends_with($value, '"')) {
116            $value = \substr($value, 1, -1);
117            return \strtr($value, [
118                '\"' => '"',
119                '\\\\' => '\\',
120            ]);
121        }
122        if (\str_starts_with($value, "'") && \str_ends_with($value, "'")) {
123            return \substr($value, 1, -1);
124        }
125        return $value;
126    }
127
128    /**
129     * Sort arrays by keys recursively.
130     *
131     * @param mixed $value The input value
132     *
133     * @return mixed The output value (sorted by keys if the $value is an array)
134     */
135    protected static function ksortRecursive(mixed $value) : mixed
136    {
137        if ( ! \is_array($value)) {
138            return $value;
139        }
140        \ksort($value);
141        foreach ($value as &$val) {
142            $val = static::ksortRecursive($val);
143        }
144        return $value;
145    }
146}