Completed
Push — master ( ac48e3...f05831 )
by Tom
04:26
created

ConfigurationLoader::logDebug()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

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 2
eloc 3
nc 2
nop 1
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
            $customName = pathinfo($customFilename, PATHINFO_FILENAME);
213
            if (OperatingSystem::isWindows()) {
214
                $config['plugin']['folders'][] = getenv('WINDIR') . '/' . $customName . '/modules';
215
                $config['plugin']['folders'][] = OperatingSystem::getHomeDir() . '/' . $customName . '/modules';
216
            } else {
217
                $config['plugin']['folders'][] = OperatingSystem::getHomeDir() . '/.' . $customName . '/modules';
218
            }
219
            $config['plugin']['folders'][] = $magentoRootFolder . '/lib/' . $customName . '/modules';
220
            foreach ($config['plugin']['folders'] as $folder) {
221
                if (is_dir($folder)) {
222
                    $moduleBaseFolders[] = $folder;
223
                }
224
            }
225
226
            /**
227
             * Allow modules to be placed vendor folder if not in phar mode
228
             */
229
            if (!$this->_isPharMode) {
230
                if (is_dir($this->getVendorDir())) {
231
                    $finder = Finder::create();
232
                    $finder
233
                        ->files()
234
                        ->depth(2)
235
                        ->followLinks()
236
                        ->ignoreUnreadableDirs(true)
237
                        ->name($customFilename)
238
                        ->in($this->getVendorDir());
239
240
                    foreach ($finder as $file) {
241
                        /* @var $file SplFileInfo */
242
                        $this->registerPluginConfigFile($magentoRootFolder, $file);
243
                    }
244
                }
245
            }
246
247
            if (count($moduleBaseFolders) > 0) {
248
                // Glob plugin folders
249
                $finder = Finder::create();
250
                $finder
251
                    ->files()
252
                    ->depth(1)
253
                    ->followLinks()
254
                    ->ignoreUnreadableDirs(true)
255
                    ->name($customFilename)
256
                    ->in($moduleBaseFolders);
257
258
                foreach ($finder as $file) {
259
                    /* @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
     * Check if there is a user config file. ~/.n98-magerun.yaml
289
     *
290
     * @param array $config
291
     * @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...
292
     *
293
     * @return array
294
     */
295
    public function loadUserConfig(array $config, $magentoRootFolder = null)
296
    {
297
        if (null === $this->_userConfig) {
298
            $this->_userConfig = array();
299
            $locator = new ConfigLocator($this->_customConfigFilename, $magentoRootFolder);
300
            if ($userConfigFile = $locator->getUserConfigFile()) {
301
                $this->_userConfig = $userConfigFile->toArray();
302
            }
303
        }
304
305
        $config = ArrayFunctions::mergeArrays($config, $this->_userConfig);
306
307
        return $config;
308
    }
309
310
    /**
311
     * MAGENTO_ROOT/app/etc/n98-magerun.yaml
312
     *
313
     * @param string $magentoRootFolder
314
     * @param string $magerunStopFileFolder
315
     * @param array $config
316
     *
317
     * @return array
318
     */
319
    public function loadProjectConfig($magentoRootFolder, $magerunStopFileFolder, array $config)
320
    {
321
        if (null !== $this->_projectConfig) {
322
            return ArrayFunctions::mergeArrays($config, $this->_projectConfig);
323
        }
324
325
        $this->_projectConfig = array();
326
327
        $locator = new ConfigLocator($this->_customConfigFilename, $magentoRootFolder);
328
329
        if ($projectConfigFile = $locator->getProjectConfigFile()) {
330
            $this->_projectConfig = $projectConfigFile->toArray();
331
        }
332
333
        if ($stopFileConfigFile = $locator->getStopFileConfigFile($magerunStopFileFolder)) {
334
            $this->_projectConfig = $stopFileConfigFile->mergeArray($this->_projectConfig);
335
        }
336
337
        return ArrayFunctions::mergeArrays($config, $this->_projectConfig);
338
    }
339
340
    /**
341
     * Loads a plugin config file and merges it to plugin config
342
     *
343
     * @param string $magentoRootFolder
344
     * @param SplFileInfo $file
345
     */
346
    protected function registerPluginConfigFile($magentoRootFolder, $file)
347
    {
348
        if (BinaryString::startsWith($file->getPathname(), 'vfs://')) {
349
            $path = $file->getPathname();
350
        } else {
351
            $path = $file->getRealPath();
352
353
            if ($path === "") {
354
                throw new \UnexpectedValueException(sprintf("Realpath for '%s' did return an empty string.", $file));
355
            }
356
357
            if ($path === false) {
358
                $this->log(sprintf("<error>Plugin config file broken link '%s'</error>", $file));
359
                return;
360
            }
361
        }
362
363
        $this->logDebug('Load plugin config <comment>' . $path . '</comment>');
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