Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
97 / 97 |
|
100.00% |
18 / 18 |
CRAP | |
100.00% |
1 / 1 |
Config | |
100.00% |
97 / 97 |
|
100.00% |
18 / 18 |
53 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
createFromData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
parseData | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
8 | |||
merge | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
mergeFromData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
render | |
100.00% |
24 / 24 |
|
100.00% |
1 / 1 |
9 | |||
writeToFile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
toArrayObject | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
toJson | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
toYaml | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
toIni | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
toXml | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
changesAllowed | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
arrayToXml | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
5 | |||
arrayToYaml | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
arrayToIni | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
9 | |||
__set | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
__unset | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Config; |
15 | |
16 | use Pop\Utils\ArrayObject; |
17 | use SimpleXMLElement; |
18 | use DOMDocument; |
19 | |
20 | /** |
21 | * Config class |
22 | * |
23 | * @category Pop |
24 | * @package Pop\Config |
25 | * @author Nick Sagona, III <dev@nolainteractive.com> |
26 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
27 | * @license http://www.popphp.org/license New BSD License |
28 | * @version 4.0.0 |
29 | */ |
30 | class Config extends ArrayObject |
31 | { |
32 | |
33 | /** |
34 | * Flag for whether changes are allowed after object instantiation |
35 | * @var bool |
36 | */ |
37 | protected bool $allowChanges = false; |
38 | |
39 | /** |
40 | * Constructor |
41 | * |
42 | * Instantiate a config object |
43 | * |
44 | * @param mixed $data |
45 | * @param bool $changes |
46 | * @throws Exception |
47 | */ |
48 | public function __construct(mixed $data = [], bool $changes = false) |
49 | { |
50 | $this->allowChanges = $changes; |
51 | parent::__construct($data); |
52 | } |
53 | |
54 | /** |
55 | * Method to create a config object from parsed data |
56 | * |
57 | * @param mixed $data |
58 | * @param bool $changes |
59 | * @return self |
60 | */ |
61 | public static function createFromData(mixed $data = [], bool $changes = false): Config |
62 | { |
63 | return new self(self::parseData($data), $changes); |
64 | } |
65 | |
66 | /** |
67 | * Method to parse data and return config values |
68 | * |
69 | * @param mixed $data |
70 | * @return array |
71 | */ |
72 | public static function parseData(mixed $data): array |
73 | { |
74 | // If PHP |
75 | if ((strtolower((substr($data, -6)) == '.phtml') || |
76 | strtolower((substr($data, -4)) == '.php'))) { |
77 | $data = include $data; |
78 | // If JSON |
79 | } else if (strtolower(substr($data, -5)) == '.json') { |
80 | $data = json_decode(file_get_contents($data), true); |
81 | // If YAML - requires YAML ext |
82 | } else if ((strtolower(substr($data, -5)) == '.yaml') || |
83 | (strtolower(substr($data, -4)) == '.yml')) { |
84 | $data = yaml_parse(file_get_contents($data)); |
85 | // If INI |
86 | } else if (strtolower(substr($data, -4)) == '.ini') { |
87 | $data = parse_ini_file($data, true); |
88 | // If XML |
89 | } else if (strtolower(substr($data, -4)) == '.xml') { |
90 | $data = (array)simplexml_load_file($data); |
91 | } else { |
92 | $data = []; |
93 | } |
94 | |
95 | return $data; |
96 | } |
97 | |
98 | /** |
99 | * Merge the values of another config object into this one. |
100 | * By default, existing values are overwritten, unless the |
101 | * $preserve flag is set to true. |
102 | * |
103 | * @param mixed $data |
104 | * @param bool $preserve |
105 | * @throws Exception |
106 | * @return Config |
107 | */ |
108 | public function merge(mixed $data, bool $preserve = false): Config |
109 | { |
110 | if (!$this->allowChanges) { |
111 | throw new Exception('Real-time configuration changes are not allowed.'); |
112 | } |
113 | |
114 | if ($data instanceof Config) { |
115 | $data = $data->toArray(); |
116 | } |
117 | |
118 | $this->data = ($preserve) ? |
119 | array_merge_recursive($this->data, $data) : array_replace_recursive($this->data, $data); |
120 | |
121 | return $this; |
122 | } |
123 | |
124 | /** |
125 | * Merge the values of another config object into this one. |
126 | * By default, existing values are overwritten, unless the |
127 | * $preserve flag is set to true. |
128 | * |
129 | * @param mixed $data |
130 | * @param bool $preserve |
131 | * @throws Exception |
132 | * @return Config |
133 | */ |
134 | public function mergeFromData(mixed $data, bool $preserve = false): Config |
135 | { |
136 | if (!$this->allowChanges) { |
137 | throw new Exception('Real-time configuration changes are not allowed.'); |
138 | } |
139 | |
140 | return $this->merge(self::parseData($data), $preserve); |
141 | } |
142 | |
143 | /** |
144 | * Render data to a string format |
145 | * |
146 | * @param string $format |
147 | * @throws Exception |
148 | * @return string |
149 | */ |
150 | public function render(string $format): string |
151 | { |
152 | $config = ''; |
153 | |
154 | switch (strtolower($format)) { |
155 | case 'php': |
156 | case 'phtml': |
157 | $config = '<?php' . PHP_EOL . PHP_EOL; |
158 | $config .= 'return ' . var_export($this->toArray(), true) . ';'; |
159 | $config .= PHP_EOL; |
160 | break; |
161 | case 'json': |
162 | $config = $this->toJson(); |
163 | break; |
164 | case 'yml': |
165 | case 'yaml': |
166 | return $this->toYaml(); |
167 | case 'ini': |
168 | $config = $this->toIni(); |
169 | break; |
170 | case 'xml': |
171 | $config =$this->toXml(); |
172 | break; |
173 | default: |
174 | throw new Exception( |
175 | "Invalid type '" . $format . "'. Supported config file types are PHP, JSON, YAML, INI or XML." |
176 | ); |
177 | } |
178 | |
179 | return $config; |
180 | } |
181 | |
182 | /** |
183 | * Write the config data to file |
184 | * |
185 | * @param string $filename |
186 | * @throws Exception |
187 | * @return void |
188 | */ |
189 | public function writeToFile(string $filename): void |
190 | { |
191 | if (str_contains($filename, '.')) { |
192 | $ext = strtolower(substr($filename, (strrpos($filename, '.') + 1))); |
193 | file_put_contents($filename, $this->render($ext)); |
194 | } |
195 | } |
196 | |
197 | /** |
198 | * Get the config values as an array |
199 | * |
200 | * @throws \Pop\Utils\Exception |
201 | * @return ArrayObject|\ArrayObject |
202 | */ |
203 | public function toArrayObject($native = false): ArrayObject|\ArrayObject |
204 | { |
205 | return ($native) ? new \ArrayObject($this->toArray(), \ArrayObject::ARRAY_AS_PROPS) : new ArrayObject($this->toArray()); |
206 | } |
207 | |
208 | /** |
209 | * Get the config values as a JSON string |
210 | * |
211 | * @return string |
212 | */ |
213 | public function toJson(): string |
214 | { |
215 | return $this->jsonSerialize(JSON_PRETTY_PRINT); |
216 | } |
217 | |
218 | /** |
219 | * Get the config values as an YAML string |
220 | * |
221 | * @return string |
222 | */ |
223 | public function toYaml(): string |
224 | { |
225 | return $this->arrayToYaml($this->toArray()); |
226 | } |
227 | |
228 | /** |
229 | * Get the config values as an INI string |
230 | * |
231 | * @return string |
232 | */ |
233 | public function toIni(): string |
234 | { |
235 | return $this->arrayToIni($this->toArray()); |
236 | } |
237 | |
238 | /** |
239 | * Get the config values as an XML string |
240 | * |
241 | * @return string |
242 | */ |
243 | public function toXml(): string |
244 | { |
245 | $config = new SimpleXMLElement('<?xml version="1.0"?><config></config>'); |
246 | $this->arrayToXml($this->toArray(), $config); |
247 | |
248 | $dom = new DOMDocument('1.0'); |
249 | $dom->preserveWhiteSpace = false; |
250 | $dom->formatOutput = true; |
251 | $dom->loadXML($config->asXML()); |
252 | return $dom->saveXML(); |
253 | } |
254 | |
255 | /** |
256 | * Return if changes to the config are allowed. |
257 | * |
258 | * @return bool |
259 | */ |
260 | public function changesAllowed(): bool |
261 | { |
262 | return $this->allowChanges; |
263 | } |
264 | |
265 | /** |
266 | * Method to convert array to XML |
267 | * |
268 | * @param array $array |
269 | * @param SimpleXMLElement $config |
270 | * @return void |
271 | */ |
272 | protected function arrayToXml(array $array, SimpleXMLElement &$config): void |
273 | { |
274 | foreach($array as $key => $value) { |
275 | if(is_array($value)) { |
276 | $subNode = (!is_numeric($key)) ? $config->addChild($key) : $config->addChild('item'); |
277 | $this->arrayToXml($value, $subNode); |
278 | } else { |
279 | if (!is_numeric($key)) { |
280 | $config->addChild($key, htmlspecialchars($value)); |
281 | } else { |
282 | $config->addChild('item', htmlspecialchars($value)); |
283 | } |
284 | } |
285 | } |
286 | } |
287 | |
288 | /** |
289 | * Method to convert array to Yaml |
290 | * |
291 | * @param array $array |
292 | * @return string |
293 | */ |
294 | protected function arrayToYaml(array $array): string |
295 | { |
296 | return yaml_emit($array); |
297 | } |
298 | |
299 | /** |
300 | * Method to convert array to INI |
301 | * |
302 | * @param array $array |
303 | * @return string |
304 | */ |
305 | protected function arrayToIni(array $array): string |
306 | { |
307 | $ini = ''; |
308 | $lastWasArray = false; |
309 | |
310 | foreach ($array as $key => $value) { |
311 | if (is_array($value)) { |
312 | if (!$lastWasArray) { |
313 | $ini .= PHP_EOL; |
314 | } |
315 | $ini .= '[' . $key . ']' . PHP_EOL; |
316 | foreach ($value as $k => $v) { |
317 | if (!is_array($v)) { |
318 | $ini .= $key . |
319 | '[' . ((!is_numeric($k)) ? $k : null) . '] = ' . |
320 | ((!is_numeric($v)) ? '"' . $v . '"' : $v) . PHP_EOL; |
321 | } |
322 | } |
323 | $ini .= PHP_EOL; |
324 | $lastWasArray = true; |
325 | } else { |
326 | $ini .= $key . " = " . ((!is_numeric($value)) ? '"' . $value . '"' : $value) . PHP_EOL; |
327 | $lastWasArray = false; |
328 | } |
329 | } |
330 | |
331 | return $ini; |
332 | } |
333 | |
334 | /** |
335 | * Set a value |
336 | * |
337 | * @param ?string $name |
338 | * @param mixed $value |
339 | * @return static |
340 | */ |
341 | public function __set(?string $name = null, mixed $value = null) |
342 | { |
343 | if (!$this->allowChanges) { |
344 | throw new Exception('Real-time configuration changes are not allowed.'); |
345 | } |
346 | parent::__set($name, $value); |
347 | } |
348 | |
349 | /** |
350 | * Unset a value |
351 | * |
352 | * @param string $name |
353 | * @throws Exception |
354 | * @return void |
355 | */ |
356 | public function __unset(string $name): void |
357 | { |
358 | if (!$this->allowChanges) { |
359 | throw new Exception('Real-time configuration changes are not allowed.'); |
360 | } |
361 | parent::__unset($name); |
362 | } |
363 | |
364 | } |