Passed
Push — master ( f528cb...6f8ed4 )
by
unknown
04:11
created

SimpleConfigurationLoader::createInstance()   B

Complexity

Conditions 7
Paths 32

Size

Total Lines 48
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 7
eloc 19
c 5
b 0
f 0
nc 32
nop 0
dl 0
loc 48
ccs 0
cts 20
cp 0
crap 56
rs 8.8333
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
        // query whether or not a custom configuration directory has been speified, if yes override the default one
312
        if ($this->input->hasOptionSpecified(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR) && $this->input->getOption(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR)) {
313
            $customConfigurationDir = $this->input->getOption(InputOptionKeysInterface::CUSTOM_CONFIGURATION_DIR);
314
        }
315
316
        // specify the default directory for custom configuration files
317
        if (is_dir($customConfigurationDir)) {
318
            $directories[] = $customConfigurationDir;
319
        }
320
321
322
        // load and return the configuration from the files found in the passed directories
323
        $completeConfiguration = $this->configurationFactory->factoryFromDirectories($installationDir, $defaultConfigurationDir, $directories, $format, $params, $paramsFile);
0 ignored issues
show
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

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