Passed
Push — pac-512-fix-url-suffix ( 719c52 )
by
unknown
06:00
created

SimpleConfigurationLoader   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 460
Duplicated Lines 0 %

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 113
c 10
b 0
f 0
dl 0
loc 460
rs 9.36
wmc 38

14 Methods

Rating   Name   Duplication   Size   Complexity  
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 __construct() 0 18 1
A getVendorDir() 0 3 1
A getMagentoEnv() 0 3 1
A load() 0 14 1
A createInstance() 0 36 5
A getCommandName() 0 3 1
A getDefaultLibraries() 0 22 4
A getMagentoConfig() 0 3 1
C createConfiguration() 0 78 12
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
    /**
42
     * The key for the Magento Edition in the metadata extracted from the Composer configuration.
43
     *
44
     * @var string
45
     */
46
    const EDITION = 'edition';
47
48
    /**
49
     * The key for the Magento Version in the metadata extracted from the Composer configuration.
50
     *
51
     * @var string
52
     */
53
    const VERSION = 'version';
54
55
    /**
56
     * The container instance.
57
     *
58
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
59
     */
60
    protected $container;
61
62
    /**
63
     * The actual input instance.
64
     *
65
     * @var \Symfony\Component\Console\Input\InputInterface
66
     */
67
    protected $input;
68
69
    /**
70
     * The library loader instance.
71
     *
72
     * @param \TechDivision\Import\Cli\LibraryLoader
73
     */
74
    protected $libraryLoader;
75
76
    /**
77
     * The configuration factory instance.
78
     *
79
     * @var \TechDivision\Import\Configuration\ConfigurationFactoryInterface
80
     */
81
    protected $configurationFactory;
82
83
    /**
84
     * The available command names.
85
     *
86
     * @var \TechDivision\Import\Utils\CommandNames
87
     */
88
    protected $commandNames;
89
90
    /**
91
     * The mapping of the command names to the entity type codes
92
     *
93
     * @var \TechDivision\Import\Utils\Mappings\CommandNameToEntityTypeCode
94
     */
95
    protected $commandNameToEntityTypeCode;
96
97
    /**
98
     * The console option loader instance.
99
     *
100
     * @var \TechDivision\Import\ConsoleOptionLoaderInterface
101
     */
102
    protected $consoleOptionLoader;
103
104
    /**
105
     * The default sorting for the edition detection.
106
     *
107
     * @var array
108
     */
109
    protected $editionSortOrder = array(
110
        EditionNamesInterface::EE,
111
        EditionNamesInterface::CE
112
    );
113
114
    /**
115
     * Initializes the configuration loader.
116
     *
117
     * @param \Symfony\Component\Console\Input\InputInterface                  $input                        The input instance
118
     * @param \Symfony\Component\DependencyInjection\ContainerInterface        $container                    The container instance
119
     * @param \TechDivision\Import\Cli\Configuration\LibraryLoader             $libraryLoader                The configuration loader instance
120
     * @param \TechDivision\Import\Configuration\ConfigurationFactoryInterface $configurationFactory         The configuration factory instance
121
     * @param \TechDivision\Import\Utils\CommandNames                          $commandNames                 The available command names
122
     * @param \TechDivision\Import\Utils\Mappings\CommandNameToEntityTypeCode  $commandNameToEntityTypeCodes The mapping of the command names to the entity type codes
123
     * @param \TechDivision\Import\ConsoleOptionLoaderInterface                $consoleOptionLoader          The console option loader instance
124
     */
125
    public function __construct(
126
        InputInterface $input,
127
        ContainerInterface $container,
128
        LibraryLoader $libraryLoader,
129
        ConfigurationFactoryInterface $configurationFactory,
130
        CommandNames $commandNames,
131
        CommandNameToEntityTypeCode $commandNameToEntityTypeCodes,
132
        ConsoleOptionLoaderInterface $consoleOptionLoader
133
    ) {
134
135
        // set the passed instances
136
        $this->input = $input;
137
        $this->container = $container;
138
        $this->libraryLoader = $libraryLoader;
139
        $this->configurationFactory = $configurationFactory;
140
        $this->commandNames = $commandNames;
141
        $this->commandNameToEntityTypeCode = $commandNameToEntityTypeCodes;
142
        $this->consoleOptionLoader = $consoleOptionLoader;
143
    }
144
145
    /**
146
     * Factory implementation to create a new initialized configuration instance.
147
     *
148
     * If command line options are specified, they will always override the
149
     * values found in the configuration file.
150
     *
151
     * @return \TechDivision\Import\Configuration\ConfigurationInterface The configuration instance
152
     */
153
    public function load()
154
    {
155
156
        // initially try to create the configuration instance
157
        $instance = $this->createInstance();
158
159
        // we have to set the entity type code at least
160
        $instance->setEntityTypeCode($this->getEntityTypeCode());
161
162
        // load and merge the console options
163
        $this->getConsoleOptionLoader()->load($instance);
164
165
        // return the initialized configuration instance
166
        return $instance;
167
    }
168
169
    /**
170
     * This method create the configuration instance from the configuration file
171
     * defined by the commandline args and options.
172
     *
173
     * @return \TechDivision\Import\Configuration\ConfigurationInterface The configuration instance loaded from the configuration file
174
     * @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
175
     */
176
    protected function createInstance()
177
    {
178
179
        // try to load the Magento installation directory
180
        $installationDir = $this->input->getOption(InputOptionKeysInterface::INSTALLATION_DIR);
181
182
        // query whether or not, a configuration file has been specified
183
        $configuration = $this->input->getOption(InputOptionKeysInterface::CONFIGURATION);
184
185
        // load the configuration from the file with the given filename
186
        $instance = $configuration ? $this->createConfiguration($configuration) : $this->createConfiguration();
187
188
        // query whether or not the installation directory is a valid Magento root directory
189
        if ($this->isMagentoRootDir($installationDir)) {
190
            // if yes, try to load the Magento Edition from the Composer configuration file
191
            $metadata = $this->getEditionMapping($installationDir);
192
            // initialize/override the Magento edition/version with the values from the Magento installation
193
            $instance->setMagentoEdition($metadata[SimpleConfigurationLoader::EDITION]);
194
            $instance->setMagentoVersion($metadata[SimpleConfigurationLoader::VERSION]);
195
        }
196
197
        // initialize/override the Magento edition with the value from the command line
198
        if ($magentoEdition = $this->input->getOption(InputOptionKeysInterface::MAGENTO_EDITION)) {
199
            $instance->setMagentoEdition($magentoEdition);
200
        }
201
202
        // initialize/override the Magento version with the value from the command line
203
        if ($magentoVersion = $this->input->getOption(InputOptionKeysInterface::MAGENTO_VERSION)) {
204
            $instance->setMagentoVersion($magentoVersion);
205
        }
206
207
        // set the actual command name in the configuration
208
        $instance->setCommandName($this->input->getFirstArgument());
209
210
        // return the instance
211
        return $instance;
212
    }
213
214
    /**
215
     * Create and return a new configuration instance from the passed configuration filename
216
     * after merging additional specified params from the commandline.
217
     *
218
     * @param string|null $filename The configuration filename to use
219
     *
220
     * @return \TechDivision\Import\Configuration\ConfigurationInterface The configuration instance
221
     */
222
    protected function createConfiguration($filename = null)
223
    {
224
225
        // initialize the params specified with the --params parameter
226
        $params = null;
227
228
        // try to load the params from the commandline
229
        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

229
        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...
230
            $params = $this->input->getOption(InputOptionKeysInterface::PARAMS);
231
        }
232
233
        // initialize the params file specified with the --params-file parameter
234
        $paramsFile = null;
235
236
        // try to load the path of the params file from the commandline
237
        if ($this->input->hasOptionSpecified(InputOptionKeysInterface::PARAMS_FILE) && $this->input->getOption(InputOptionKeysInterface::PARAMS_FILE)) {
238
            $paramsFile = $this->input->getOption(InputOptionKeysInterface::PARAMS_FILE);
239
        }
240
241
        // if a filename has been passed, try to load the configuration from the file
242
        if ($filename !== null && is_file($filename)) {
243
            return $this->configurationFactory->factory($filename, pathinfo($filename, PATHINFO_EXTENSION), $params, $paramsFile);
0 ignored issues
show
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

243
            return $this->configurationFactory->factory($filename, /** @scrutinizer ignore-type */ pathinfo($filename, PATHINFO_EXTENSION), $params, $paramsFile);
Loading history...
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

243
            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...
244
        }
245
246
        // initialize the array for the directories
247
        $directories = array();
248
249
        // set the default file format
250
        $format = 'json';
251
252
        // load the actual vendor directory and entity type code
253
        $vendorDir = $this->getVendorDir();
254
255
        // load the default configuration directory from the DI configuration
256
        $defaultConfigurationDir = $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_DEFAULT_CONFIGURATION_DIR);
257
258
        // load the directories that has to be parsed for configuration files1
259
        foreach ($this->getDefaultLibraries() as $defaultLibrary) {
260
            // initialize the directory name
261
            $directory = implode(
262
                DIRECTORY_SEPARATOR,
263
                array_merge(
264
                    array($vendorDir),
265
                    explode('/', $defaultLibrary),
266
                    explode('/', $defaultConfigurationDir)
267
                )
268
            );
269
270
            // query whether or not the directory is available1
271
            if (is_dir($directory)) {
272
                $directories[] = $directory;
273
            }
274
        }
275
276
        // load the assumed installation directory
277
        $installationDir = $this->input->getOption(InputOptionKeysInterface::INSTALLATION_DIR);
278
279
        // initialize the default custom configuration directory
280
        $customConfigurationDir = implode(
281
            DIRECTORY_SEPARATOR,
282
            array_merge(
283
                array($installationDir),
284
                explode('/', $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_CUSTOM_CONFIGURATION_DIR))
285
            )
286
        );
287
288
        // query whether or not a custom configuration directory has been speified, if yes override the default one
289
        if ($this->input->hasOptionSpecified(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR) && $this->input->getOption(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR)) {
290
            $customConfigurationDir = $this->input->getOption(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR);
291
        }
292
293
        // specify the default directory for custom configuration files
294
        if (is_dir($customConfigurationDir)) {
295
            $directories[] = $customConfigurationDir;
296
        }
297
298
        // load and return the configuration from the files found in the passed directories
299
        return $this->configurationFactory->factoryFromDirectories($installationDir, $defaultConfigurationDir, $directories, $format, $params, $paramsFile);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $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 used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
Bug Best Practice introduced by
The expression return $this->configurat..., $params, $paramsFile) returns the type void which is incompatible with the documented return type TechDivision\Import\Conf...\ConfigurationInterface.
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

299
        return $this->configurationFactory->factoryFromDirectories($installationDir, /** @scrutinizer ignore-type */ $defaultConfigurationDir, $directories, $format, $params, $paramsFile);
Loading history...
300
    }
301
302
    /**
303
     * Return's the DI container instance.
304
     *
305
     * @return \Symfony\Component\DependencyInjection\ContainerInterface The DI container instance
306
     */
307
    protected function getContainer()
308
    {
309
        return $this->container;
310
    }
311
312
    /**
313
     * The console option loader instance.
314
     *
315
     * @return \TechDivision\Import\ConsoleOptionLoaderInterface The instance
316
     */
317
    protected function getConsoleOptionLoader()
318
    {
319
        return $this->consoleOptionLoader;
320
    }
321
322
    /**
323
     * Return's the absolute path to the actual vendor directory.
324
     *
325
     * @return string The absolute path to the actual vendor directory
326
     * @throws \Exception Is thrown, if none of the possible vendor directories can be found
327
     */
328
    protected function getVendorDir()
329
    {
330
        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...
331
    }
332
333
    /**
334
     * Return's the actual command name.
335
     *
336
     * @return string The actual command name
337
     */
338
    protected function getCommandName()
339
    {
340
        return $this->input->getArgument('command');
341
    }
342
343
    /**
344
     * Return's the command's entity type code.
345
     *
346
     * @return string The command's entity type code
347
     * @throws \Exception Is thrown, if the command name can not be mapped
348
     */
349
    protected function getEntityTypeCode()
350
    {
351
352
        // try to map the command name to a entity type code
353
        if (array_key_exists($commandName = $this->getCommandName(), (array) $this->commandNameToEntityTypeCode)) {
354
            return $this->commandNameToEntityTypeCode[$commandName];
355
        }
356
357
        // throw an exception if not possible
358
        throw new \Exception(sprintf('Can\'t map command name %s to a entity type', $commandName));
359
    }
360
361
    /**
362
     * Returns the mapped Magento Edition from the passed Magento installation.
363
     *
364
     * @param string $installationDir The Magento installation directory
365
     *
366
     * @return array The array with the mapped Magento Edition (either CE or EE) + the Version
367
     * @throws \Exception Is thrown, if the passed installation directory doesn't contain a valid Magento installation
368
     */
369
    protected function getEditionMapping($installationDir)
370
    {
371
372
        // load the default edition mappings from the configuration
373
        $editionMappings = $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_EDITION_MAPPINGS);
374
375
        // load the composer file from the Magento root directory
376
        $composer = json_decode(file_get_contents($composerFile = sprintf('%s/composer.lock', $installationDir)), true);
377
378
        // initialize the array that contains the packages to identify the Magento edition
379
        $packages = array();
380
381
        // query whether or not packages are available in the composer file
382
        if (isset($composer[MagentoConfigurationKeys::COMPOSER_PACKAGES])) {
383
            // iterate over the available packages to figure out the ones that allows us to identify the Magento edition
384
            foreach ($composer[MagentoConfigurationKeys::COMPOSER_PACKAGES] as $package) {
385
                // try to load and explode the Magento Edition identifier from the Composer name
386
                $possibleEdition = $package[MagentoConfigurationKeys::COMPOSER_EDITION_NAME_ATTRIBUTE];
387
388
                // try to load and explode the Magento Edition from the Composer configuration
389
                if (isset($editionMappings[$possibleEdition])) {
390
                    // try to load and explode the Magento Version from the Composer configuration
391
                    if (isset($package[MagentoConfigurationKeys::COMPOSER_EDITION_VERSION_ATTRIBUTE])) {
392
                        // add Magento edition => version mapping and continue
393
                        $packages[$editionMappings[$possibleEdition]] = $package[MagentoConfigurationKeys::COMPOSER_EDITION_VERSION_ATTRIBUTE];
394
                        continue;
395
                    }
396
397
                    // throw an exception if the package has NO version defineds
398
                    throw new \Exception(
399
                        sprintf(
400
                            'Can\'t detect a valid Magento version for package "%s" in "%s", please set Magento Version with the "--magento-version" option',
401
                            $possibleEdition,
402
                            $composerFile
403
                        )
404
                    );
405
                }
406
            }
407
        }
408
409
        // create the default sort order for the edition detection
410
        $editionSortOrder = array_flip($this->editionSortOrder);
411
412
        // sort the packages according the default sort order
413
        uksort($packages, function ($a, $b) use ($editionSortOrder) {
414
            return $editionSortOrder[$a] <=> $editionSortOrder[$b];
415
        });
416
417
        // return the array with the Magento Version/Edition data
418
        foreach ($packages as $edition => $version) {
419
            return array(
420
                SimpleConfigurationLoader::EDITION => $edition,
421
                SimpleConfigurationLoader::VERSION => $version
422
            );
423
        }
424
425
        // throw an exception if NO edition information can be found in the composer.lock file
426
        throw new \Exception(
427
            sprintf(
428
                'Can\'t detect a valid Magento edition/version in "%s", please pass them with the "--magento-edition" and "--magento-version" options',
429
                $composerFile
430
            )
431
        );
432
    }
433
434
435
    /**
436
     * Return's the application's default libraries.
437
     *
438
     * @return array The default libraries
439
     */
440
    protected function getDefaultLibraries()
441
    {
442
443
        // load the default libraries from the configuration
444
        $defaultLibraries = $this->getContainer()->getParameter(DependencyInjectionKeys::APPLICATION_DEFAULT_LIBRARIES);
445
446
        // initialize the array for the libraries
447
        $libraries = array();
448
449
        // append each library only ONCE
450
        foreach ($defaultLibraries as $libraries) {
451
            foreach ($libraries as $library) {
452
                if (in_array($library, $libraries)) {
453
                    continue;
454
                }
455
                // append the library
456
                $libraries[] = $library;
457
            }
458
        }
459
460
        // return the array with the libraries
461
        return $libraries;
462
    }
463
464
    /**
465
     * Query whether or not, the passed directory is a Magento root directory.
466
     *
467
     * @param string $dir The directory to query
468
     *
469
     * @return boolean TRUE if the directory is a Magento root directory, else FALSE
470
     */
471
    protected function isMagentoRootDir($dir)
472
    {
473
        return is_file($this->getMagentoEnv($dir));
474
    }
475
476
    /**
477
     * Return's the path to the Magento file with the environment configuration.
478
     *
479
     * @param string $dir The path to the Magento root directory
480
     *
481
     * @return string The path to the Magento file with the environment configuration
482
     */
483
    protected function getMagentoEnv($dir)
484
    {
485
        return sprintf('%s/app/etc/env.php', $dir);
486
    }
487
488
    /**
489
     * Return's the path to the Magento file with the environment configuration.
490
     *
491
     * @param string $dir The path to the Magento root directory
492
     *
493
     * @return string The path to the Magento file with the environment configuration
494
     */
495
    protected function getMagentoConfig($dir)
496
    {
497
        return sprintf('%s/app/etc/config.php', $dir);
498
    }
499
}
500