Completed
Push — master ( 7616e7...c05391 )
by Andre
01:44
created

Space::init()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 3
nop 0
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
            if (is_array($value)) {
149
                $config[$key] = $this->replacePlaceholders($value);
150
                continue;
151
            }
152
153
            if (is_string($value)) {
154
                $config[$key] = strtr($value, $this->placeholders);
155
            }
156
        }
157
158
        return $config;
159
    }
160
161
    /**
162
     * @return int
163
     */
164
    protected function getTimestamp()
165
    {
166
        $timestamp = 0;
167
        foreach ($this->getReadableSources() as $path) {
168
            $timestamp = max($timestamp, filemtime($path));
169
            $timestamp = max($timestamp, filemtime(dirname($path)));
170
        }
171
172
        return $timestamp;
173
    }
174
    
175
    /**
176
     * get the actually readable source paths
177
     *
178
     * @return array
179
     */
180
    protected function getReadableSources()
181
    {
182
        $paths = [];
183
184
        foreach ($this->getPaths() as $path) {
185
            if (is_readable($path)) {
186
                $paths[] = $path;
187
            }
188
        }
189
190
        return $paths;
191
    }
192
193
    /**
194
     * flattens the config array for the given environment
195
     *
196
     * @param array $config the raw config array
197
     * @return array the flattened config array
198
     */
199
    protected function flattenSections(array $config)
200
    {
201
        $merged = [];
202
203
        $sections = $this->getSections();
204
205
        if (empty($sections)) {
206
            throw new PreconditionException('No sections have been configured.');
207
        }
208
209
        foreach (array_unique($this->getSections()) as $section) {
210
            $section = strtolower($section);
211
212
            if (!isset($config[$section])) {
213
                continue;
214
            }
215
216
            $merged = $this->merge($merged, $config[$section]);
217
        }
218
219
        return $merged;
220
    }
221
    
222
    /**
223
     * get the config cache handler
224
     *
225
     * @return Cache
226
     */
227
    public function getCache()
228
    {
229
        return $this->cache;
230
    }
231
232
    /**
233
     * set the config cache handler
234
     *
235
     * @param Cache $cache
236
     * @return $this
237
     */
238
    public function setCache(Cache $cache)
239
    {
240
        $this->cache = $cache;
241
242
        return $this;
243
    }
244
245
    /**
246
     * get the environment
247
     *
248
     * @return array
249
     */
250
    public function getSections()
251
    {
252
        return $this->sections;
253
    }
254
255
    /**
256
     * set the sections to use
257
     *
258
     * @param array $sections
259
     * @return $this
260
     */
261
    public function setSections(array $sections)
262
    {
263
        $this->sections = $sections;
264
265
        return $this;
266
    }
267
268
    /**
269
     * @param string $section
270
     * @return $this
271
     */
272
    public function addSection(string $section)
273
    {
274
        if (!in_array($section, $this->sections)) {
275
            $this->sections[] = $section;
276
        }
277
278
        return $this;
279
    }
280
281
    /**
282
     * get the parser
283
     *
284
     * @return Parser
285
     */
286
    public function getParser()
287
    {
288
        if (null === $this->parser) {
289
            $this->parser = new Parser();
290
        }
291
292
        return $this->parser;
293
    }
294
295
    /**
296
     * set the parser
297
     *
298
     * @param AbstractParser $parser
299
     * @return $this
300
     */
301
    public function setParser(AbstractParser $parser)
302
    {
303
        $this->parser = $parser;
304
305
        return $this;
306
    }
307
308
    /**
309
     * get configuration file paths
310
     *
311
     * @return array an array of configuration file paths
312
     */
313
    public function getPaths(): array
314
    {
315
        return $this->paths;
316
    }
317
318
    /**
319
     * @param array $paths
320
     * @return Space
321
     */
322
    public function setPaths(array $paths): Space
323
    {
324
        $this->paths = $paths;
325
326
        return $this;
327
    }
328
329
    /**
330
     * @param string $path
331
     * @return Space
332
     */
333
    public function addPath($path): Space
334
    {
335
        if (!in_array($path, $this->paths)) {
336
            $this->paths[] = $path;
337
        }
338
339
        return $this;
340
    }
341
342
    /**
343
     * get the path to the cache config file for the current environment
344
     *
345
     * @return string the file path
346
     */
347
    protected function getCacheKey()
348
    {
349
        return strtolower(md5(sprintf('%s_%s', implode('::', $this->getPaths()), implode('::', $this->getSections()))));
350
    }
351
352
    /**
353
     * @param string $path
354
     * @param mixed|null $default
355
     * @return array|mixed|null
356
     */
357
    protected function resolve($path, $default = null)
358
    {
359
        $segments = explode('.', $path);
360
        $result = $this->config;
361
362
        foreach ($segments as $segment) {
363
            if (!isset($result[$segment])) {
364
                return $default;
365
            }
366
367
            $result = $result[$segment];
368
        }
369
370
        return $result;
371
    }
372
373
    /**
374
     * @param array $config
375
     * @return array
376
     */
377
    protected function doFlatten($config)
378
    {
379
        $flattened = [];
380
381
        foreach ($config as $key => $value) {
382
            if (is_array($value)) {
383
                $tmp = $this->doFlatten($value);
384
385
                foreach ($tmp as $k => $v) {
386
                    $flattened[$key . '.' . $k] = $v;
387
                }
388
389
                continue;
390
            }
391
392
            $flattened[$key] = $value;
393
        }
394
395
        return $flattened;
396
    }
397
398
    /**
399
     * recursively merges the arrays
400
     * later keys overload earlier keys
401
     * numeric keys are appended
402
     *
403
     * @param array $base
404
     * @param array $subject
405
     *
406
     * @return array the result of the merge
407
     */
408
    protected function merge(array $base, array $subject): array
409
    {
410
        foreach ($subject as $k => $v) {
411
            if (is_numeric($k)) {
412
                $base[] = $v;
413
            } elseif (!array_key_exists($k, $base)) {
414
                $base[$k] = $v;
415
            } elseif (is_array($v) || is_array($base[$k])) {
416
                $base[$k] = $this->merge((array) $base[$k], (array) $v);
417
            } else {
418
                $base[$k] = $v;
419
            }
420
        }
421
422
        return $base;
423
    }
424
425
    /**
426
     * @return array
427
     */
428
    public function getPlaceholders()
429
    {
430
        return $this->placeholders;
431
    }
432
433
    /**
434
     * @param array $placeholders
435
     * @return $this
436
     */
437
    public function setPlaceholders(array $placeholders)
438
    {
439
        $this->placeholders = $placeholders;
440
441
        return $this;
442
    }
443
444
    /**
445
     * @param $placeholder
446
     * @param $value
447
     */
448
    public function addPlaceholder($placeholder, $value)
449
    {
450
        $this->placeholders[$placeholder] = $value;
451
    }
452
}
453