Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
32 / 32 |
|
100.00% |
3 / 3 |
CRAP | |
100.00% |
1 / 1 |
ConfigHelper | |
100.00% |
32 / 32 |
|
100.00% |
3 / 3 |
19 | |
100.00% |
1 / 1 |
__construct | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
isAssoc | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
normalize | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
13 | |||
flatten | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | /** |
6 | * This file is part of php-fast-forward/config. |
7 | * |
8 | * This source file is subject to the license bundled |
9 | * with this source code in the file LICENSE. |
10 | * |
11 | * @link https://github.com/php-fast-forward/config |
12 | * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <github@mentordosnerds.com> |
13 | * @license https://opensource.org/licenses/MIT MIT License |
14 | */ |
15 | |
16 | namespace FastForward\Config\Helper; |
17 | |
18 | use Dflydev\DotAccessData\Data; |
19 | use Dflydev\DotAccessData\DataInterface; |
20 | use Dflydev\DotAccessData\Util; |
21 | |
22 | /** |
23 | * Class ConfigHelper. |
24 | * |
25 | * Provides a set of static helper methods for manipulating configuration arrays, |
26 | * particularly handling associative arrays with dot notation and nested structures. |
27 | * This class SHALL NOT be instantiated and MUST be used statically. |
28 | */ |
29 | final class ConfigHelper |
30 | { |
31 | /** |
32 | * ConfigHelper constructor. |
33 | * |
34 | * This constructor is private to prevent instantiation of the class. |
35 | * The class MUST be used in a static context only. |
36 | * |
37 | * @codeCoverageIgnore |
38 | */ |
39 | private function __construct() |
40 | { |
41 | // Prevent instantiation |
42 | } |
43 | |
44 | /** |
45 | * Determines if the provided value is an associative array. |
46 | * |
47 | * This method SHALL check whether the given array uses string keys, |
48 | * distinguishing it from indexed arrays. |
49 | * |
50 | * @param mixed $value the value to check |
51 | * |
52 | * @return bool true if the array is associative; false otherwise |
53 | */ |
54 | public static function isAssoc(mixed $value): bool |
55 | { |
56 | return \is_array($value) && Util::isAssoc($value); |
57 | } |
58 | |
59 | /** |
60 | * Normalizes a configuration array using dot notation delimiters. |
61 | * |
62 | * This method SHALL recursively convert keys containing delimiters into nested arrays. |
63 | * For example, a key like "database.host" SHALL be transformed into |
64 | * ['database' => ['host' => 'value']]. |
65 | * |
66 | * @param array $config the configuration array to normalize |
67 | * |
68 | * @return array the normalized configuration array |
69 | */ |
70 | public static function normalize(array $config): array |
71 | { |
72 | if (!self::isAssoc($config)) { |
73 | return $config; |
74 | } |
75 | |
76 | $normalized = []; |
77 | |
78 | $reflectionConst = new \ReflectionClassConstant(Data::class, 'DELIMITERS'); |
79 | $delimiters = $reflectionConst->getValue(); |
80 | |
81 | $delimiterChars = implode('', $delimiters); |
82 | $delimitersPattern = '/[' . preg_quote($delimiterChars, '/') . ']/'; |
83 | |
84 | foreach ($config as $key => $value) { |
85 | if (self::isAssoc($value)) { |
86 | $value = self::normalize($value); |
87 | } |
88 | |
89 | if (!\is_string($key) || false === strpbrk($key, $delimiterChars)) { |
90 | $normalized[$key] = $value; |
91 | |
92 | continue; |
93 | } |
94 | |
95 | $parts = preg_split($delimitersPattern, $key); |
96 | $current = &$normalized; |
97 | $lastIndex = \count($parts) - 1; |
98 | |
99 | foreach ($parts as $index => $part) { |
100 | if ($index !== $lastIndex) { |
101 | if (!isset($current[$part]) || !\is_array($current[$part])) { |
102 | $current[$part] = []; |
103 | } |
104 | $current = &$current[$part]; |
105 | |
106 | continue; |
107 | } |
108 | |
109 | if (isset($current[$part]) && \is_array($current[$part]) && \is_array($value)) { |
110 | $current[$part] = Util::mergeAssocArray($current[$part], $value, DataInterface::MERGE); |
111 | |
112 | continue; |
113 | } |
114 | |
115 | $current[$part] = $value; |
116 | } |
117 | } |
118 | |
119 | return $normalized; |
120 | } |
121 | |
122 | /** |
123 | * Flattens a nested configuration array into a dot-notated traversable set. |
124 | * |
125 | * This method SHALL recursively iterate through the nested array structure |
126 | * and convert it into a flat representation where keys reflect the nested path. |
127 | * |
128 | * For example: |
129 | * Input: ['database' => ['host' => 'localhost']] |
130 | * Output: ['database.host' => 'localhost'] |
131 | * |
132 | * @param array $config the configuration array to flatten |
133 | * @param string $rootKey (Optional) The root key prefix for recursive calls |
134 | * |
135 | * @return \Traversable<string, mixed> a traversable list of flattened key-value pairs |
136 | */ |
137 | public static function flatten(array $config, string $rootKey = ''): \Traversable |
138 | { |
139 | foreach ($config as $key => $value) { |
140 | if (\is_array($value)) { |
141 | yield from self::flatten($value, $rootKey . $key . '.'); |
142 | } else { |
143 | yield $rootKey . $key => $value; |
144 | } |
145 | } |
146 | } |
147 | } |