Completed
Push — master ( 36e8c5...2454a8 )
by Tom
12:20 queued 07:25
created

ConfigurationLoader::loadUserConfig()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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