Passed
Push — master ( f7b5a3...3719c9 )
by Alexis
02:00
created

ConfigurationLoader::export()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of the awurth/config package.
5
 *
6
 * (c) Alexis Wurth <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace AWurth\Config;
13
14
use AWurth\Config\Loader\JsonFileLoader;
15
use AWurth\Config\Loader\PhpFileLoader;
16
use AWurth\Config\Loader\YamlFileLoader;
17
use Symfony\Component\Config\ConfigCache;
18
use Symfony\Component\Config\FileLocator;
19
use Symfony\Component\Config\Loader\DelegatingLoader;
20
use Symfony\Component\Config\Loader\LoaderInterface;
21
use Symfony\Component\Config\Loader\LoaderResolver;
22
use Symfony\Component\Config\Resource\FileResource;
23
24
/**
25
 * Configuration Loader.
26
 *
27
 * @author Alexis Wurth <[email protected]>
28
 */
29
class ConfigurationLoader
30
{
31
    /**
32
     * @var array
33
     */
34
    protected $configurations;
35
36
    /**
37
     * @var LoaderInterface
38
     */
39
    protected $loader;
40
41
    /**
42
     * @var LoaderInterface[]
43
     */
44
    protected $loaders;
45
46
    /**
47
     * @var Options
48
     */
49
    protected $options;
50
51
    /**
52
     * @var array
53
     */
54
    protected $parameters;
55
56
    /**
57
     * @var FileResource[]
58
     */
59
    protected $resources;
60
61
    /**
62
     * Constructor.
63
     *
64
     * @param Options|array $options
65
     * @param array         $parameters
66
     */
67
    public function __construct($options = [], array $parameters = [])
68
    {
69
        $this->configurations = [];
70
        $this->resources = [];
71
        $this->parameters = $parameters;
72
73
        if ($options instanceof Options) {
74
            $this->options = $options;
75
        } elseif (is_array($options)) {
76
            $this->options = new Options($options);
77
        } else {
78
            $this->options = new Options();
79
        }
80
    }
81
82
    /**
83
     * Loads the configuration from a cache file if it exists, or parses a configuration file if not.
84
     *
85
     * @param string $file
86
     * @param string $cachePath
87
     * @param bool   $debug
88
     *
89
     * @return array
90
     */
91
    public function load($file, $cachePath = null, $debug = false)
92
    {
93
        if (null !== $cachePath) {
94
            $cache = new ConfigCache($cachePath, $debug);
95
            if (!$cache->isFresh()) {
96
                $configuration = $this->loadFile($file);
97
                $this->export($cache, $configuration);
98
99
                return $configuration;
100
            }
101
102
            return self::requireFile($cachePath);
103
        }
104
105
        return $this->loadFile($file);
106
    }
107
108
    /**
109
     * Loads the configuration from a file.
110
     *
111
     * @param string $file
112
     *
113
     * @return array
114
     */
115
    public function loadFile($file)
116
    {
117
        $this->initLoader();
118
119
        $this->parseFile($file);
120
121
        $configuration = $this->mergeConfiguration();
122
123 View Code Duplication
        if ($this->options->areParametersEnabled()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
124
            if (isset($configuration[$this->options->getParametersKey()])) {
125
                $this->mergeParameters($configuration[$this->options->getParametersKey()]);
126
            }
127
128
            $this->replacePlaceholders($configuration);
129
        }
130
131
        return $configuration;
132
    }
133
134
    /**
135
     * Exports the configuration to a cache file.
136
     *
137
     * @param ConfigCache $cache
138
     * @param array       $configuration
139
     */
140
    public function export(ConfigCache $cache, array $configuration)
141
    {
142
        $content = '<?php'.PHP_EOL.PHP_EOL.'return '.var_export($configuration, true).';'.PHP_EOL;
143
144
        $cache->write($content, $this->resources);
145
    }
146
147
    /**
148
     * Gets the file loaders.
149
     *
150
     * @return LoaderInterface[]
151
     */
152
    public function getLoaders()
153
    {
154
        return $this->loaders;
155
    }
156
157
    /**
158
     * Adds a file loader.
159
     *
160
     * @param LoaderInterface $loader
161
     *
162
     * @return self
163
     */
164
    public function addLoader(LoaderInterface $loader)
165
    {
166
        $this->loaders[] = $loader;
167
168
        return $this;
169
    }
170
171
    /**
172
     * Sets the file loaders.
173
     *
174
     * @param LoaderInterface[] $loaders
175
     */
176
    public function setLoaders(array $loaders)
177
    {
178
        $this->loaders = $loaders;
179
    }
180
181
    /**
182
     * Gets the parameters.
183
     *
184
     * @return array
185
     */
186
    public function getParameters()
187
    {
188
        return $this->parameters;
189
    }
190
191
    /**
192
     * Sets the parameters.
193
     *
194
     * @param array $parameters
195
     */
196
    public function setParameters(array $parameters)
197
    {
198
        $this->parameters = $parameters;
199
    }
200
201
    /**
202
     * Gets the options.
203
     *
204
     * @return Options
205
     */
206
    public function getOptions()
207
    {
208
        return $this->options;
209
    }
210
211
    /**
212
     * Sets the options.
213
     *
214
     * @param Options $options
215
     */
216
    public function setOptions(Options $options)
217
    {
218
        $this->options = $options;
219
    }
220
221
    /**
222
     * Initializes the file loader.
223
     */
224
    protected function initLoader()
225
    {
226
        if (null === $this->loader) {
227
            $locator = new FileLocator();
228
229
            $this->addLoader(new PhpFileLoader($locator));
230
            $this->addLoader(new YamlFileLoader($locator));
231
            $this->addLoader(new JsonFileLoader($locator));
232
233
            $loaderResolver = new LoaderResolver($this->loaders);
234
235
            $this->loader = new DelegatingLoader($loaderResolver);
236
        }
237
    }
238
239
    /**
240
     * Returns whether the file path is an absolute path.
241
     *
242
     * @param string $file
243
     *
244
     * @return bool
245
     */
246
    protected function isAbsolutePath($file)
247
    {
248
        if ('/' === $file[0] || '\\' === $file[0]
249
            || (strlen($file) > 3 && ctype_alpha($file[0])
250
                && ':' === $file[1]
251
                && ('\\' === $file[2] || '/' === $file[2])
252
            )
253
            || null !== parse_url($file, PHP_URL_SCHEME)
254
        ) {
255
            return true;
256
        }
257
258
        return false;
259
    }
260
261
    /**
262
     * Loads an imported file.
263
     *
264
     * @param string      $path
265
     * @param string      $originalFile
266
     * @param string|null $key
267
     */
268
    protected function loadImport($path, $originalFile, $key = null)
269
    {
270
        if ($this->options->areParametersEnabled()) {
271
            $this->replaceStringPlaceholders($path);
272
        }
273
274
        if ($this->isAbsolutePath($path) && file_exists($path)) {
275
            $this->parseFile($path, $key);
276
        } else {
277
            $this->parseFile(dirname($originalFile).DIRECTORY_SEPARATOR.$path, $key);
278
        }
279
    }
280
281
    /**
282
     * Loads file imports recursively.
283
     *
284
     * @param array       $values
285
     * @param string|null $originalFile
286
     */
287
    protected function loadImports(&$values, $originalFile = null)
288
    {
289
        if (isset($values[$this->options->getImportsKey()])) {
290
            $imports = $values[$this->options->getImportsKey()];
291
292
            if (is_string($imports)) {
293
                $this->loadImport($imports, $originalFile);
294
            } elseif (is_array($imports)) {
295
                foreach ($imports as $key => $file) {
296
                    $this->loadImport($file, $originalFile, is_string($key) ? $key : null);
297
                }
298
            }
299
        }
300
301
        unset($values[$this->options->getImportsKey()]);
302
    }
303
304
    /**
305
     * Merges all loaded configurations into a single array.
306
     *
307
     * @return array
308
     */
309
    protected function mergeConfiguration()
310
    {
311
        if (count($this->configurations) > 1) {
312
            return call_user_func_array('array_replace_recursive', $this->configurations);
313
        }
314
315
        return $this->configurations[0];
316
    }
317
318
    /**
319
     * Merges new parameters with existing ones.
320
     *
321
     * @param array $parameters
322
     */
323
    protected function mergeParameters(array $parameters)
324
    {
325
        $this->parameters = array_replace_recursive($this->parameters, $parameters);
326
    }
327
328
    /**
329
     * Parses a configuration file.
330
     *
331
     * @param string $file
332
     * @param string $key
333
     */
334
    protected function parseFile($file, $key = null)
335
    {
336
        $values = $this->loader->load($file);
337
338
        if (!empty($values)) {
339 View Code Duplication
            if ($this->options->areParametersEnabled() && isset($values[$this->options->getParametersKey()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
                $this->replacePlaceholders($values[$this->options->getParametersKey()]);
341
                $this->mergeParameters($values[$this->options->getParametersKey()]);
342
            }
343
344
            if ($this->options->areImportsEnabled()) {
345
                $this->loadImports($values, $file);
346
            }
347
348
            $this->configurations[] = null !== $key ? [$key => $values] : $values;
349
            $this->resources[] = new FileResource($file);
350
        }
351
    }
352
353
    /**
354
     * Parses the configuration and replaces placeholders with the corresponding parameters values.
355
     *
356
     * @param array $configuration
357
     */
358
    protected function replacePlaceholders(array &$configuration)
359
    {
360
        array_walk_recursive($configuration, [$this, 'replaceStringPlaceholders']);
361
    }
362
363
    /**
364
     * Replaces configuration placeholders with the corresponding parameters values.
365
     *
366
     * @param string $string
367
     */
368
    protected function replaceStringPlaceholders(&$string)
369
    {
370
        if (is_string($string)) {
371
            $string = preg_replace_callback('/%([0-9A-Za-z._-]+)%/', function ($matches) {
372
                return isset($this->parameters[$matches[1]]) ? $this->parameters[$matches[1]] : null;
373
            }, $string);
374
        }
375
    }
376
377
    /**
378
     * Includes a PHP file.
379
     *
380
     * @param string $file
381
     *
382
     * @return array
383
     */
384
    private static function requireFile($file)
385
    {
386
        return require $file;
387
    }
388
}
389