Completed
Push — master ( 1235b0...90fadd )
by Tim
15s
created

ConfigurationLoader   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 350
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 31
c 3
b 0
f 0
lcom 2
cbo 6
dl 0
loc 350
ccs 0
cts 135
cp 0
rs 9.8

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getMagentoDbConnection() 0 23 2
F load() 0 130 19
A isMagentoRootDir() 0 4 1
A getMagentoEnv() 0 4 1
B newDatabaseConfiguration() 0 25 3
A newDsn() 0 4 1
A getDefaultLibraries() 0 16 2
A getDefaultDirectory() 0 16 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Cli\ConfigurationLoader
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 Psr\Log\LogLevel;
24
use Rhumsaa\Uuid\Uuid;
25
use TechDivision\Import\App\Simple;
26
use TechDivision\Import\Utils\EntityTypeCodes;
27
use TechDivision\Import\Cli\Command\InputOptionKeys;
28
use TechDivision\Import\Cli\Command\InputArgumentKeys;
29
use TechDivision\Import\Cli\Configuration\LibraryLoader;
30
use TechDivision\Import\Cli\Utils\MagentoConfigurationKeys;
31
use TechDivision\Import\Configuration\Jms\Configuration;
32
use TechDivision\Import\Configuration\Jms\Configuration\Database;
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 ConfigurationLoader extends SimpleConfigurationLoader
44
{
45
46
    /**
47
     * The array with the default entity type code => import directory mappings.
48
     *
49
     * @var array
50
     */
51
    protected $defaultDirectories = array(
52
        EntityTypeCodes::CATALOG_PRODUCT  => 'products',
53
        EntityTypeCodes::CATALOG_CATEGORY => 'categories',
54
        EntityTypeCodes::EAV_ATTRIBUTE    => 'attributes'
55
    );
56
57
    /**
58
     * The Magento Edition specific default libraries.
59
     *
60
     * @var array
61
     */
62
    protected $defaultLibraries = array(
63
        'ce' => array(
64
            'techdivision/import-app-simple',
65
            'techdivision/import',
66
            'techdivision/import-attribute',
67
            'techdivision/import-category',
68
            'techdivision/import-product',
69
            'techdivision/import-product-bundle',
70
            'techdivision/import-product-link',
71
            'techdivision/import-product-media',
72
            'techdivision/import-product-variant'
73
        ),
74
        'ee' => array(
75
            'techdivision/import-app-simple',
76
            'techdivision/import',
77
            'techdivision/import-ee',
78
            'techdivision/import-attribute',
79
            'techdivision/import-category',
80
            'techdivision/import-category-ee',
81
            'techdivision/import-product',
82
            'techdivision/import-product-ee',
83
            'techdivision/import-product-bundle',
84
            'techdivision/import-product-bundle-ee',
85
            'techdivision/import-product-link',
86
            'techdivision/import-product-link-ee',
87
            'techdivision/import-product-media',
88
            'techdivision/import-product-media-ee',
89
            'techdivision/import-product-variant',
90
            'techdivision/import-product-variant-ee'
91
        )
92
    );
93
94
    /**
95
     * Factory implementation to create a new initialized configuration instance.
96
     *
97
     * If command line options are specified, they will always override the
98
     * values found in the configuration file.
99
     *
100
     * @return \TechDivision\Import\Cli\Configuration The configuration instance
101
     * @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
102
     */
103
    public function load()
104
    {
105
106
        // load the configuration instance
107
        $instance = parent::load();
108
109
        // query whether or not an operation name has been specified as command line
110
        // option, if yes override the value from the configuration file
111
        if ($operationName = $this->input->getArgument(InputArgumentKeys::OPERATION_NAME)) {
112
            $instance->setOperationName($operationName);
113
        }
114
115
        // query whether or not a Magento version has been specified as command line
116
        // option, if yes override the value from the configuration file
117
        if ($magentoVersion = $this->input->getOption(InputOptionKeys::MAGENTO_VERSION)) {
118
            $instance->setMagentoVersion($magentoVersion);
119
        }
120
121
        // query whether or not a directory containing the imported files has been specified as command line
122
        // option, if yes override the value from the configuration file
123
        if ($targetDir = $this->input->getOption(InputOptionKeys::TARGET_DIR)) {
124
            $instance->setTargetDir($targetDir);
125
        }
126
127
        // query whether or not a directory containing the archived imported files has been specified as command line
128
        // option, if yes override the value from the configuration file
129
        if ($archiveDir = $this->input->getOption(InputOptionKeys::ARCHIVE_DIR)) {
130
            $instance->setArchiveDir($archiveDir);
131
        }
132
133
        // query whether or not the debug mode has been specified as command line
134
        // option, if yes override the value from the configuration file
135
        if ($archiveArtefacts = $this->input->getOption(InputOptionKeys::ARCHIVE_ARTEFACTS)) {
136
            $instance->setArchiveArtefacts($instance->mapBoolean($archiveArtefacts));
137
        }
138
139
        // query whether or not a source date format has been specified as command
140
        // line  option, if yes override the value from the configuration file
141
        if ($sourceDateFormat = $this->input->getOption(InputOptionKeys::SOURCE_DATE_FORMAT)) {
142
            $instance->setSourceDateFormat($sourceDateFormat);
143
        }
144
145
        // query whether or not the debug mode has been specified as command line
146
        // option, if yes override the value from the configuration file
147
        if ($debugMode = $this->input->getOption(InputOptionKeys::DEBUG_MODE)) {
148
            $instance->setDebugMode($instance->mapBoolean($debugMode));
149
        }
150
151
        // query whether or not the log level has been specified as command line
152
        // option, if yes override the value from the configuration file
153
        if ($logLevel = $this->input->getOption(InputOptionKeys::LOG_LEVEL)) {
154
            $instance->setLogLevel($logLevel);
155
        }
156
157
        // query whether or not we've an valid Magento root directory specified
158
        if ($this->isMagentoRootDir($installationDir = $instance->getInstallationDir())) {
159
            // if yes, add the database configuration
160
            $instance->addDatabase($this->getMagentoDbConnection($installationDir));
161
162
            // add the source directory if NOT specified in the configuration file
163
            if ($instance->getSourceDir() === null) {
164
                $instance->setSourceDir($sourceDir = sprintf('%s/var/importexport', $installationDir));
165
            }
166
167
            // add the target directory if NOT specified in the configuration file
168
            if ($instance->getTargetDir() === null) {
169
                $instance->setTargetDir($sourceDir);
0 ignored issues
show
Bug introduced by
The variable $sourceDir does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
170
            }
171
        }
172
173
        // query whether or not a DB ID has been specified as command line
174
        // option, if yes override the value from the configuration file
175
        if ($useDbId = $this->input->getOption(InputOptionKeys::USE_DB_ID)) {
176
            $instance->setUseDbId($useDbId);
177
        } else {
178
            // query whether or not a PDO DSN has been specified as command line
179
            // option, if yes override the value from the configuration file
180
            if ($dsn = $this->input->getOption(InputOptionKeys::DB_PDO_DSN)) {
181
                // first REMOVE all other database configurations
182
                $instance->clearDatabases();
183
184
                // add the database configuration
185
                $instance->addDatabase(
186
                    $this->newDatabaseConfiguration(
187
                        $dsn,
188
                        $this->input->getOption(InputOptionKeys::DB_USERNAME),
189
                        $this->input->getOption(InputOptionKeys::DB_PASSWORD)
190
                    )
191
                );
192
            }
193
        }
194
195
        // extend the plugins with the main configuration instance
196
        /** @var \TechDivision\Import\Cli\Configuration\Subject $subject */
197
        foreach ($instance->getPlugins() as $plugin) {
198
            // set the configuration instance on the plugin
199
            $plugin->setConfiguration($instance);
200
201
            // query whether or not the plugin has subjects configured
202
            if ($subjects = $plugin->getSubjects()) {
203
                // extend the plugin's subjects with the main configuration instance
204
                /** @var \TechDivision\Import\Cli\Configuration\Subject $subject */
205
                foreach ($subjects as $subject) {
206
                    // set the configuration instance on the subject
207
                    $subject->setConfiguration($instance);
208
                }
209
            }
210
        }
211
212
        // query whether or not the debug mode is enabled and log level
213
        // has NOT been overwritten with a commandline option
214
        if ($instance->isDebugMode() && !$this->input->getOption(InputOptionKeys::LOG_LEVEL)) {
215
            // set debug log level, if log level has NOT been overwritten on command line
216
            $instance->setLogLevel(LogLevel::DEBUG);
217
        }
218
219
        // prepend the array with the Magento Edition specific core libraries
220
        $instance->setExtensionLibraries(
221
            array_merge(
222
                $this->getDefaultLibraries($instance->getMagentoEdition()),
223
                $instance->getExtensionLibraries()
224
            )
225
        );
226
227
        // load the extension libraries, if configured
228
        $this->libraryLoader->load($instance);
229
230
        // return the initialized configuration instance
231
        return $instance;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $instance; (TechDivision\Import\Conf...ation\Jms\Configuration) is incompatible with the return type declared by the interface TechDivision\Import\Cli\...onLoaderInterface::load of type TechDivision\Import\Cli\Configuration.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
232
    }
233
234
    /**
235
     * Query whether or not, the passed directory is a Magento root directory.
236
     *
237
     * @param string $dir The directory to query
238
     *
239
     * @return boolean TRUE if the directory is a Magento root directory, else FALSE
240
     */
241
    protected function isMagentoRootDir($dir)
242
    {
243
        return is_file($this->getMagentoEnv($dir));
244
    }
245
246
    /**
247
     * Return's the path to the Magento file with the environment configuration.
248
     *
249
     * @param string $dir The path to the Magento root directory
250
     *
251
     * @return string The path to the Magento file with the environment configuration
252
     */
253
    protected function getMagentoEnv($dir)
254
    {
255
        return sprintf('%s/app/etc/env.php', $dir);
256
    }
257
258
    /**
259
     * Return's the requested Magento DB connction data.
260
     *
261
     * @param string $dir            The path to the Magento root directory
262
     * @param string $connectionName The connection name to return the data for
263
     *
264
     * @return array The connection data
265
     * @throws \Exception Is thrown, if the requested DB connection is not available
266
     */
267
    protected function getMagentoDbConnection($dir, $connectionName = 'default')
268
    {
269
270
        // load the magento environment
271
        $env = require $this->getMagentoEnv($dir);
272
273
        // query whether or not, the requested connection is available
274
        if (isset($env[MagentoConfigurationKeys::DB][MagentoConfigurationKeys::CONNECTION][$connectionName])) {
275
            // load the connection data
276
            $connection = $env[MagentoConfigurationKeys::DB][MagentoConfigurationKeys::CONNECTION][$connectionName];
277
278
            // create and return a new database configuration
279
            return $this->newDatabaseConfiguration(
280
                $this->newDsn($connection[MagentoConfigurationKeys::HOST], $connection[MagentoConfigurationKeys::DBNAME]),
281
                $connection[MagentoConfigurationKeys::USERNAME],
282
                $connection[MagentoConfigurationKeys::PASSWORD],
283
                false
284
            );
285
        }
286
287
        // throw an execption if not
288
        throw new \Exception(sprintf('Requested Magento DB connection "%s" not found in Magento "%s"', $connectionName, $dir));
289
    }
290
291
    /**
292
     * Create's and return's a new database configuration instance, initialized with
293
     * the passed values.
294
     *
295
     * @param string      $dsn      The DSN to use
296
     * @param string      $username The username to  use
297
     * @param string|null $password The passed to use
298
     * @param boolean     $default  TRUE if this should be the default connection
299
     * @param string      $id       The ID to use
300
     *
301
     * @return \TechDivision\Import\Configuration\Jms\Configuration\Database The database configuration instance
302
     */
303
    protected function newDatabaseConfiguration($dsn, $username = 'root', $password = null, $default = true, $id = null)
304
    {
305
306
        // initialize a new database configuration
307
        $database = new Database();
308
        $database->setDsn($dsn);
309
        $database->setDefault($default);
310
        $database->setUsername($username);
311
312
        // query whether or not an ID has been passed
313
        if ($id === null) {
314
            $id = Uuid::uuid4()->__toString();
315
        }
316
317
        // set the ID
318
        $database->setId($id);
319
320
        // query whether or not a password has been passed
321
        if ($password) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $password of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
322
            $database->setPassword($password);
323
        }
324
325
        // return the database configuration
326
        return $database;
327
    }
328
329
    /**
330
     * Create's and return's a new DSN from the passed values.
331
     *
332
     * @param string $host    The host to use
333
     * @param string $dbName  The database name to use
334
     * @param string $charset The charset to use
335
     *
336
     * @return string The DSN
337
     */
338
    protected function newDsn($host, $dbName, $charset = 'utf8')
339
    {
340
        return sprintf('mysql:host=%s;dbname=%s;charset=%s', $host, $dbName, $charset);
341
    }
342
343
    /**
344
     * Return's the Magento Edition specific default libraries. Supported Magento Editions are CE or EE.
345
     *
346
     * @param string $magentoEdition The Magento Edition to return the libraries for
347
     *
348
     * @return array The Magento Edition specific default libraries
349
     * @throws \Exception Is thrown, if the passed Magento Edition is NOT supported
350
     */
351
    protected function getDefaultLibraries($magentoEdition)
352
    {
353
354
        // query whether or not, default libraries for the passed edition are available
355
        if (isset($this->defaultLibraries[$edition = strtolower($magentoEdition)])) {
356
            return $this->defaultLibraries[$edition];
357
        }
358
359
        // throw an exception, if the passed edition is not supported
360
        throw new \Exception(
361
            sprintf(
362
                'Default libraries for Magento \'%s\' not supported (MUST be one of CE or EE)',
363
                $magentoEdition
364
            )
365
        );
366
    }
367
368
    /**
369
     * Return's the entity types specific default import directory.
370
     *
371
     * @param string $entityTypeCode The entity type code to return the default import directory for
372
     *
373
     * @return string The default default import directory
374
     * @throws \Exception Is thrown, if no default import directory for the passed entity type code is available
375
     */
376
    protected function getDefaultDirectory($entityTypeCode)
377
    {
378
379
        // query whether or not, a default configuration file for the passed entity type is available
380
        if (isset($this->defaultDirectories[$entityTypeCode])) {
381
            return $this->defaultDirectories[$entityTypeCode];
382
        }
383
384
        // throw an exception, if the passed entity type is not supported
385
        throw new \Exception(
386
            sprintf(
387
                'Entity Type Code \'%s\' not supported (MUST be one of catalog_product or catalog_category)',
388
                $entityTypeCode
389
            )
390
        );
391
    }
392
}
393