Completed
Push — master ( 18890d...dd6620 )
by Tim
15s
created

getDefaultConfigurationFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 16
ccs 0
cts 12
cp 0
rs 9.4285
cc 2
eloc 7
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * TechDivision\Import\Cli\SimpleConfigurationLoader
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import-cli-simple
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Cli;
22
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\DependencyInjection\ContainerInterface;
25
use TechDivision\Import\ConfigurationFactoryInterface;
26
use TechDivision\Import\Cli\Command\InputOptionKeys;
27
use TechDivision\Import\Cli\Configuration\LibraryLoader;
28
use TechDivision\Import\Cli\Utils\DependencyInjectionKeys;
29
use TechDivision\Import\Cli\Utils\MagentoConfigurationKeys;
30
use TechDivision\Import\Utils\CommandNames;
31
use TechDivision\Import\Utils\EntityTypeCodes;
32
use TechDivision\Import\Utils\Mappings\CommandNameToEntityTypeCode;
33
34
/**
35
 * The configuration loader implementation.
36
 *
37
 * @author    Tim Wagner <[email protected]>
38
 * @copyright 2016 TechDivision GmbH <[email protected]>
39
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
40
 * @link      https://github.com/techdivision/import-cli-simple
41
 * @link      http://www.techdivision.com
42
 */
43
class SimpleConfigurationLoader implements ConfigurationLoaderInterface
44
{
45
46
    /**
47
     * The composer name => Magento Edition mappings.
48
     *
49
     * @var array
50
     */
51
    protected $editionMappings = array(
52
        'magento2ce'                 => 'CE',
53
        'project-community-edition'  => 'CE',
54
        'magento2ee'                 => 'EE',
55
        'project-enterprise-edition' => 'EE'
56
    );
57
58
    protected $configurationFileMappings = array(
59
        EntityTypeCodes::NONE                      => 'techdivision-import',
60
        EntityTypeCodes::EAV_ATTRIBUTE             => 'techdivision-import',
61
        EntityTypeCodes::CATALOG_PRODUCT           => 'techdivision-import',
62
        EntityTypeCodes::CATALOG_PRODUCT_PRICE     => 'techdivision-import-price',
63
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY => 'techdivision-import-inventory',
64
        EntityTypeCodes::CATALOG_CATEGORY          => 'techdivision-import'
65
    );
66
67
    /**
68
     * The array with the default entity type => configuration mapping.
69
     *
70
     * @var array
71
     */
72
    protected $defaultConfigurations = array(
73
        'ce' => array(
74
            EntityTypeCodes::NONE                      => 'techdivision/import-product',
75
            EntityTypeCodes::EAV_ATTRIBUTE             => 'techdivision/import-attribute',
76
            EntityTypeCodes::CATALOG_PRODUCT           => 'techdivision/import-product',
77
            EntityTypeCodes::CATALOG_PRODUCT_PRICE     => 'techdivision/import-product',
78
            EntityTypeCodes::CATALOG_PRODUCT_INVENTORY => 'techdivision/import-product',
79
            EntityTypeCodes::CATALOG_CATEGORY          => 'techdivision/import-category'
80
        ),
81
        'ee' => array(
82
            EntityTypeCodes::NONE                      => 'techdivision/import-product-ee',
83
            EntityTypeCodes::EAV_ATTRIBUTE             => 'techdivision/import-attribute',
84
            EntityTypeCodes::CATALOG_PRODUCT           => 'techdivision/import-product-ee',
85
            EntityTypeCodes::CATALOG_PRODUCT_PRICE     => 'techdivision/import-product-ee',
86
            EntityTypeCodes::CATALOG_PRODUCT_INVENTORY => 'techdivision/import-product-ee',
87
            EntityTypeCodes::CATALOG_CATEGORY          => 'techdivision/import-category-ee'
88
        )
89
    );
90
91
    /**
92
     * The container instance.
93
     *
94
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
95
     */
96
    protected $container;
97
98
    /**
99
     * The actual input instance.
100
     *
101
     * @var \Symfony\Component\Console\Input\InputInterface
102
     */
103
    protected $input;
104
105
    /**
106
     * The library loader instance.
107
     *
108
     * @param \TechDivision\Import\Cli\LibraryLoader
109
     */
110
    protected $libraryLoader;
111
112
    /**
113
     * The configuration factory instance.
114
     *
115
     * @var \TechDivision\Import\ConfigurationFactoryInterface
116
     */
117
    protected $configurationFactory;
118
119
    /**
120
     * The available command names.
121
     *
122
     * @var \TechDivision\Import\Utils\CommandNames
123
     */
124
    protected $commandNames;
125
126
    /**
127
     * The mapping of the command names to the entity type codes
128
     *
129
     * @var \TechDivision\Import\Utils\Mappings\CommandNameToEntityTypeCode
130
     */
131
    protected $commandNameToEntityTypeCode;
132
133
    /**
134
     * Initializes the configuration loader.
135
     *
136
     * @param \Symfony\Component\Console\Input\InputInterface                 $input                        The input instance
137
     * @param \Symfony\Component\DependencyInjection\ContainerInterface       $container                    The container instance
138
     * @param \TechDivision\Import\Cli\Configuration\LibraryLoader            $libraryLoader                The configuration loader instance
139
     * @param \TechDivision\Import\ConfigurationFactoryInterface              $configurationFactory         The configuration factory instance
140
     * @param \TechDivision\Import\Utils\CommandNames                         $commandNames                 The available command names
141
     * @param \TechDivision\Import\Utils\Mappings\CommandNameToEntityTypeCode $commandNameToEntityTypeCodes The mapping of the command names to the entity type codes
142
     */
143
    public function __construct(
144
        InputInterface $input,
145
        ContainerInterface $container,
146
        LibraryLoader $libraryLoader,
147
        ConfigurationFactoryInterface $configurationFactory,
148
        CommandNames $commandNames,
149
        CommandNameToEntityTypeCode $commandNameToEntityTypeCodes
150
    ) {
151
152
        // set the passed instances
153
        $this->input = $input;
154
        $this->container = $container;
155
        $this->libraryLoader = $libraryLoader;
156
        $this->configurationFactory = $configurationFactory;
157
        $this->commandNames = $commandNames;
158
        $this->commandNameToEntityTypeCode = $commandNameToEntityTypeCodes;
159
    }
160
161
    /**
162
     * Factory implementation to create a new initialized configuration instance.
163
     *
164
     * If command line options are specified, they will always override the
165
     * values found in the configuration file.
166
     *
167
     * @return \TechDivision\Import\ConfigurationInterface The configuration instance
168
     * @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
169
     */
170
    public function load()
171
    {
172
173
        // load the actual vendor directory and entity type code
174
        $vendorDir = $this->getVendorDir();
175
176
        // the path of the JMS serializer directory, relative to the vendor directory
177
        $jmsDir = DIRECTORY_SEPARATOR . 'jms' . DIRECTORY_SEPARATOR . 'serializer' . DIRECTORY_SEPARATOR . 'src';
178
179
        // try to find the path to the JMS Serializer annotations
180
        if (!file_exists($annotationDir = $vendorDir . DIRECTORY_SEPARATOR . $jmsDir)) {
181
            // stop processing, if the JMS annotations can't be found
182
            throw new \Exception(
183
                sprintf(
184
                    'The jms/serializer libarary can not be found in one of "%s"',
185
                    implode(', ', $vendorDir)
186
                )
187
            );
188
        }
189
190
        // register the autoloader for the JMS serializer annotations
191
        \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
192
            'JMS\Serializer\Annotation',
193
            $annotationDir
194
        );
195
196
        // query whether or not, a configuration file has been specified
197
        if ($configuration = $this->input->getOption(InputOptionKeys::CONFIGURATION)) {
198
            // load the configuration from the file with the given filename
199
            $instance = $this->configurationFactory->factory($configuration);
200
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
201
        } elseif ($magentoEdition = $this->input->getOption(InputOptionKeys::MAGENTO_EDITION)) {
202
            // use the Magento Edition that has been specified as option
203
            $instance = $this->configurationFactory->factory($this->getDefaultConfiguration($magentoEdition, $this->getEntityTypeCode()));
204
205
            // override the Magento Edition
206
            $instance->setMagentoEdition($magentoEdition);
207
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
208
        } else {
209
            // finally, query whether or not the installation directory is a valid Magento root directory
210
            if (!$this->isMagentoRootDir($installationDir = $this->input->getOption(InputOptionKeys::INSTALLATION_DIR))) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class TechDivision\Import\Cli\SimpleConfigurationLoader as the method isMagentoRootDir() does only exist in the following sub-classes of TechDivision\Import\Cli\SimpleConfigurationLoader: TechDivision\Import\Cli\ConfigurationLoader. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
211
                throw new \Exception(
212
                    sprintf(
213
                        'Directory "%s" specified with option "--installation-dir" is not a valid Magento root directory',
214
                        $installationDir
215
                    )
216
                );
217
            }
218
219
            // load the composer file from the Magento root directory
220
            $composer = json_decode(file_get_contents($composerFile = sprintf('%s/composer.json', $installationDir)), true);
221
222
            // try to load and explode the Magento Edition identifier from the Composer name
223
            $explodedVersion = explode('/', $composer[MagentoConfigurationKeys::COMPOSER_EDITION_NAME_ATTRIBUTE]);
224
225
            // try to locate Magento Edition
226
            if (!isset($this->editionMappings[$possibleEdition = end($explodedVersion)])) {
227
                throw new \Exception(
228
                    sprintf(
229
                        '"%s" detected in "%s" is not a valid Magento Edition, please set Magento Edition with the "--magento-edition" option',
230
                        $possibleEdition,
231
                        $composerFile
232
                    )
233
                );
234
            }
235
236
            // if Magento Edition/Version are available, load them
237
            $magentoEdition = $this->editionMappings[$possibleEdition];
238
239
            // use the Magento Edition that has been detected by the installation directory
240
            $instance = $this->configurationFactory->factory($this->getDefaultConfiguration($magentoEdition, $this->getEntityTypeCode()));
241
242
            // override the Magento Edition, if NOT explicitly specified
243
            $instance->setMagentoEdition($magentoEdition);
244
        }
245
246
        // query whether or not a system name has been specified as command line option, if yes override the value from the configuration file
247 View Code Duplication
        if (($this->input->hasOptionSpecified(InputOptionKeys::SYSTEM_NAME) && $this->input->getOption(InputOptionKeys::SYSTEM_NAME)) || $instance->getSystemName() === null) {
0 ignored issues
show
Bug introduced by
The method hasOptionSpecified() does not exist on Symfony\Component\Console\Input\InputInterface. Did you maybe mean hasOption()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
            $instance->setSystemName($this->input->getOption(InputOptionKeys::SYSTEM_NAME));
249
        }
250
251
        // query whether or not a PID filename has been specified as command line option, if yes override the value from the configuration file
252 View Code Duplication
        if (($this->input->hasOptionSpecified(InputOptionKeys::PID_FILENAME) && $this->input->getOption(InputOptionKeys::PID_FILENAME)) || $instance->getPidFilename() === null) {
0 ignored issues
show
Bug introduced by
The method hasOptionSpecified() does not exist on Symfony\Component\Console\Input\InputInterface. Did you maybe mean hasOption()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
            $instance->setPidFilename($this->input->getOption(InputOptionKeys::PID_FILENAME));
254
        }
255
256
        // query whether or not a Magento installation directory has been specified as command line option, if yes override the value from the configuration file
257 View Code Duplication
        if (($this->input->hasOptionSpecified(InputOptionKeys::INSTALLATION_DIR) && $this->input->getOption(InputOptionKeys::INSTALLATION_DIR)) || $instance->getInstallationDir() === null) {
0 ignored issues
show
Bug introduced by
The method hasOptionSpecified() does not exist on Symfony\Component\Console\Input\InputInterface. Did you maybe mean hasOption()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
258
            $instance->setInstallationDir($this->input->getOption(InputOptionKeys::INSTALLATION_DIR));
259
        }
260
261
        // query whether or not a Magento edition has been specified as command line option, if yes override the value from the configuration file
262 View Code Duplication
        if (($this->input->hasOptionSpecified(InputOptionKeys::MAGENTO_EDITION) && $this->input->getOption(InputOptionKeys::MAGENTO_EDITION)) || $instance->getMagentoEdition() === null) {
0 ignored issues
show
Bug introduced by
The method hasOptionSpecified() does not exist on Symfony\Component\Console\Input\InputInterface. Did you maybe mean hasOption()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
            $instance->setMagentoEdition($this->input->getOption(InputOptionKeys::MAGENTO_EDITION));
264
        }
265
266
        // query whether or not a directory for the source files has been specified as command line option, if yes override the value from the configuration file
267 View Code Duplication
        if (($this->input->hasOptionSpecified(InputOptionKeys::SOURCE_DIR) && $this->input->getOption(InputOptionKeys::SOURCE_DIR)) || $instance->getSourceDir() === null) {
0 ignored issues
show
Bug introduced by
The method hasOptionSpecified() does not exist on Symfony\Component\Console\Input\InputInterface. Did you maybe mean hasOption()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
268
            $instance->setSourceDir($this->input->getOption(InputOptionKeys::SOURCE_DIR));
269
        }
270
271
        // return the initialized configuration instance
272
        return $instance;
273
    }
274
275
    /**
276
     * Return's the DI container instance.
277
     *
278
     * @return \Symfony\Component\DependencyInjection\ContainerInterface The DI container instance
279
     */
280
    protected function getContainer()
281
    {
282
        return $this->container;
283
    }
284
285
    /**
286
     * Return's the absolute path to the actual vendor directory.
287
     *
288
     * @return string The absolute path to the actual vendor directory
289
     * @throws \Exception Is thrown, if none of the possible vendor directories can be found
290
     */
291
    protected function getVendorDir()
292
    {
293
        return $this->getContainer()->getParameter(DependencyInjectionKeys::CONFIGURATION_VENDOR_DIR);
294
    }
295
296
    /**
297
     * Return's the actual command name.
298
     *
299
     * @return string The actual command name
300
     */
301
    protected function getCommandName()
302
    {
303
        return $this->input->getArgument('command');
304
    }
305
306
    /**
307
     * Return's the command's entity type code.
308
     *
309
     * @return string The command's entity type code
310
     * @throws \Exception Is thrown, if the command name can not be mapped
311
     */
312
    protected function getEntityTypeCode()
313
    {
314
315
        // try to map the command name to a entity type code
316
        if (array_key_exists($commandName = $this->getCommandName(), $this->commandNameToEntityTypeCode)) {
317
            return $this->commandNameToEntityTypeCode[$commandName];
318
        }
319
320
        // throw an exception if not possible
321
        throw new \Exception(sprintf('Can\' map command name %s to a entity type', $commandName));
322
    }
323
324
    /**
325
     * Return's the default configuration for the passed Magento Edition and the actual entity type.
326
     *
327
     * @param string $magentoEdition The Magento Edition to return the configuration for
328
     * @param string $entityTypeCode The entity type code to use
329
     *
330
     * @return string The path to the default configuration
331
     */
332
    protected function getDefaultConfiguration($magentoEdition, $entityTypeCode)
333
    {
334
        return sprintf(
335
            '%s/%s/etc/%s.json',
336
            $this->getVendorDir(),
337
            $this->getDefaultConfigurationLibrary(
338
                $magentoEdition,
339
                $entityTypeCode
340
            ),
341
            $this->getDefaultConfigurationFile($entityTypeCode)
342
        );
343
    }
344
345
    /**
346
     * Return's the name of the default configuration file.
347
     *
348
     * @param string $entityTypeCode The entity type code to return the default configuration file for
349
     *
350
     * @return string The name of the entity type's default configuration file
351
     * @throws \Exception
352
     */
353
    protected function getDefaultConfigurationFile($entityTypeCode)
354
    {
355
356
        // query whether or not a default configuration file for the passed entity type code exists
357
        if (isset($this->configurationFileMappings[$entityTypeCode])) {
358
            return $this->configurationFileMappings[$entityTypeCode];
359
        }
360
361
        // throw an exception, if no default configuration file for the passed entity type is available
362
        throw new \Exception(
363
            sprintf(
364
                'Can\'t find a default configuration file for entity Type Code \'%s\' (MUST be one of catalog_product, catalog_product_price, catalog_product_inventory, catalog_category or eav_attribute)',
365
                $entityTypeCode
366
            )
367
        );
368
    }
369
370
    /**
371
     * Return's the Magento Edition and entity type's specific default library that contains
372
     * the configuration file.
373
     *
374
     * @param string $magentoEdition The Magento Edition to return the default library for
375
     * @param string $entityTypeCode The entity type code to return the default library file for
376
     *
377
     * @return string The name of the library that contains the default configuration file for the passed Magento Edition and entity type code
378
     * @throws \Exception Is thrown, if no default configuration for the passed entity type code is available
379
     */
380
    protected function getDefaultConfigurationLibrary($magentoEdition, $entityTypeCode)
381
    {
382
383
        // query whether or not, a default configuration file for the passed entity type is available
384
        if (isset($this->defaultConfigurations[$edition = strtolower($magentoEdition)])) {
385
            if (isset($this->defaultConfigurations[$edition][$entityTypeCode])) {
386
                return $this->defaultConfigurations[$edition][$entityTypeCode];
387
            }
388
389
            // throw an exception, if the passed entity type is not supported
390
            throw new \Exception(
391
                sprintf(
392
                    'Entity Type Code \'%s\' not supported by entity type code \'%s\' (MUST be one of catalog_product, catalog_category or eav_attribute)',
393
                    $edition,
394
                    $entityTypeCode
395
                )
396
            );
397
        }
398
399
        // throw an exception, if the passed edition is not supported
400
        throw new \Exception(
401
            sprintf(
402
                'Default configuration for Magento \'%s\' not supported (MUST be one of CE or EE)',
403
                $magentoEdition
404
            )
405
        );
406
    }
407
}
408