SimpleConfigurationLoader   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 490
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 12
Bugs 0 Features 0
Metric Value
eloc 129
c 12
b 0
f 0
dl 0
loc 490
ccs 0
cts 139
cp 0
rs 9.2
wmc 40

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 18 1
A load() 0 14 1
B createInstance() 0 48 7
A getCommandName() 0 3 1
B getEditionMapping() 0 61 6
A getConsoleOptionLoader() 0 3 1
A getContainer() 0 3 1
A isMagentoRootDir() 0 3 1
A getEntityTypeCode() 0 10 2
A getVendorDir() 0 3 1
A getMagentoEnv() 0 3 1
C createConfiguration() 0 97 13
A getDefaultLibraries() 0 22 4

How to fix   Complexity   

Complex Class

Complex classes like SimpleConfigurationLoader 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.

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 SimpleConfigurationLoader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * TechDivision\Import\Cli\SimpleConfigurationLoader
5
 *
6
 * PHP version 7
7
 *
8
 * @author    Tim Wagner <[email protected]>
9
 * @copyright 2016 TechDivision GmbH <[email protected]>
10
 * @license   https://opensource.org/licenses/MIT
11
 * @link      https://github.com/techdivision/import-cli-simple
12
 * @link      http://www.techdivision.com
13
 */
14
15
namespace TechDivision\Import\Cli;
16
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\DependencyInjection\ContainerInterface;
19
use TechDivision\Import\ConsoleOptionLoaderInterface;
20
use TechDivision\Import\Cli\Configuration\LibraryLoader;
21
use TechDivision\Import\Cli\Utils\DependencyInjectionKeys;
22
use TechDivision\Import\Cli\Utils\MagentoConfigurationKeys;
23
use TechDivision\Import\Utils\CommandNames;
24
use TechDivision\Import\Utils\EditionNamesInterface;
25
use TechDivision\Import\Utils\InputOptionKeysInterface;
26
use TechDivision\Import\Utils\Mappings\CommandNameToEntityTypeCode;
27
use TechDivision\Import\Configuration\ConfigurationFactoryInterface;
28
29
/**
30
 * The configuration loader implementation.
31
 *
32
 * @author    Tim Wagner <[email protected]>
33
 * @copyright 2016 TechDivision GmbH <[email protected]>
34
 * @license   https://opensource.org/licenses/MIT
35
 * @link      https://github.com/techdivision/import-cli-simple
36
 * @link      http://www.techdivision.com
37
 */
38
class SimpleConfigurationLoader implements ConfigurationLoaderInterface
39
{
40
    /**
41
     * The key for the Magento Folder to env.php.
42
     *
43
     * @var string
44
     */
45
    const CONFIGENVPATH = '/app/etc';
46
47
    /**
48
     * The key for the Magento Edition in the metadata extracted from the Composer configuration.
49
     *
50
     * @var string
51
     */
52
    const EDITION = 'edition';
53
54
    /**
55
     * The key for the Magento Version in the metadata extracted from the Composer configuration.
56
     *
57
     * @var string
58
     */
59
    const VERSION = 'version';
60
61
    /**
62
     * The container instance.
63
     *
64
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
65
     */
66
    protected $container;
67
68
    /**
69
     * The actual input instance.
70
     *
71
     * @var \Symfony\Component\Console\Input\InputInterface
72
     */
73
    protected $input;
74
75
    /**
76
     * The library loader instance.
77
     *
78
     * @param \TechDivision\Import\Cli\LibraryLoader
79
     */
80
    protected $libraryLoader;
81
82
    /**
83
     * The configuration factory instance.
84
     *
85
     * @var \TechDivision\Import\Configuration\ConfigurationFactoryInterface
86
     */
87
    protected $configurationFactory;
88
89
    /**
90
     * The available command names.
91
     *
92
     * @var \TechDivision\Import\Utils\CommandNames
93
     */
94
    protected $commandNames;
95
96
    /**
97
     * The mapping of the command names to the entity type codes
98
     *
99
     * @var \TechDivision\Import\Utils\Mappings\CommandNameToEntityTypeCode
100
     */
101
    protected $commandNameToEntityTypeCode;
102
103
    /**
104
     * The console option loader instance.
105
     *
106
     * @var \TechDivision\Import\ConsoleOptionLoaderInterface
107
     */
108
    protected $consoleOptionLoader;
109
110
    /**
111
     * The default sorting for the edition detection.
112
     *
113
     * @var array
114
     */
115
    protected $editionSortOrder = array(
116
        EditionNamesInterface::EE,
117
        EditionNamesInterface::CE
118
    );
119
120
    /**
121
     * @var array
122
     */
123
    private $configurationFiles = array();
124
125
    /**
126
     * Initializes the configuration loader.
127
     *
128
     * @param \Symfony\Component\Console\Input\InputInterface                  $input                        The input instance
129
     * @param \Symfony\Component\DependencyInjection\ContainerInterface        $container                    The container instance
130
     * @param \TechDivision\Import\Cli\Configuration\LibraryLoader             $libraryLoader                The configuration loader instance
131
     * @param \TechDivision\Import\Configuration\ConfigurationFactoryInterface $configurationFactory         The configuration factory instance
132
     * @param \TechDivision\Import\Utils\CommandNames                          $commandNames                 The available command names
133
     * @param \TechDivision\Import\Utils\Mappings\CommandNameToEntityTypeCode  $commandNameToEntityTypeCodes The mapping of the command names to the entity type codes
134
     * @param \TechDivision\Import\ConsoleOptionLoaderInterface                $consoleOptionLoader          The console option loader instance
135
     */
136
    public function __construct(
137
        InputInterface $input,
138
        ContainerInterface $container,
139
        LibraryLoader $libraryLoader,
140
        ConfigurationFactoryInterface $configurationFactory,
141
        CommandNames $commandNames,
142
        CommandNameToEntityTypeCode $commandNameToEntityTypeCodes,
143
        ConsoleOptionLoaderInterface $consoleOptionLoader
144
    ) {
145
146
        // set the passed instances
147
        $this->input = $input;
148
        $this->container = $container;
149
        $this->libraryLoader = $libraryLoader;
150
        $this->configurationFactory = $configurationFactory;
151
        $this->commandNames = $commandNames;
152
        $this->commandNameToEntityTypeCode = $commandNameToEntityTypeCodes;
153
        $this->consoleOptionLoader = $consoleOptionLoader;
154
    }
155
156
    /**
157
     * Factory implementation to create a new initialized configuration instance.
158
     *
159
     * If command line options are specified, they will always override the
160
     * values found in the configuration file.
161
     *
162
     * @return \TechDivision\Import\Configuration\ConfigurationInterface The configuration instance
163
     */
164
    public function load()
165
    {
166
167
        // initially try to create the configuration instance
168
        $instance = $this->createInstance();
169
170
        // we have to set the entity type code at least
171
        $instance->setEntityTypeCode($this->getEntityTypeCode());
172
173
        // load and merge the console options
174
        $this->getConsoleOptionLoader()->load($instance);
175
176
        // return the initialized configuration instance
177
        return $instance;
178
    }
179
180
    /**
181
     * This method create the configuration instance from the configuration file
182
     * defined by the commandline args and options.
183
     *
184
     * @return \TechDivision\Import\Configuration\ConfigurationInterface The configuration instance loaded from the configuration file
185
     * @throws \Exception Is thrown, if the specified configuration file doesn't exist or the mandatory arguments/options to run the requested operation are not available
186
     */
187
    protected function createInstance()
188
    {
189
190
        // try to load the Magento installation directory
191
        $installationDir = $this->input->getOption(InputOptionKeysInterface::INSTALLATION_DIR);
192
193
        // try to load the Magento config directory
194
        $configurationDirParam = $this->input->getOption(InputOptionKeysInterface::CONFIGURATION_DIR);
195
        $configurationDir = $configurationDirParam ?: $installationDir . SimpleConfigurationLoader::CONFIGENVPATH;
196
197
        // query whether or not, a configuration file has been specified
198
        $configuration = $this->input->getOption(InputOptionKeysInterface::CONFIGURATION);
199
200
        // load the configuration from the file with the given filename
201
        $instance = $configuration ? $this->createConfiguration($configuration) : $this->createConfiguration();
202
203
        // query whether or not the installation directory is a valid Magento root directory
204
        if ($this->isMagentoRootDir($configurationDir)) {
205
            // if yes, try to load the Magento Edition from the Composer configuration file
206
            $metadata = $this->getEditionMapping($installationDir);
207
            // initialize/override the Magento edition/version with the values from the Magento installation
208
            $instance->setMagentoEdition($metadata[SimpleConfigurationLoader::EDITION]);
209
            $instance->setMagentoVersion($metadata[SimpleConfigurationLoader::VERSION]);
210
        }
211
212
        // initialize/override the Magento edition with the value from the command line
213
        if ($magentoEdition = $this->input->getOption(InputOptionKeysInterface::MAGENTO_EDITION)) {
214
            $instance->setMagentoEdition($magentoEdition);
215
        }
216
217
        // initialize/override the Magento version with the value from the command line
218
        if ($magentoVersion = $this->input->getOption(InputOptionKeysInterface::MAGENTO_VERSION)) {
219
            $instance->setMagentoVersion($magentoVersion);
220
        }
221
222
        // set the actual command name in the configuration
223
        $instance->setCommandName($this->input->getFirstArgument());
224
225
        $configurationFiles = [];
226
227
        foreach ($this->configurationFiles as $configurationFile) {
228
            $configurationFiles[] = $configurationFile->getPathname();
229
        }
230
        // set the configuration Files in the configuration instance
231
        $instance->setConfigurationFiles($configurationFiles);
232
233
        // return the instance
234
        return $instance;
235
    }
236
237
    /**
238
     * Create and return a new configuration instance from the passed configuration filename
239
     * after merging additional specified params from the commandline.
240
     *
241
     * @param string|null $filename The configuration filename to use
242
     *
243
     * @return \TechDivision\Import\Configuration\ConfigurationInterface The configuration instance
244
     */
245
    protected function createConfiguration($filename = null)
246
    {
247
248
        // initialize the params specified with the --params parameter
249
        $params = null;
250
251
        // try to load the params from the commandline
252
        if ($this->input->hasOptionSpecified(InputOptionKeysInterface::PARAMS) && $this->input->getOption(InputOptionKeysInterface::PARAMS)) {
0 ignored issues
show
Bug introduced by
The method hasOptionSpecified() does not exist on Symfony\Component\Console\Input\InputInterface. Did you maybe mean hasOption()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

252
        if ($this->input->/** @scrutinizer ignore-call */ hasOptionSpecified(InputOptionKeysInterface::PARAMS) && $this->input->getOption(InputOptionKeysInterface::PARAMS)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
253
            $params = $this->input->getOption(InputOptionKeysInterface::PARAMS);
254
        }
255
256
        // initialize the params file specified with the --params-file parameter
257
        $paramsFile = null;
258
259
        // try to load the path of the params file from the commandline
260
        if ($this->input->hasOptionSpecified(InputOptionKeysInterface::PARAMS_FILE) && $this->input->getOption(InputOptionKeysInterface::PARAMS_FILE)) {
261
            $paramsFile = $this->input->getOption(InputOptionKeysInterface::PARAMS_FILE);
262
        }
263
264
        // if a filename has been passed, try to load the configuration from the file
265
        if ($filename !== null && is_file($filename)) {
266
            return $this->configurationFactory->factory($filename, pathinfo($filename, PATHINFO_EXTENSION), $params, $paramsFile);
0 ignored issues
show
Unused Code introduced by
The call to TechDivision\Import\Conf...oryInterface::factory() has too many arguments starting with $params. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

266
            return $this->configurationFactory->/** @scrutinizer ignore-call */ factory($filename, pathinfo($filename, PATHINFO_EXTENSION), $params, $paramsFile);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
It seems like pathinfo($filename, Tech...Cli\PATHINFO_EXTENSION) can also be of type array; however, parameter $type of TechDivision\Import\Conf...oryInterface::factory() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

266
            return $this->configurationFactory->factory($filename, /** @scrutinizer ignore-type */ pathinfo($filename, PATHINFO_EXTENSION), $params, $paramsFile);
Loading history...
267
        }
268
269
        // initialize the array for the directories
270
        $directories = array();
271
272
        // set the default file format
273
        $format = 'json';
274
275
        // load the actual vendor directory and entity type code
276
        $vendorDir = $this->getVendorDir();
277
278
        // load the default configuration directory from the DI configuration
279
        $defaultConfigurationDir = $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_DEFAULT_CONFIGURATION_DIR);
280
281
        // load the directories that has to be parsed for configuration files1
282
        foreach ($this->getDefaultLibraries() as $defaultLibrary) {
283
            // initialize the directory name
284
            $directory = implode(
285
                DIRECTORY_SEPARATOR,
286
                array_merge(
287
                    array($vendorDir),
288
                    explode('/', $defaultLibrary),
289
                    explode('/', $defaultConfigurationDir)
290
                )
291
            );
292
293
            // query whether or not the directory is available1
294
            if (is_dir($directory)) {
295
                $directories[] = $directory;
296
            }
297
        }
298
299
        // load the assumed installation directory
300
        $installationDir = $this->input->getOption(InputOptionKeysInterface::INSTALLATION_DIR);
301
302
        // initialize the default custom configuration directory
303
        $customConfigurationDir = implode(
304
            DIRECTORY_SEPARATOR,
305
            array_merge(
306
                array($installationDir),
307
                explode('/', $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_CUSTOM_CONFIGURATION_DIR))
308
            )
309
        );
310
311
        // initialize the default custom configuration directory
312
        $customConfigurationPublicDir = implode(
313
            DIRECTORY_SEPARATOR,
314
            array_merge(
315
                [$installationDir],
316
                explode('/', $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_CUSTOM_CONFIGURATION_PUBLIC_DIR))
317
            )
318
        );
319
320
        // query whether or not a custom configuration directory has been speified, if yes override the default one
321
        if ($this->input->hasOptionSpecified(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR) && $this->input->getOption(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR)) {
322
            $customConfigurationDir = $this->input->getOption(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR);
323
        }
324
325
        // specify the default directory for custom configuration files
326
        if (is_dir($customConfigurationDir)) {
327
            $directories[] = $customConfigurationDir;
328
        }
329
330
        // specify the default directory for custom configuration public files
331
        if (is_dir($customConfigurationPublicDir)) {
332
            $directories[] = $customConfigurationPublicDir;
333
        }
334
335
336
        // load and return the configuration from the files found in the passed directories
337
        $completeConfiguration = $this->configurationFactory->factoryFromDirectories($installationDir, $defaultConfigurationDir, $directories, $format, $params, $paramsFile);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $completeConfiguration is correct as $this->configurationFact..., $params, $paramsFile) targeting TechDivision\Import\Conf...actoryFromDirectories() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
It seems like $defaultConfigurationDir can also be of type array; however, parameter $defaultConfigurationDir of TechDivision\Import\Conf...actoryFromDirectories() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

337
        $completeConfiguration = $this->configurationFactory->factoryFromDirectories($installationDir, /** @scrutinizer ignore-type */ $defaultConfigurationDir, $directories, $format, $params, $paramsFile);
Loading history...
338
339
        $this->configurationFiles = $this->configurationFactory->getConfigurationFiles($directories, $format);
340
341
        return $completeConfiguration;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $completeConfiguration returns the type void which is incompatible with the documented return type TechDivision\Import\Conf...\ConfigurationInterface.
Loading history...
342
    }
343
344
    /**
345
     * Return's the DI container instance.
346
     *
347
     * @return \Symfony\Component\DependencyInjection\ContainerInterface The DI container instance
348
     */
349
    protected function getContainer()
350
    {
351
        return $this->container;
352
    }
353
354
    /**
355
     * The console option loader instance.
356
     *
357
     * @return \TechDivision\Import\ConsoleOptionLoaderInterface The instance
358
     */
359
    protected function getConsoleOptionLoader()
360
    {
361
        return $this->consoleOptionLoader;
362
    }
363
364
    /**
365
     * Return's the absolute path to the actual vendor directory.
366
     *
367
     * @return string The absolute path to the actual vendor directory
368
     * @throws \Exception Is thrown, if none of the possible vendor directories can be found
369
     */
370
    protected function getVendorDir()
371
    {
372
        return $this->getContainer()->getParameter(DependencyInjectionKeys::CONFIGURATION_VENDOR_DIR);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getContain...NFIGURATION_VENDOR_DIR) also could return the type array|boolean which is incompatible with the documented return type string.
Loading history...
373
    }
374
375
    /**
376
     * Return's the actual command name.
377
     *
378
     * @return string The actual command name
379
     */
380
    protected function getCommandName()
381
    {
382
        return $this->input->getArgument('command');
383
    }
384
385
    /**
386
     * Return's the command's entity type code.
387
     *
388
     * @return string The command's entity type code
389
     * @throws \Exception Is thrown, if the command name can not be mapped
390
     */
391
    protected function getEntityTypeCode()
392
    {
393
394
        // try to map the command name to a entity type code
395
        if (array_key_exists($commandName = $this->getCommandName(), (array) $this->commandNameToEntityTypeCode)) {
396
            return $this->commandNameToEntityTypeCode[$commandName];
397
        }
398
399
        // throw an exception if not possible
400
        throw new \Exception(sprintf('Can\'t map command name %s to a entity type', $commandName));
401
    }
402
403
    /**
404
     * Returns the mapped Magento Edition from the passed Magento installation.
405
     *
406
     * @param string $installationDir The Magento installation directory
407
     *
408
     * @return array The array with the mapped Magento Edition (either CE or EE) + the Version
409
     * @throws \Exception Is thrown, if the passed installation directory doesn't contain a valid Magento installation
410
     */
411
    protected function getEditionMapping($installationDir)
412
    {
413
414
        // load the default edition mappings from the configuration
415
        $editionMappings = $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_EDITION_MAPPINGS);
416
417
        // load the composer file from the Magento root directory
418
        $composer = json_decode(file_get_contents($composerFile = sprintf('%s/composer.lock', $installationDir)), true);
419
420
        // initialize the array that contains the packages to identify the Magento edition
421
        $packages = array();
422
423
        // query whether or not packages are available in the composer file
424
        if (isset($composer[MagentoConfigurationKeys::COMPOSER_PACKAGES])) {
425
            // iterate over the available packages to figure out the ones that allows us to identify the Magento edition
426
            foreach ($composer[MagentoConfigurationKeys::COMPOSER_PACKAGES] as $package) {
427
                // try to load and explode the Magento Edition identifier from the Composer name
428
                $possibleEdition = $package[MagentoConfigurationKeys::COMPOSER_EDITION_NAME_ATTRIBUTE];
429
430
                // try to load and explode the Magento Edition from the Composer configuration
431
                if (isset($editionMappings[$possibleEdition])) {
432
                    // try to load and explode the Magento Version from the Composer configuration
433
                    if (isset($package[MagentoConfigurationKeys::COMPOSER_EDITION_VERSION_ATTRIBUTE])) {
434
                        // add Magento edition => version mapping and continue
435
                        $packages[$editionMappings[$possibleEdition]] = $package[MagentoConfigurationKeys::COMPOSER_EDITION_VERSION_ATTRIBUTE];
436
                        continue;
437
                    }
438
439
                    // throw an exception if the package has NO version defineds
440
                    throw new \Exception(
441
                        sprintf(
442
                            'Can\'t detect a valid Magento version for package "%s" in "%s", please set Magento Version with the "--magento-version" option',
443
                            $possibleEdition,
444
                            $composerFile
445
                        )
446
                    );
447
                }
448
            }
449
        }
450
451
        // create the default sort order for the edition detection
452
        $editionSortOrder = array_flip($this->editionSortOrder);
453
454
        // sort the packages according the default sort order
455
        uksort($packages, function ($a, $b) use ($editionSortOrder) {
456
            return $editionSortOrder[$a] <=> $editionSortOrder[$b];
457
        });
458
459
        // return the array with the Magento Version/Edition data
460
        foreach ($packages as $edition => $version) {
461
            return array(
462
                SimpleConfigurationLoader::EDITION => $edition,
463
                SimpleConfigurationLoader::VERSION => $version
464
            );
465
        }
466
467
        // throw an exception if NO edition information can be found in the composer.lock file
468
        throw new \Exception(
469
            sprintf(
470
                'Can\'t detect a valid Magento edition/version in "%s", please pass them with the "--magento-edition" and "--magento-version" options',
471
                $composerFile
472
            )
473
        );
474
    }
475
476
477
    /**
478
     * Return's the application's default libraries.
479
     *
480
     * @return array The default libraries
481
     */
482
    protected function getDefaultLibraries()
483
    {
484
485
        // load the default libraries from the configuration
486
        $defaultLibraries = $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_DEFAULT_LIBRARIES);
487
488
        // initialize the array for the libraries
489
        $libraries = array();
490
491
        // append each library only ONCE
492
        foreach ($defaultLibraries as $libraries) {
493
            foreach ($libraries as $library) {
494
                if (in_array($library, $libraries)) {
495
                    continue;
496
                }
497
                // append the library
498
                $libraries[] = $library;
499
            }
500
        }
501
502
        // return the array with the libraries
503
        return $libraries;
504
    }
505
506
    /**
507
     * Query whether or not, the passed directory is a Magento root directory.
508
     *
509
     * @param string $dir The directory to query
510
     *
511
     * @return boolean TRUE if the directory is a Magento root directory, else FALSE
512
     */
513
    protected function isMagentoRootDir($dir)
514
    {
515
        return is_file($this->getMagentoEnv($dir));
516
    }
517
518
    /**
519
     * Return's the path to the Magento file with the environment configuration.
520
     *
521
     * @param string $dir The path to the Magento root directory
522
     *
523
     * @return string The path to the Magento file with the environment configuration
524
     */
525
    protected function getMagentoEnv($dir)
526
    {
527
        return sprintf('%s/env.php', $dir);
528
    }
529
}
530