Completed
Push — master ( 3523cb...ac48e3 )
by Tom
9s
created

ConfigurationLoader   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 390
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 9
Bugs 5 Features 2
Metric Value
wmc 44
c 9
b 5
f 2
lcom 1
cbo 9
dl 0
loc 390
rs 8.3396

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getPartialConfig() 0 11 2
A loadStageTwo() 0 12 2
A toArray() 0 8 2
A loadDistConfig() 0 12 2
B loadSystemConfig() 0 20 5
C loadPluginConfig() 0 62 10
A applyVariables() 0 9 2
A loadUserConfig() 0 14 3
A loadProjectConfig() 0 20 4
B registerPluginConfigFile() 0 23 4
A getVendorDir() 0 16 3
A getConfigurationLoaderDir() 0 4 1
A logDebug() 0 6 2
A log() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ConfigurationLoader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ConfigurationLoader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace N98\Magento\Application;
4
5
use N98\Util\ArrayFunctions;
6
use N98\Util\BinaryString;
7
use N98\Util\OperatingSystem;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use Symfony\Component\Finder\Finder;
10
use Symfony\Component\Finder\SplFileInfo;
11
use Symfony\Component\Yaml\Yaml;
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(array $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(array $initConfig)
158
    {
159
        if ($this->_distConfig == null) {
160
            $distConfigFilePath = __DIR__ . '/../../../../config.yaml';
161
            $this->_distConfig = ConfigFile::createFromFile($distConfigFilePath)->toArray();
162
        }
163
        $this->logDebug('Load dist config');
164
165
        $config = ArrayFunctions::mergeArrays($this->_distConfig, $initConfig);
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(array $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
                $this->logDebug('Load system config <comment>' . $systemWideConfigFile . '</comment>');
188
                $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...
189
            } else {
190
                $this->_systemConfig = array();
191
            }
192
        }
193
194
        $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...
195
        return $config;
196
    }
197
198
    /**
199
     * Load config from all installed bundles
200
     *
201
     * @param array $config
202
     * @param string $magentoRootFolder
203
     *
204
     * @return array
205
     */
206
    public function loadPluginConfig(array $config, $magentoRootFolder)
207
    {
208
        if ($this->_pluginConfig == null) {
209
            $this->_pluginConfig = array();
210
            $moduleBaseFolders = array();
211
            $customFilename = $this->_customConfigFilename;
212
            if (OperatingSystem::isWindows()) {
213
                $config['plugin']['folders'][] = getenv('WINDIR') . '/' . $customFilename . '/modules';
214
                $config['plugin']['folders'][] = OperatingSystem::getHomeDir() . '/' . $customFilename . '/modules';
215
            } else {
216
                $config['plugin']['folders'][] = OperatingSystem::getHomeDir() . '/.' . $customFilename . '/modules';
217
            }
218
            $config['plugin']['folders'][] = $magentoRootFolder . '/lib/' . $customFilename . '/modules';
219
            foreach ($config['plugin']['folders'] as $folder) {
220
                if (is_dir($folder)) {
221
                    $moduleBaseFolders[] = $folder;
222
                }
223
            }
224
225
            /**
226
             * Allow modules to be placed vendor folder if not in phar mode
227
             */
228
            if (!$this->_isPharMode) {
229
                if (is_dir($this->getVendorDir())) {
230
                    $finder = Finder::create();
231
                    $finder
232
                        ->files()
233
                        ->depth(2)
234
                        ->followLinks()
235
                        ->ignoreUnreadableDirs(true)
236
                        ->name($customFilename)
237
                        ->in($this->getVendorDir());
238
239
                    foreach ($finder as $file) {
240
                        /* @var $file SplFileInfo */
241
                        $this->registerPluginConfigFile($magentoRootFolder, $file);
242
                    }
243
                }
244
            }
245
246
            if (count($moduleBaseFolders) > 0) {
247
                // Glob plugin folders
248
                $finder = Finder::create();
249
                $finder
250
                    ->files()
251
                    ->depth(1)
252
                    ->followLinks()
253
                    ->ignoreUnreadableDirs(true)
254
                    ->name($customFilename)
255
                    ->in($moduleBaseFolders);
256
257
                foreach ($finder as $file) {
258
                    /* @var $file SplFileInfo */
259
                    $this->registerPluginConfigFile($magentoRootFolder, $file);
260
                }
261
            }
262
        }
263
264
        $config = ArrayFunctions::mergeArrays($config, $this->_pluginConfig);
265
266
        return $config;
267
    }
268
269
    /**
270
     * @param string $rawConfig
271
     * @param string $magentoRootFolder
272
     * @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...
273
     *
274
     * @return string
275
     */
276
    protected function applyVariables($rawConfig, $magentoRootFolder, SplFileInfo $file = null)
277
    {
278
        $replace = array(
279
            '%module%' => $file ? $file->getPath() : '',
280
            '%root%' => $magentoRootFolder,
281
        );
282
283
        return str_replace(array_keys($replace), $replace, $rawConfig);
284
    }
285
286
    /**
287
     * Check if there is a user config file. ~/.n98-magerun.yaml
288
     *
289
     * @param array $config
290
     * @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...
291
     *
292
     * @return array
293
     */
294
    public function loadUserConfig(array $config, $magentoRootFolder = null)
295
    {
296
        if (null === $this->_userConfig) {
297
            $this->_userConfig = array();
298
            $locator = new ConfigLocator($this->_customConfigFilename, $magentoRootFolder);
299
            if ($userConfigFile = $locator->getUserConfigFile()) {
300
                $this->_userConfig = $userConfigFile->toArray();
301
            }
302
        }
303
304
        $config = ArrayFunctions::mergeArrays($config, $this->_userConfig);
305
306
        return $config;
307
    }
308
309
    /**
310
     * MAGENTO_ROOT/app/etc/n98-magerun.yaml
311
     *
312
     * @param string $magentoRootFolder
313
     * @param string $magerunStopFileFolder
314
     * @param array $config
315
     *
316
     * @return array
317
     */
318
    public function loadProjectConfig($magentoRootFolder, $magerunStopFileFolder, array $config)
319
    {
320
        if (null !== $this->_projectConfig) {
321
            return ArrayFunctions::mergeArrays($config, $this->_projectConfig);
322
        }
323
324
        $this->_projectConfig = array();
325
326
        $locator = new ConfigLocator($this->_customConfigFilename, $magentoRootFolder);
327
328
        if ($projectConfigFile = $locator->getProjectConfigFile()) {
329
            $this->_projectConfig = $projectConfigFile->toArray();
330
        }
331
332
        if ($stopFileConfigFile = $locator->getStopFileConfigFile($magerunStopFileFolder)) {
333
            $this->_projectConfig = $stopFileConfigFile->mergeArray($this->_projectConfig);
334
        }
335
336
        return ArrayFunctions::mergeArrays($config, $this->_projectConfig);
337
    }
338
339
    /**
340
     * Loads a plugin config file and merges it to plugin config
341
     *
342
     * @param string $magentoRootFolder
343
     * @param SplFileInfo $file
344
     */
345
    protected function registerPluginConfigFile($magentoRootFolder, $file)
346
    {
347
        if (BinaryString::startsWith($file->getPathname(), 'vfs://')) {
348
            $path = $file->getPathname();
349
        } else {
350
            $path = $file->getRealPath();
351
352
            if ($path === "") {
353
                throw new \UnexpectedValueException(sprintf("Realpath for '%s' did return an empty string.", $file));
354
            }
355
356
            if ($path === false) {
357
                $this->log(sprintf("<error>Plugin config file broken link '%s'</error>", $file));
358
                return;
359
            }
360
        }
361
362
        $this->logDebug('Load plugin config <comment>' . $path . '</comment>');
363
364
        $localPluginConfigFile = ConfigFile::createFromFile($path);
365
        $localPluginConfigFile->applyVariables($magentoRootFolder, $file);
366
        $this->_pluginConfig = $localPluginConfigFile->mergeArray($this->_pluginConfig);
367
    }
368
369
    /**
370
     * @return string
371
     */
372
    public function getVendorDir()
373
    {
374
        /* old vendor folder to give backward compatibility */
375
        $vendorFolder = $this->getConfigurationLoaderDir() . '/../../../../vendor';
376
        if (is_dir($vendorFolder)) {
377
            return $vendorFolder;
378
        }
379
380
        /* correct vendor folder for composer installations */
381
        $vendorFolder = $this->getConfigurationLoaderDir() . '/../../../../../../../vendor';
382
        if (is_dir($vendorFolder)) {
383
            return $vendorFolder;
384
        }
385
386
        return '';
387
    }
388
389
    /**
390
     * @return string
391
     */
392
    public function getConfigurationLoaderDir()
393
    {
394
        return __DIR__;
395
    }
396
397
    /**
398
     * @param string $message
399
     */
400
    private function logDebug($message)
401
    {
402
        if (OutputInterface::VERBOSITY_DEBUG <= $this->_output->getVerbosity()) {
403
            $this->log('<debug>' . $message . '</debug>');
404
        }
405
    }
406
407
    /**
408
     * @param string $message
409
     */
410
    private function log($message)
411
    {
412
        $this->_output->writeln($message);
413
    }
414
}
415