Completed
Push — develop ( 6f71dd...40edc3 )
by Tom
11s
created

ConfigurationLoader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 3
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
 *
24
 * @package N98\Magento\Command
25
 */
26
class ConfigurationLoader
27
{
28
    /**
29
     * Config passed in the constructor
30
     *
31
     * @var array
32
     */
33
    protected $_initialConfig;
34
35
    /**
36
     * @var array
37
     */
38
    protected $_configArray = null;
39
40
    /**
41
     * Cache
42
     *
43
     * @var array
44
     */
45
    protected $_distConfig;
46
47
    /**
48
     * Cache
49
     *
50
     * @var array
51
     */
52
    protected $_pluginConfig;
53
54
    /**
55
     * Cache
56
     *
57
     * @var array
58
     */
59
    protected $_systemConfig;
60
61
    /**
62
     * Cache
63
     *
64
     * @var array
65
     */
66
    protected $_userConfig;
67
68
    /**
69
     * Cache
70
     *
71
     * @var array
72
     */
73
    protected $_projectConfig;
74
75
    /**
76
     * @var string
77
     */
78
    protected $_customConfigFilename = 'n98-magerun.yaml';
79
80
    /**
81
     * @var bool
82
     */
83
    protected $_isPharMode = true;
84
85
    /**
86
     * @var OutputInterface
87
     */
88
    protected $_output;
89
90
    /**
91
     * Load config
92
     * If $magentoRootFolder is null, only non-project config is loaded
93
     *
94
     * @param array $config
95
     * @param bool $isPharMode
96
     * @param OutputInterface $output
97
     */
98
    public function __construct($config, $isPharMode, OutputInterface $output)
99
    {
100
        $this->_initialConfig = $config;
101
        $this->_isPharMode = $isPharMode;
102
        $this->_output = $output;
103
    }
104
105
    /**
106
     * @param bool $loadExternalConfig
107
     * @return array
108
     */
109
    public function getPartialConfig($loadExternalConfig = true)
110
    {
111
        $config = $this->_initialConfig;
112
        $config = $this->loadDistConfig($config);
113
        if ($loadExternalConfig) {
114
            $config = $this->loadSystemConfig($config);
115
            $config = $this->loadUserConfig($config);
116
        }
117
118
        return $config;
119
    }
120
121
    /**
122
     * @param string $magentoRootFolder
123
     * @param bool $loadExternalConfig
124
     * @param string $magerunStopFileFolder
125
     */
126
    public function loadStageTwo($magentoRootFolder, $loadExternalConfig = true, $magerunStopFileFolder = '')
127
    {
128
        $config = $this->_initialConfig;
129
        $config = $this->loadDistConfig($config);
130
        if ($loadExternalConfig) {
131
            $config = $this->loadPluginConfig($config, $magentoRootFolder);
132
            $config = $this->loadSystemConfig($config);
133
            $config = $this->loadUserConfig($config, $magentoRootFolder);
134
            $config = $this->loadProjectConfig($magentoRootFolder, $magerunStopFileFolder, $config);
135
        }
136
        $this->_configArray = $config;
137
    }
138
139
    /**
140
     * @throws \ErrorException
141
     *
142
     * @return array
143
     */
144
    public function toArray()
145
    {
146
        if ($this->_configArray == null) {
147
            throw new \ErrorException('Configuration not yet fully loaded');
148
        }
149
150
        return $this->_configArray;
151
    }
152
153
    /**
154
     * @param array $initConfig
155
     *
156
     * @return array
157
     */
158
    protected function loadDistConfig($initConfig)
159
    {
160
        if ($this->_distConfig == null) {
161
            $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...
162
        }
163
        if (OutputInterface::VERBOSITY_DEBUG <= $this->_output->getVerbosity()) {
164
            $this->_output->writeln('<debug>Load dist config</debug>');
165
        }
166
        $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...
167
168
        return $config;
169
    }
170
171
    /**
172
     * Check if there is a global config file in /etc folder
173
     *
174
     * @param array $config
175
     *
176
     * @return array
177
     */
178
    public function loadSystemConfig($config)
179
    {
180
        if ($this->_systemConfig == null) {
181
            if (OperatingSystem::isWindows()) {
182
                $systemWideConfigFile = getenv('WINDIR') . DIRECTORY_SEPARATOR . $this->_customConfigFilename;
183
            } else {
184
                $systemWideConfigFile = '/etc/' . $this->_customConfigFilename;
185
            }
186
187
            if ($systemWideConfigFile && file_exists($systemWideConfigFile)) {
188
                if (OutputInterface::VERBOSITY_DEBUG <= $this->_output->getVerbosity()) {
189
                    $this->_output->writeln(
190
                        '<debug>Load system config <comment>' . $systemWideConfigFile . '</comment></debug>'
191
                    );
192
                }
193
                $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...
194
            } else {
195
                $this->_systemConfig = array();
196
            }
197
        }
198
199
        $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...
200
201
        return $config;
202
    }
203
204
    /**
205
     * Load config from all installed bundles
206
     *
207
     * @param array $config
208
     * @param string $magentoRootFolder
209
     *
210
     * @return array
211
     */
212
    public function loadPluginConfig($config, $magentoRootFolder)
213
    {
214
        if ($this->_pluginConfig == null) {
215
            $this->_pluginConfig = array();
216
            $moduleBaseFolders = array();
217
            if (OperatingSystem::isWindows()) {
218
                $config['plugin']['folders'][] = getenv('WINDIR') . '/n98-magerun2/modules';
219
                $config['plugin']['folders'][] = OperatingSystem::getHomeDir() . '/n98-magerun2/modules';
220
            } else {
221
                $config['plugin']['folders'][] = OperatingSystem::getHomeDir() . '/.n98-magerun2/modules';
222
            }
223
            $config['plugin']['folders'][] = $magentoRootFolder . '/lib/n98-magerun2/modules';
224
            foreach ($config['plugin']['folders'] as $folder) {
225
                if (is_dir($folder)) {
226
                    $moduleBaseFolders[] = $folder;
227
                }
228
            }
229
230
            /**
231
             * Allow modules to be placed vendor folder if not in phar mode
232
             */
233
            if (!$this->_isPharMode) {
234
                if (is_dir($this->getVendorDir())) {
235
                    $finder = Finder::create();
236
                    $finder
237
                        ->files()
238
                        ->depth(2)
239
                        ->followLinks()
240
                        ->ignoreUnreadableDirs(true)
241
                        ->name($this->_customConfigFilename)
242
                        ->in($this->getVendorDir());
243
244
                    foreach ($finder as $file) {
245
                        /* @var $file \Symfony\Component\Finder\SplFileInfo */
246
                        $this->registerPluginConfigFile($magentoRootFolder, $file);
247
                    }
248
                }
249
            }
250
251
            if (count($moduleBaseFolders) > 0) {
252
                // Glob plugin folders
253
                $finder = Finder::create();
254
                $finder
255
                    ->files()
256
                    ->depth(1)
257
                    ->followLinks()
258
                    ->ignoreUnreadableDirs(true)
259
                    ->name($this->_customConfigFilename)
260
                    ->in($moduleBaseFolders);
261
262
                foreach ($finder as $file) {
263
                    /* @var $file \Symfony\Component\Finder\SplFileInfo */
264
                    $this->registerPluginConfigFile($magentoRootFolder, $file);
265
                }
266
            }
267
        }
268
269
        $config = ArrayFunctions::mergeArrays($config, $this->_pluginConfig);
270
271
        return $config;
272
    }
273
274
    /**
275
     * @param string $rawConfig
276
     * @param string $magentoRootFolder
277
     * @param \Symfony\Component\Finder\SplFileInfo $file
278
     *
279
     * @return string
280
     */
281
    protected function applyVariables($rawConfig, $magentoRootFolder, $file = null)
282
    {
283
        $replace = array(
284
            '%module%' => $file ? $file->getPath() : '',
285
            '%root%'   => $magentoRootFolder,
286
        );
287
288
        return str_replace(array_keys($replace), $replace, $rawConfig);
289
    }
290
291
292
    /**
293
     * Check if there is a user config file. ~/.n98-magerun.yaml
294
     *
295
     * @param array $config
296
     * @param string $magentoRootFolder
297
     *
298
     * @return array
299
     */
300
    public function loadUserConfig($config, $magentoRootFolder = null)
301
    {
302
        if ($this->_userConfig == null) {
303
            $this->_userConfig = array();
304
            $homeDirectory = OperatingSystem::getHomeDir();
305
            if (OperatingSystem::isWindows()) {
306
                $personalConfigFile = $homeDirectory . DIRECTORY_SEPARATOR . $this->_customConfigFilename;
307
            } else {
308
                $personalConfigFile = $homeDirectory . DIRECTORY_SEPARATOR . '.' . $this->_customConfigFilename;
309
            }
310
311 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...
312
                $userConfig = $this->applyVariables(\file_get_contents($personalConfigFile), $magentoRootFolder, null);
313
                $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...
314
315
                return $config;
316
            }
317
        }
318
319
        $config = ArrayFunctions::mergeArrays($config, $this->_userConfig);
320
321
        return $config;
322
    }
323
324
    /**
325
     * MAGENTO_ROOT/app/etc/n98-magerun.yaml
326
     *
327
     * @param string $magentoRootFolder
328
     * @param string $magerunStopFileFolder
329
     * @param array $config
330
     *
331
     * @return array
332
     */
333
    public function loadProjectConfig($magentoRootFolder, $magerunStopFileFolder, $config)
334
    {
335
        if ($this->_projectConfig == null) {
336
            $this->_projectConfig = array();
337
338
            $projectConfigFile = $magentoRootFolder . DIRECTORY_SEPARATOR . 'app/etc/' . $this->_customConfigFilename;
339 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...
340
                $projectConfig = $this->applyVariables(
341
                    \file_get_contents($projectConfigFile),
342
                    $magentoRootFolder,
343
                    null
344
                );
345
                $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...
346
            }
347
348
            $stopFileConfigFile = $magerunStopFileFolder . DIRECTORY_SEPARATOR . '.' . $this->_customConfigFilename;
349 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...
350
                $projectConfig = $this->applyVariables(
351
                    \file_get_contents($stopFileConfigFile),
352
                    $magentoRootFolder,
353
                    null
354
                );
355
                $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...
356
            }
357
358
            $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...
359
        }
360
361
        return $config;
362
    }
363
364
    /**
365
     * Loads a plugin config file and merges it to plugin config
366
     *
367
     * @param string $magentoRootFolder
368
     * @param SplFileInfo $file
369
     */
370
    protected function registerPluginConfigFile($magentoRootFolder, $file)
371
    {
372
        if (BinaryString::startsWith($file->getPathname(), 'vfs://')) {
373
            $path = $file->getPathname();
374
        } else {
375
            $path = $file->getRealPath();
376
377
            if ($path === "") {
378
                throw new \UnexpectedValueException(sprintf("Realpath for '%s' did return an empty string.", $file));
379
            }
380
381
            if ($path === false) {
382
                $this->_output->writeln(sprintf("<error>Plugin config file broken link '%s'</error>", $file));
383
384
                return;
385
            }
386
        }
387
388
        if (OutputInterface::VERBOSITY_DEBUG <= $this->_output->getVerbosity()) {
389
            $this->_output->writeln('<debug>Load plugin config <comment>' . $path . '</comment></debug>');
390
        }
391
392
        $localPluginConfig = \file_get_contents($path);
393
        $localPluginConfig = Yaml::parse($this->applyVariables($localPluginConfig, $magentoRootFolder, $file));
394
395
        $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 393 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...
396
    }
397
398
    /**
399
     * @return string
400
     */
401
    public function getVendorDir()
402
    {
403
        /* old vendor folder to give backward compatibility */
404
        $vendorFolder = $this->getConfigurationLoaderDir() . '/../../../../vendor';
405
        if (is_dir($vendorFolder)) {
406
            return $vendorFolder;
407
        }
408
409
        /* correct vendor folder for composer installations */
410
        $vendorFolder = $this->getConfigurationLoaderDir() . '/../../../../../../../vendor';
411
        if (is_dir($vendorFolder)) {
412
            return $vendorFolder;
413
        }
414
415
        return '';
416
    }
417
418
    /**
419
     * @return string
420
     */
421
    public function getConfigurationLoaderDir()
422
    {
423
        return __DIR__;
424
    }
425
}
426