Completed
Push — master ( 5e3345...9f93bc )
by Andre
02:09
created

Space::flattenSections()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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