Completed
Pull Request — master (#42)
by Vladimir
02:27
created

Configuration::handleImports()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 42
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 15.1593

Importance

Changes 0
Metric Value
cc 5
eloc 21
nc 6
nop 0
dl 0
loc 42
ccs 7
cts 27
cp 0.2592
crap 15.1593
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright 2017 Vladimir Jimenez
5
 * @license   https://github.com/allejo/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx;
9
10
use allejo\stakx\System\Filesystem;
11
use allejo\stakx\Utilities\ArrayUtilities;
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerInterface;
14
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
15
use Symfony\Component\Yaml\Exception\ParseException;
16
use Symfony\Component\Yaml\Yaml;
17
18
class Configuration implements LoggerAwareInterface
19
{
20
    const DEFAULT_NAME = '_config.yml';
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
21
    const IMPORT_KEYWORD = 'import';
22
23
    /**
24
     * A list of regular expressions or files directly related to stakx websites that should not be copied over to the
25
     * compiled website as an asset.
26
     *
27
     * @var array
28
     */
29
    public static $stakxSourceFiles = array('/^_(?!themes).*/', '/.twig$/');
30
31
    /**
32
     * An array representation of the main Yaml configuration.
33
     *
34
     * @var array
35
     */
36
    private $configuration;
37
38
    /**
39
     * @var string
40
     */
41
    private $configFile;
42
43
    /**
44
     * @var LoggerInterface
45
     */
46
    private $output;
47
48
    /**
49
     * @var Filesystem
50
     */
51
    private $fs;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
52
53
    /**
54
     * Configuration constructor.
55
     */
56 27
    public function __construct()
57
    {
58 27
        $this->configuration = array();
59 27
        $this->fs = new Filesystem();
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 12 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
60 27
    }
61
62
    /**
63
     * Parse a given configuration file and configure this Configuration instance.
64
     *
65
     * This function should be called with 'null' passed when "configuration-less" mode is used
66
     *
67
     * @param string|null $configFile     The path to the configuration file. If null, the default configuration will be
68
     *                                    used
69
     * @param bool        $ignoreDefaults When set to true, the default configuration will not be merged
70
     */
71 27
    public function parseConfiguration($configFile = null, $ignoreDefaults = false)
72
    {
73 27
        $this->configFile = $configFile;
74
75 27
        if ($this->fs->exists($configFile))
76 27
        {
77 14
            $configContents = $this->fs->safeReadFile($this->configFile);
78
79
            try
80
            {
81 14
                $this->configuration = Yaml::parse($configContents);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Symfony\Component\Yaml\...:parse($configContents) can also be of type string or object<stdClass>. However, the property $configuration is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
82
            }
83 14
            catch (ParseException $e)
84
            {
85
                $this->output->error('Parsing the configuration failed: {message}', array(
86
                    'message' => $e->getMessage(),
87
                ));
88
                $this->output->error('Using default configuration...');
89
            }
90
91 14
            $this->handleImports();
92 14
        }
93
94 27
        !$ignoreDefaults && $this->mergeDefaultConfiguration();
95
96 27
        $this->handleDeprecations();
97 27
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102 14
    public function setLogger(LoggerInterface $logger)
103
    {
104 14
        $this->output = $logger;
105 14
    }
106
107 14
    public function isDebug()
108
    {
109 14
        return $this->returnConfigOption('debug', false);
110
    }
111
112
    /**
113
     * @TODO 1.0.0 Remove support for 'base' in next major release; it has been replaced by 'baseurl'
114
     *
115
     * @return mixed|null
116
     */
117 2
    public function getBaseUrl()
118
    {
119 2
        $base = $this->returnConfigOption('base');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
120 2
        $baseUrl = $this->returnConfigOption('baseurl');
121
122 2
        if (is_null($base) || (!empty($baseUrl)))
123 2
        {
124 2
            return $baseUrl;
125
        }
126
127
        return $base;
128
    }
129
130
    /**
131
     * @return string[]
132
     */
133 1
    public function getDataFolders()
134
    {
135 1
        return $this->returnConfigOption('data');
136
    }
137
138
    /**
139
     * @return string[]
140
     */
141 1
    public function getDataSets()
142
    {
143 1
        return $this->returnConfigOption('datasets');
144
    }
145
146 1
    public function getIncludes()
147
    {
148 1
        return $this->returnConfigOption('include', array());
149
    }
150
151 1
    public function getExcludes()
152
    {
153 1
        return $this->returnConfigOption('exclude', array());
154
    }
155
156 14
    public function getTheme()
157
    {
158 14
        return $this->returnConfigOption('theme');
159
    }
160
161 1
    public function getConfiguration()
162
    {
163 1
        return $this->configuration;
164
    }
165
166 1
    public function getPageViewFolders()
167
    {
168 1
        return $this->returnConfigOption('pageviews');
169
    }
170
171 2
    public function getTargetFolder()
172
    {
173 2
        return $this->returnConfigOption('target');
174
    }
175
176 1
    public function getCollectionsFolders()
177
    {
178 1
        return $this->returnConfigOption('collections');
179
    }
180
181 14
    public function getTwigAutoescape()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
182
    {
183 14
        return $this->configuration['twig']['autoescape'];
184
    }
185
186
    public function getRedirectTemplate()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
187
    {
188
        return $this->configuration['templates']['redirect'];
189
    }
190
191
    /**
192
     * Return the specified configuration option if available, otherwise return the default.
193
     *
194
     * @param string     $name    The configuration option to lookup
195
     * @param mixed|null $default The default value returned if the configuration option isn't found
196
     *
197
     * @return mixed|null
198
     */
199 27
    private function returnConfigOption($name, $default = null)
200
    {
201 27
        return isset($this->configuration[$name]) ? $this->configuration[$name] : $default;
202
    }
203
204 27
    private function mergeDefaultConfiguration()
205
    {
206
        $defaultConfig = array(
207 27
            'baseurl'   => '',
208 27
            'target'    => '_site',
209
            'twig'      => array(
210 27
                'autoescape' => false,
211 27
            ),
212
            'include'   => array(
213 27
                '.htaccess',
214 27
            ),
215
            'exclude'   => array(
216 27
                'node_modules/',
217 27
                'stakx-theme.yml',
218 27
                self::DEFAULT_NAME,
219 27
            ),
220
            'templates' => array(
221 27
                'redirect' => false,
222 27
            ),
223 27
        );
224
225 27
        $this->configuration = ArrayUtilities::array_merge_defaults($defaultConfig, $this->configuration, 'name');
226 27
    }
227
228 27
    private function handleDeprecations()
229
    {
230
        // @TODO 1.0.0 handle 'base' deprecation in _config.yml
231 27
        $base = $this->returnConfigOption('base');
232
233 27
        if (!is_null($base))
234 27
        {
235
            $this->output->warning("The 'base' configuration option has been replaced by 'baseurl' and will be removed in in version 1.0.0.");
236
        }
237 27
    }
238
239 14
    private function handleImports()
240
    {
241 14
        if (!isset($this->configuration[self::IMPORT_KEYWORD]))
242 14
        {
243 14
            $this->output->debug('{file}: does not import any other files', array(
244 14
                'file' => $this->configFile,
245 14
            ));
246
247 14
            return;
248
        }
249
250
        $thisFile = $this->fs->getRelativePath($this->configFile);
251
        $imports = $this->configuration[self::IMPORT_KEYWORD];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
252
253
        foreach ($imports as $import)
254
        {
255
            if (!$this->configImportIsValid($import))
256
            {
257
                continue;
258
            }
259
260
            $this->output->debug('{file}: imports additional file: {import}', array(
261
                'file' => $thisFile,
262
                'import' => $import,
263
            ));
264
265
            try
266
            {
267
                $importedConfig = $this->parseOther($import)->getConfiguration();
268
                $this->mergeImports($importedConfig);
269
            }
270
            catch (FileNotFoundException $e)
271
            {
272
                $this->output->warning('{file}: could not import file: {import}', array(
273
                    'file' => $thisFile,
274
                    'import' => $import,
275
                ));
276
            }
277
        }
278
279
        unset($this->configuration[self::IMPORT_KEYWORD]);
280
    }
281
282
    /**
283
     * Check whether a given file path is a valid import
284
     *
285
     * @param  string $filePath
286
     *
287
     * @return bool
288
     */
289
    private function configImportIsValid($filePath)
0 ignored issues
show
Coding Style introduced by
function configImportIsValid() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
290
    {
291
        $thisFile = $this->fs->getRelativePath($this->configFile);
292
        $errorMsg = '';
293
294
        if ($this->fs->isDir($filePath))
295
        {
296
            $errorMsg = 'a directory';
297
        }
298
        elseif ($this->fs->isSymlink($filePath))
299
        {
300
            $errorMsg = 'a symbolically linked file';
301
        }
302
        elseif ($this->fs->absolutePath($this->configFile) == $this->fs->absolutePath($filePath))
303
        {
304
            $errorMsg = 'a config recursively';
305
        }
306
        elseif (($ext = $this->fs->getExtension($filePath)) != 'yml' && $ext != 'yaml')
307
        {
308
            $errorMsg = 'a non-YAML configuration';
309
        }
310
311
        if (!($noErrors = empty($errorMsg)))
312
        {
313
            $this->output->error("{file}: you can't import {message}: {import}", array(
314
                'file' => $thisFile,
315
                'message' => $errorMsg,
316
                'import' => $filePath
317
            ));
318
        }
319
320
        return $noErrors;
321
    }
322
323
    /**
324
     * Import a given configuration to be merged with the parent configuration
325
     *
326
     * @param  string $filePath
327
     *
328
     * @return Configuration
329
     */
330
    private function parseOther($filePath)
331
    {
332
        $config = new self();
333
        $config->setLogger($this->output);
334
        $config->parseConfiguration($filePath, true);
335
336
        return $config;
337
    }
338
339
    /**
340
     * Merge the given array with existing configuration
341
     *
342
     * @param array $importedConfig
343
     */
344
    private function mergeImports(array $importedConfig)
345
    {
346
        $arraySplit = ArrayUtilities::associative_array_split(self::IMPORT_KEYWORD, $this->configuration, false);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
347
        $beforeImport = ArrayUtilities::array_merge_defaults($arraySplit[0], $importedConfig, 'name');
348
        $result = ArrayUtilities::array_merge_defaults($beforeImport, $arraySplit[1], 'name');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
349
350
        $this->configuration = $result;
351
    }
352
}
353