Completed
Push — master ( c05391...d7ccd4 )
by Andre
01:52
created

Space::replacePlaceholders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
3
namespace TheIconic\Config;
4
5
use TheIconic\Config\Exception\ParserException;
6
use TheIconic\Config\Exception\PreconditionException;
7
use TheIconic\Config\Parser\AbstractParser;
8
use TheIconic\Config\Parser\Autodetect as Parser;
9
10
/**
11
 * handler for shared application config
12
 *
13
 * @package Shared\Helper
14
 */
15
class Space
16
{
17
18
    /**
19
     * @var null|array stores the config
20
     */
21
    protected $config;
22
23
    /**
24
     * @var array the config file paths
25
     */
26
    protected $paths = [];
27
28
    /**
29
     * @var Cache the config cache handler
30
     */
31
    protected $cache;
32
33
    /**
34
     * @var string
35
     */
36
    protected $cachePath;
37
38
    /**
39
     * @var array the sections to use
40
     */
41
    protected $sections = [];
42
43
    /**
44
     * @var AbstractParser $parser
45
     */
46
    protected $parser;
47
48
    /**
49
     * @var string the config namespace
50
     */
51
    protected $name;
52
53
    /**
54
     * @var array placeholders
55
     */
56
    protected $placeholders = [];
57
58
    /**
59
     * Space constructor.
60
     * @param $name
61
     */
62
    public function __construct($name)
63
    {
64
        $this->name = $name;
65
    }
66
67
    /**
68
     * get the config value for a given key or the entire config if key is omitted
69
     * allows specifying a default that is used if key is not present in config
70
     *
71
     * @param string|null $key the config key
72
     * @param mixed|null $default the default value
73
     * @return array|mixed|null the config value or the entire configuration array
74
     */
75
    public function get($key = null, $default = null)
76
    {
77
        $this->init();
78
79
        if (null === $key) {
80
            return $this->config;
81
        }
82
83
        return $this->resolve($key, $default);
84
    }
85
86
    /**
87
     * initialize the config space
88
     *
89
     * loads the config from cache or the config files
90
     */
91
    protected function init()
92
    {
93
        if (null !== $this->config) {
94
            return;
95
        }
96
97
        $cache = $this->getCache();
98
        $cacheKey = $this->getCacheKey();
99
100
        if ($cache->isValid($cacheKey, $this->getTimestamp())) {
101
            $this->config = $cache->read($cacheKey);
102
        } else {
103
            $this->config = $this->parse();
104
            $cache->write($cacheKey, $this->config, $this->getPaths());
105
        }
106
    }
107
108
    /**
109
     * @return array
110
     */
111
    public function flatten()
112
    {
113
        return $this->doFlatten($this->get());
114
    }
115
116
    /**
117
     * parses the configs
118
     *
119
     * @return array the parsed and flattened config array
120
     */
121
    protected function parse()
122
    {
123
        $parser = $this->getParser();
124
125
        $config = [];
126
        foreach ($this->getReadableSources() as $path) {
127
            try {
128
                $config = $this->merge($config, $this->replacePlaceholders($parser->parse($path)));
129
            } catch (ParserException $e) {
130
                // ignore parse errors
131
            }
132
        }
133
134
        return $this->flattenSections($config);
135
    }
136
137
    /**
138
     * @param array $config
139
     * @return array
140
     */
141
    protected function replacePlaceholders(array $config)
142
    {
143
        if (empty($this->placeholders)) {
144
            return $config;
145
        }
146
147
        foreach ($config as $key => $value) {
148
            $config[$key] = $this->processValue($value);
149
        }
150
151
        return $config;
152
    }
153
154
    /**
155
     * @param mixed $value
156
     * @return mixed
157
     */
158
    protected function processValue($value)
159
    {
160
        if (is_array($value)) {
161
            return $this->replacePlaceholders($value);
162
        }
163
164
        if (is_string($value)) {
165
            return strtr($value, $this->placeholders);
166
        }
167
168
        return $value;
169
    }
170
171
    /**
172
     * @return int
173
     */
174
    protected function getTimestamp()
175
    {
176
        $timestamp = 0;
177
        foreach ($this->getReadableSources() as $path) {
178
            $timestamp = max($timestamp, filemtime($path));
179
            $timestamp = max($timestamp, filemtime(dirname($path)));
180
        }
181
182
        return $timestamp;
183
    }
184
    
185
    /**
186
     * get the actually readable source paths
187
     *
188
     * @return array
189
     */
190
    protected function getReadableSources()
191
    {
192
        $paths = [];
193
194
        foreach ($this->getPaths() as $path) {
195
            if (is_readable($path)) {
196
                $paths[] = $path;
197
            }
198
        }
199
200
        return $paths;
201
    }
202
203
    /**
204
     * flattens the config array for the given environment
205
     *
206
     * @param array $config the raw config array
207
     * @return array the flattened config array
208
     */
209
    protected function flattenSections(array $config)
210
    {
211
        $merged = [];
212
213
        $sections = $this->getSections();
214
215
        if (empty($sections)) {
216
            throw new PreconditionException('No sections have been configured.');
217
        }
218
219
        foreach (array_unique($this->getSections()) as $section) {
220
            $section = strtolower($section);
221
222
            if (!isset($config[$section])) {
223
                continue;
224
            }
225
226
            $merged = $this->merge($merged, $config[$section]);
227
        }
228
229
        return $merged;
230
    }
231
    
232
    /**
233
     * get the config cache handler
234
     *
235
     * @return Cache
236
     */
237
    public function getCache()
238
    {
239
        return $this->cache;
240
    }
241
242
    /**
243
     * set the config cache handler
244
     *
245
     * @param Cache $cache
246
     * @return $this
247
     */
248
    public function setCache(Cache $cache)
249
    {
250
        $this->cache = $cache;
251
252
        return $this;
253
    }
254
255
    /**
256
     * get the environment
257
     *
258
     * @return array
259
     */
260
    public function getSections()
261
    {
262
        return $this->sections;
263
    }
264
265
    /**
266
     * set the sections to use
267
     *
268
     * @param array $sections
269
     * @return $this
270
     */
271
    public function setSections(array $sections)
272
    {
273
        $this->sections = $sections;
274
275
        return $this;
276
    }
277
278
    /**
279
     * @param string $section
280
     * @return $this
281
     */
282
    public function addSection(string $section)
283
    {
284
        if (!in_array($section, $this->sections)) {
285
            $this->sections[] = $section;
286
        }
287
288
        return $this;
289
    }
290
291
    /**
292
     * get the parser
293
     *
294
     * @return Parser
295
     */
296
    public function getParser()
297
    {
298
        if (null === $this->parser) {
299
            $this->parser = new Parser();
300
        }
301
302
        return $this->parser;
303
    }
304
305
    /**
306
     * set the parser
307
     *
308
     * @param AbstractParser $parser
309
     * @return $this
310
     */
311
    public function setParser(AbstractParser $parser)
312
    {
313
        $this->parser = $parser;
314
315
        return $this;
316
    }
317
318
    /**
319
     * get configuration file paths
320
     *
321
     * @return array an array of configuration file paths
322
     */
323
    public function getPaths(): array
324
    {
325
        return $this->paths;
326
    }
327
328
    /**
329
     * @param array $paths
330
     * @return Space
331
     */
332
    public function setPaths(array $paths): Space
333
    {
334
        $this->paths = $paths;
335
336
        return $this;
337
    }
338
339
    /**
340
     * @param string $path
341
     * @return Space
342
     */
343
    public function addPath($path): Space
344
    {
345
        if (!in_array($path, $this->paths)) {
346
            $this->paths[] = $path;
347
        }
348
349
        return $this;
350
    }
351
352
    /**
353
     * get the path to the cache config file for the current environment
354
     *
355
     * @return string the file path
356
     */
357
    protected function getCacheKey()
358
    {
359
        return strtolower(md5(sprintf('%s_%s', implode('::', $this->getPaths()), implode('::', $this->getSections()))));
360
    }
361
362
    /**
363
     * @param string $path
364
     * @param mixed|null $default
365
     * @return array|mixed|null
366
     */
367
    protected function resolve($path, $default = null)
368
    {
369
        $segments = explode('.', $path);
370
        $result = $this->config;
371
372
        foreach ($segments as $segment) {
373
            if (!isset($result[$segment])) {
374
                return $default;
375
            }
376
377
            $result = $result[$segment];
378
        }
379
380
        return $result;
381
    }
382
383
    /**
384
     * @param array $config
385
     * @return array
386
     */
387
    protected function doFlatten($config)
388
    {
389
        $flattened = [];
390
391
        foreach ($config as $key => $value) {
392
            if (is_array($value)) {
393
                $tmp = $this->doFlatten($value);
394
395
                foreach ($tmp as $k => $v) {
396
                    $flattened[$key . '.' . $k] = $v;
397
                }
398
399
                continue;
400
            }
401
402
            $flattened[$key] = $value;
403
        }
404
405
        return $flattened;
406
    }
407
408
    /**
409
     * recursively merges the arrays
410
     * later keys overload earlier keys
411
     * numeric keys are appended
412
     *
413
     * @param array $base
414
     * @param array $subject
415
     *
416
     * @return array the result of the merge
417
     */
418
    protected function merge(array $base, array $subject): array
419
    {
420
        foreach ($subject as $k => $v) {
421
            if (is_numeric($k)) {
422
                $base[] = $v;
423
            } elseif (array_key_exists($k, $base) && (is_array($v) || is_array($base[$k]))) {
424
                $base[$k] = $this->merge((array) $base[$k], (array) $v);
425
            } else {
426
                $base[$k] = $v;
427
            }
428
        }
429
430
        return $base;
431
    }
432
433
    /**
434
     * @return array
435
     */
436
    public function getPlaceholders()
437
    {
438
        return $this->placeholders;
439
    }
440
441
    /**
442
     * @param array $placeholders
443
     * @return $this
444
     */
445
    public function setPlaceholders(array $placeholders)
446
    {
447
        $this->placeholders = $placeholders;
448
449
        return $this;
450
    }
451
452
    /**
453
     * @param $placeholder
454
     * @param $value
455
     */
456
    public function addPlaceholder($placeholder, $value)
457
    {
458
        $this->placeholders[$placeholder] = $value;
459
    }
460
}
461