Completed
Push — master ( 383d0c...3523cb )
by Tom
04:14
created

ConfigurationLoader::loadSystemConfig()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
dl 0
loc 24
rs 8.5125
c 2
b 0
f 1
cc 6
eloc 15
nc 7
nop 1
1
<?php
2
3
namespace N98\Magento\Application;
4
5
use N98\Util\BinaryString;
6
use N98\Util\OperatingSystem;
7
use Symfony\Component\Console\Output\OutputInterface;
8
use Symfony\Component\Finder\Finder;
9
use Symfony\Component\Finder\SplFileInfo;
10
use Symfony\Component\Yaml\Yaml;
11
use N98\Util\ArrayFunctions;
12
13
/**
14
 * Config consists of several parts which are merged.
15
 * The configuration which is global (not Magento project specific) is loaded
16
 * during construction.
17
 *
18
 * As soon as the Magento folder is known, loadStageTwo should be called.
19
 *
20
 * The toArray method only works if the Magento folder specific configuration is already loaded.
21
 *
22
 * Class ConfigurationLoader
23
 * @package N98\Magento\Command
24
 */
25
class ConfigurationLoader
26
{
27
    /**
28
     * Config passed in the constructor
29
     *
30
     * @var array
31
     */
32
    protected $_initialConfig;
33
34
    /**
35
     * @var array
36
     */
37
    protected $_configArray = null;
38
39
    /**
40
     * Cache
41
     *
42
     * @var array
43
     */
44
    protected $_distConfig;
45
46
    /**
47
     * Cache
48
     *
49
     * @var array
50
     */
51
    protected $_pluginConfig;
52
53
    /**
54
     * Cache
55
     *
56
     * @var array
57
     */
58
    protected $_systemConfig;
59
60
    /**
61
     * Cache
62
     *
63
     * @var array
64
     */
65
    protected $_userConfig;
66
67
    /**
68
     * Cache
69
     *
70
     * @var array
71
     */
72
    protected $_projectConfig;
73
74
    /**
75
     * @var string
76
     */
77
    protected $_customConfigFilename = 'n98-magerun2.yaml';
78
79
    /**
80
     * @var bool
81
     */
82
    protected $_isPharMode = true;
83
84
    /**
85
     * @var OutputInterface
86
     */
87
    protected $_output;
88
89
    /**
90
     * Load config
91
     * If $magentoRootFolder is null, only non-project config is loaded
92
     *
93
     * @param array $config
94
     * @param bool $isPharMode
95
     * @param OutputInterface $output
96
     */
97
    public function __construct($config, $isPharMode, OutputInterface $output)
98
    {
99
        $this->_initialConfig = $config;
100
        $this->_isPharMode = $isPharMode;
101
        $this->_output = $output;
102
    }
103
104
    /**
105
     * @param bool $loadExternalConfig
106
     * @return array
107
     */
108
    public function getPartialConfig($loadExternalConfig = true)
109
    {
110
        $config = $this->_initialConfig;
111
        $config = $this->loadDistConfig($config);
112
        if ($loadExternalConfig) {
113
            $config = $this->loadSystemConfig($config);
114
            $config = $this->loadUserConfig($config);
115
        }
116
117
        return $config;
118
    }
119
120
    /**
121
     * @param string $magentoRootFolder
122
     * @param bool   $loadExternalConfig
123
     * @param string $magerunStopFileFolder
124
     */
125
    public function loadStageTwo($magentoRootFolder, $loadExternalConfig = true, $magerunStopFileFolder = '')
126
    {
127
        $config = $this->_initialConfig;
128
        $config = $this->loadDistConfig($config);
129
        if ($loadExternalConfig) {
130
            $config = $this->loadPluginConfig($config, $magentoRootFolder);
131
            $config = $this->loadSystemConfig($config);
132
            $config = $this->loadUserConfig($config, $magentoRootFolder);
133
            $config = $this->loadProjectConfig($magentoRootFolder, $magerunStopFileFolder, $config);
134
        }
135
        $this->_configArray = $config;
136
    }
137
138
    /**
139
     * @throws \ErrorException
140
     *
141
     * @return array
142
     */
143
    public function toArray()
144
    {
145
        if ($this->_configArray == null) {
146
            throw new \ErrorException('Configuration not yet fully loaded');
147
        }
148
149
        return $this->_configArray;
150
    }
151
152
    /**
153
     * @param array $initConfig
154
     *
155
     * @return array
156
     */
157
    protected function loadDistConfig($initConfig)
158
    {
159
        if ($this->_distConfig == null) {
160
            $this->_distConfig = Yaml::parse(__DIR__ . '/../../../../config.yaml');
0 ignored issues
show
Documentation Bug introduced by
It seems like \Symfony\Component\Yaml\.../../../../config.yaml') can also be of type string. However, the property $_distConfig 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...
161
        }
162
        if (OutputInterface::VERBOSITY_DEBUG <= $this->_output->getVerbosity()) {
163
            $this->_output->writeln('<debug>Load dist config</debug>');
164
        }
165
        $config = ArrayFunctions::mergeArrays($this->_distConfig, $initConfig);
0 ignored issues
show
Bug introduced by
It seems like $this->_distConfig can also be of type string; however, N98\Util\ArrayFunctions::mergeArrays() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
166
167
        return $config;
168
    }
169
170
    /**
171
     * Check if there is a global config file in /etc folder
172
     *
173
     * @param array $config
174
     *
175
     * @return array
176
     */
177
    public function loadSystemConfig($config)
178
    {
179
        if ($this->_systemConfig == null) {
180
            if (OperatingSystem::isWindows()) {
181
                $systemWideConfigFile = getenv('WINDIR') . DIRECTORY_SEPARATOR . $this->_customConfigFilename;
182
            } else {
183
                $systemWideConfigFile = '/etc/' . $this->_customConfigFilename;
184
            }
185
186
            if ($systemWideConfigFile && file_exists($systemWideConfigFile)) {
187
                if (OutputInterface::VERBOSITY_DEBUG <= $this->_output->getVerbosity()) {
188
                    $this->_output->writeln(
189
                        '<debug>Load system config <comment>' . $systemWideConfigFile . '</comment></debug>'
190
                    );
191
                }
192
                $this->_systemConfig = Yaml::parse($systemWideConfigFile);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Symfony\Component\Yaml\...($systemWideConfigFile) can also be of type string. However, the property $_systemConfig 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...
193
            } else {
194
                $this->_systemConfig = array();
195
            }
196
        }
197
198
        $config = ArrayFunctions::mergeArrays($config, $this->_systemConfig);
0 ignored issues
show
Bug introduced by
It seems like $this->_systemConfig can also be of type string; however, N98\Util\ArrayFunctions::mergeArrays() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
199
        return $config;
200
    }
201
202
    /**
203
     * Load config from all installed bundles
204
     *
205
     * @param array  $config
206
     * @param string $magentoRootFolder
207
     *
208
     * @return array
209
     */
210
    public function loadPluginConfig($config, $magentoRootFolder)
211
    {
212
        if ($this->_pluginConfig == null) {
213
            $this->_pluginConfig = array();
214
            $moduleBaseFolders = array();
215
            if (OperatingSystem::isWindows()) {
216
                $config['plugin']['folders'][] = getenv('WINDIR') . '/n98-magerun2/modules';
217
                $config['plugin']['folders'][] = OperatingSystem::getHomeDir() . '/n98-magerun2/modules';
218
            } else {
219
                $config['plugin']['folders'][] = OperatingSystem::getHomeDir() . '/.n98-magerun2/modules';
220
            }
221
            $config['plugin']['folders'][] = $magentoRootFolder . '/lib/n98-magerun2/modules';
222
            foreach ($config['plugin']['folders'] as $folder) {
223
                if (is_dir($folder)) {
224
                    $moduleBaseFolders[] = $folder;
225
                }
226
            }
227
228
            /**
229
             * Allow modules to be placed vendor folder if not in phar mode
230
             */
231
            if (!$this->_isPharMode) {
232
                if (is_dir($this->getVendorDir())) {
233
                    $finder = Finder::create();
234
                    $finder
235
                        ->files()
236
                        ->depth(2)
237
                        ->followLinks()
238
                        ->ignoreUnreadableDirs(true)
239
                        ->name($this->_customConfigFilename)
240
                        ->in($this->getVendorDir());
241
242
                    foreach ($finder as $file) { /* @var $file SplFileInfo */
243
                        $this->registerPluginConfigFile($magentoRootFolder, $file);
244
                    }
245
                }
246
            }
247
248
            if (count($moduleBaseFolders) > 0) {
249
                // Glob plugin folders
250
                $finder = Finder::create();
251
                $finder
252
                    ->files()
253
                    ->depth(1)
254
                    ->followLinks()
255
                    ->ignoreUnreadableDirs(true)
256
                    ->name($this->_customConfigFilename)
257
                    ->in($moduleBaseFolders);
258
259
                foreach ($finder as $file) { /* @var $file SplFileInfo */
260
                    $this->registerPluginConfigFile($magentoRootFolder, $file);
261
                }
262
            }
263
        }
264
265
        $config = ArrayFunctions::mergeArrays($config, $this->_pluginConfig);
266
267
        return $config;
268
    }
269
270
    /**
271
     * @param string      $rawConfig
272
     * @param string      $magentoRootFolder
273
     * @param SplFileInfo $file [optional]
0 ignored issues
show
Documentation introduced by
Should the type for parameter $file not be null|SplFileInfo?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
274
     *
275
     * @return string
276
     */
277
    protected function applyVariables($rawConfig, $magentoRootFolder, SplFileInfo $file = null)
278
    {
279
        $replace = array(
280
            '%module%' => $file ? $file->getPath() : '',
281
            '%root%'   => $magentoRootFolder,
282
        );
283
284
        return str_replace(array_keys($replace), $replace, $rawConfig);
285
    }
286
287
288
    /**
289
     * Check if there is a user config file. ~/.n98-magerun.yaml
290
     *
291
     * @param array  $config
292
     * @param string $magentoRootFolder [optional]
0 ignored issues
show
Documentation introduced by
Should the type for parameter $magentoRootFolder not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
293
     *
294
     * @return array
295
     */
296
    public function loadUserConfig($config, $magentoRootFolder = null)
297
    {
298
        if ($this->_userConfig == null) {
299
            $this->_userConfig = array();
300
            $homeDirectory =  OperatingSystem::getHomeDir();
301
            if (OperatingSystem::isWindows()) {
302
                $personalConfigFile = $homeDirectory . DIRECTORY_SEPARATOR . $this->_customConfigFilename;
303
            } else {
304
                $personalConfigFile = $homeDirectory . DIRECTORY_SEPARATOR . '.' . $this->_customConfigFilename;
305
            }
306
307 View Code Duplication
            if ($homeDirectory && file_exists($personalConfigFile)) {
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...
308
                $userConfig = $this->applyVariables(\file_get_contents($personalConfigFile), $magentoRootFolder, null);
309
                $this->_userConfig = Yaml::parse($userConfig);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Symfony\Component\Yaml\Yaml::parse($userConfig) can also be of type string. However, the property $_userConfig 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...
310
311
                return $config;
312
            }
313
        }
314
315
        $config = ArrayFunctions::mergeArrays($config, $this->_userConfig);
316
317
        return $config;
318
    }
319
320
    /**
321
     * MAGENTO_ROOT/app/etc/n98-magerun.yaml
322
     *
323
     * @param string $magentoRootFolder
324
     * @param string $magerunStopFileFolder
325
     * @param array $config
326
     *
327
     * @return array
328
     */
329
    public function loadProjectConfig($magentoRootFolder, $magerunStopFileFolder, $config)
330
    {
331
        if ($this->_projectConfig == null) {
332
            $this->_projectConfig = array();
333
334
            $projectConfigFile = $magentoRootFolder . DIRECTORY_SEPARATOR . 'app/etc/' . $this->_customConfigFilename;
335 View Code Duplication
            if ($projectConfigFile && file_exists($projectConfigFile)) {
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...
336
                $projectConfig = $this->applyVariables(
337
                    \file_get_contents($projectConfigFile), $magentoRootFolder, null
338
                );
339
                $this->_projectConfig = Yaml::parse($projectConfig);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Symfony\Component\Yaml\...::parse($projectConfig) can also be of type string. However, the property $_projectConfig 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...
340
            }
341
342
            $stopFileConfigFile = $magerunStopFileFolder . DIRECTORY_SEPARATOR . '.' . $this->_customConfigFilename;
343 View Code Duplication
            if (!empty($magerunStopFileFolder) && file_exists($stopFileConfigFile)) {
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...
344
                $projectConfig = $this->applyVariables(
345
                    \file_get_contents($stopFileConfigFile), $magentoRootFolder, null
346
                );
347
                $this->_projectConfig = ArrayFunctions::mergeArrays($this->_projectConfig, Yaml::parse($projectConfig));
0 ignored issues
show
Bug introduced by
It seems like $this->_projectConfig can also be of type string; however, N98\Util\ArrayFunctions::mergeArrays() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like \Symfony\Component\Yaml\...::parse($projectConfig) targeting Symfony\Component\Yaml\Yaml::parse() can also be of type string; however, N98\Util\ArrayFunctions::mergeArrays() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
348
            }
349
350
            $config = ArrayFunctions::mergeArrays($config, $this->_projectConfig);
0 ignored issues
show
Bug introduced by
It seems like $this->_projectConfig can also be of type string; however, N98\Util\ArrayFunctions::mergeArrays() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
351
        }
352
353
        return $config;
354
    }
355
356
    /**
357
     * Loads a plugin config file and merges it to plugin config
358
     *
359
     * @param string       $magentoRootFolder
360
     * @param SplFileInfo $file
361
     */
362
    protected function registerPluginConfigFile($magentoRootFolder, $file)
363
    {
364
        if (BinaryString::startsWith($file->getPathname(), 'vfs://')) {
365
            $path = $file->getPathname();
366
        } else {
367
            $path = $file->getRealPath();
368
369
            if ($path === "") {
370
                throw new \UnexpectedValueException(sprintf("Realpath for '%s' did return an empty string.", $file));
371
            }
372
373
            if ($path === false) {
374
                $this->_output->writeln(sprintf("<error>Plugin config file broken link '%s'</error>", $file));
375
                return;
376
            }
377
        }
378
379
        if (OutputInterface::VERBOSITY_DEBUG <= $this->_output->getVerbosity()) {
380
            $this->_output->writeln('<debug>Load plugin config <comment>' . $path . '</comment></debug>');
381
        }
382
383
        $localPluginConfig = \file_get_contents($path);
384
        $localPluginConfig = Yaml::parse($this->applyVariables($localPluginConfig, $magentoRootFolder, $file));
385
386
        $this->_pluginConfig = ArrayFunctions::mergeArrays($this->_pluginConfig, $localPluginConfig);
0 ignored issues
show
Bug introduced by
It seems like $localPluginConfig defined by \Symfony\Component\Yaml\...entoRootFolder, $file)) on line 384 can also be of type string; however, N98\Util\ArrayFunctions::mergeArrays() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
387
    }
388
389
    /**
390
     * @return string
391
     */
392
    public function getVendorDir()
393
    {
394
        /* old vendor folder to give backward compatibility */
395
        $vendorFolder = $this->getConfigurationLoaderDir() . '/../../../../vendor';
396
        if (is_dir($vendorFolder)) {
397
            return $vendorFolder;
398
        }
399
400
        /* correct vendor folder for composer installations */
401
        $vendorFolder = $this->getConfigurationLoaderDir() . '/../../../../../../../vendor';
402
        if (is_dir($vendorFolder)) {
403
            return $vendorFolder;
404
        }
405
406
        return '';
407
    }
408
409
    /**
410
     * @return string
411
     */
412
    public function getConfigurationLoaderDir()
413
    {
414
        return __DIR__;
415
    }
416
}
417