Completed
Push — master ( c0f7f6...e96440 )
by Tim
11s
created

AbstractImportCommand   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 341
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 17
c 5
b 0
f 0
lcom 1
cbo 12
dl 0
loc 341
ccs 0
cts 225
cp 0
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
B configure() 0 103 1
D execute() 0 127 9
A getExtensionLibraries() 0 9 1
B getVendorDir() 0 25 3
A getDefaultImportDir() 0 4 1
A getDefaultConfiguration() 0 8 1
A getDefaultOperation() 0 4 1
1
<?php
2
3
/**
4
 * TechDivision\Import\Cli\Command\ImportCommandTrait
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\Command;
22
23
use Monolog\Logger;
24
use Monolog\Handler\ErrorLogHandler;
25
use TechDivision\Import\Utils\LoggerKeys;
26
use TechDivision\Import\Utils\OperationKeys;
27
use TechDivision\Import\ConfigurationInterface;
28
use TechDivision\Import\App\Simple;
29
use TechDivision\Import\App\Utils\SynteticServiceKeys;
30
use TechDivision\Import\Cli\ConfigurationFactory;
31
use TechDivision\Import\Configuration\Jms\Configuration;
32
use TechDivision\Import\Configuration\Jms\Configuration\Database;
33
use TechDivision\Import\Configuration\Jms\Configuration\LoggerFactory;
34
use Symfony\Component\Config\FileLocator;
35
use Symfony\Component\Console\Command\Command;
36
use Symfony\Component\Console\Input\InputOption;
37
use Symfony\Component\Console\Input\InputArgument;
38
use Symfony\Component\Console\Input\InputInterface;
39
use Symfony\Component\Console\Output\OutputInterface;
40
use Symfony\Component\DependencyInjection\ContainerBuilder;
41
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
42
43
/**
44
 * The abstract import command implementation.
45
 *
46
 * @author    Tim Wagner <[email protected]>
47
 * @copyright 2016 TechDivision GmbH <[email protected]>
48
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
49
 * @link      https://github.com/techdivision/import-cli-simple
50
 * @link      http://www.techdivision.com
51
 */
52
abstract class AbstractImportCommand extends Command implements ImportCommandInterface
53
{
54
55
    /**
56
     * Configures the current command.
57
     *
58
     * @return void
59
     * @see \Symfony\Component\Console\Command\Command::configure()
60
     */
61
    protected function configure()
62
    {
63
64
        // initialize the command with the required/optional options
65
        $this->addArgument(
66
            InputArgumentKeys::OPERATION_NAME,
67
            InputArgument::OPTIONAL,
68
            'The operation that has to be used for the import, one of "add-update", "replace" or "delete"',
69
            $this->getDefaultOperation()
70
        )
71
        ->addOption(
72
            InputOptionKeys::CONFIGURATION,
73
            null,
74
            InputOption::VALUE_REQUIRED,
75
            'Specify the pathname to the configuration file to use',
76
            $this->getDefaultConfiguration()
77
        )
78
        ->addOption(
79
            InputOptionKeys::INSTALLATION_DIR,
80
            null,
81
            InputOption::VALUE_REQUIRED,
82
            'The Magento installation directory to which the files has to be imported'
83
        )
84
        ->addOption(
85
            InputOptionKeys::ENTITY_TYPE_CODE,
86
            null,
87
            InputOption::VALUE_REQUIRED,
88
            'Specify the entity type code to use'
89
        )
90
        ->addOption(
91
            InputOptionKeys::SOURCE_DIR,
92
            null,
93
            InputOption::VALUE_REQUIRED,
94
            'The directory that has to be watched for new files'
95
        )
96
        ->addOption(
97
            InputOptionKeys::TARGET_DIR,
98
            null,
99
            InputOption::VALUE_REQUIRED,
100
            'The target directory with the files that has been imported'
101
        )
102
        ->addOption(
103
            InputOptionKeys::MAGENTO_EDITION,
104
            null,
105
            InputOption::VALUE_REQUIRED,
106
            'The Magento edition to be used, either one of CE or EE'
107
        )
108
        ->addOption(
109
            InputOptionKeys::MAGENTO_VERSION,
110
            null,
111
            InputOption::VALUE_REQUIRED,
112
            'The Magento version to be used, e. g. 2.1.2'
113
        )
114
        ->addOption(
115
            InputOptionKeys::SOURCE_DATE_FORMAT,
116
            null,
117
            InputOption::VALUE_REQUIRED,
118
            'The date format used in the CSV file(s)'
119
        )
120
        ->addOption(
121
            InputOptionKeys::USE_DB_ID,
122
            null,
123
            InputOption::VALUE_REQUIRED,
124
            'The explicit database ID used for the actual import process'
125
        )
126
        ->addOption(
127
            InputOptionKeys::DB_PDO_DSN,
128
            null,
129
            InputOption::VALUE_REQUIRED,
130
            'The DSN used to connect to the Magento database where the data has to be imported, e. g. mysql:host=127.0.0.1;dbname=magento;charset=utf8'
131
        )
132
        ->addOption(
133
            InputOptionKeys::DB_USERNAME,
134
            null,
135
            InputOption::VALUE_REQUIRED,
136
            'The username used to connect to the Magento database'
137
        )
138
        ->addOption(
139
            InputOptionKeys::DB_PASSWORD,
140
            null,
141
            InputOption::VALUE_REQUIRED,
142
            'The password used to connect to the Magento database'
143
        )
144
        ->addOption(
145
            InputOptionKeys::LOG_LEVEL,
146
            null,
147
            InputOption::VALUE_REQUIRED,
148
            'The log level to use'
149
        )
150
        ->addOption(
151
            InputOptionKeys::DEBUG_MODE,
152
            null,
153
            InputOption::VALUE_REQUIRED,
154
            'Whether use the debug mode or not'
155
        )
156
        ->addOption(
157
            InputOptionKeys::PID_FILENAME,
158
            null,
159
            InputOption::VALUE_REQUIRED,
160
            'The explicit PID filename to use',
161
            sprintf('%s/%s', sys_get_temp_dir(), Configuration::PID_FILENAME)
162
        );
163
    }
164
165
    /**
166
     * Executes the current command.
167
     *
168
     * This method is not abstract because you can use this class
169
     * as a concrete class. In this case, instead of defining the
170
     * execute() method, you set the code to execute by passing
171
     * a Closure to the setCode() method.
172
     *
173
     * @param \Symfony\Component\Console\Input\InputInterface   $input  An InputInterface instance
174
     * @param \Symfony\Component\Console\Output\OutputInterface $output An OutputInterface instance
175
     *
176
     * @return null|int null or 0 if everything went fine, or an error code
177
     * @throws \LogicException When this abstract method is not implemented
178
     * @see \Symfony\Component\Console\Command\Command::execute()
179
     */
180
    protected function execute(InputInterface $input, OutputInterface $output)
181
    {
182
183
        // load the actual vendor directory
184
        $vendorDirectory = $this->getVendorDir();
185
186
        // the path of the JMS serializer directory, relative to the vendor directory
187
        $jmsDirectory = DIRECTORY_SEPARATOR . 'jms' . DIRECTORY_SEPARATOR . 'serializer' . DIRECTORY_SEPARATOR . 'src';
188
189
        // try to find the path to the JMS Serializer annotations
190
        if (!file_exists($annotationDirectory = $vendorDirectory . DIRECTORY_SEPARATOR . $jmsDirectory)) {
191
            // stop processing, if the JMS annotations can't be found
192
            throw new \Exception(
193
                sprintf(
194
                    'The jms/serializer libarary can not be found in one of %s',
195
                    implode(', ', $this->getVendorDir())
196
                )
197
            );
198
        }
199
200
        // register the autoloader for the JMS serializer annotations
201
        \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
202
            'JMS\Serializer\Annotation',
203
            $annotationDirectory
204
        );
205
206
        // load the importer configuration and set the entity type code
207
        $configuration = ConfigurationFactory::load($input);
208
209
        // initialize the DI container
210
        $container = new ContainerBuilder();
211
212
        // initialize the default loader and load the DI configuration for the this library
213
        $defaultLoader = new XmlFileLoader($container, new FileLocator($vendorDirectory));
214
215
        // load the DI configuration for all the extension libraries
216
        foreach ($this->getExtensionLibraries($configuration) as $library) {
217
            if (file_exists($diConfiguration = sprintf('%s/%s/symfony/Resources/config/services.xml', $vendorDirectory, $library))) {
218
                $defaultLoader->load($diConfiguration);
219
            } else {
220
                throw new \Exception(
221
                    sprintf(
222
                        'Can\'t load DI configuration for library %s',
223
                        $diConfiguration
224
                    )
225
                );
226
            }
227
        }
228
229
        // register autoloaders for additional vendor directories
230
        $customLoader = new XmlFileLoader($container, new FileLocator());
231
        foreach ($configuration->getAdditionalVendorDirs() as $additionalVendorDir) {
232
            // load the vendor directory's auto loader
233
            if (file_exists($autoLoader = $additionalVendorDir->getVendorDir() . '/autoload.php')) {
234
                require $autoLoader;
235
            } else {
236
                throw new \Exception(
237
                    sprintf(
238
                        'Can\'t find autoloader in configured additional vendor directory %s',
239
                        $additionalVendorDir->getVendorDir()
240
                    )
241
                );
242
            }
243
244
            // try to load the DI configuration for the configured extension libraries
245
            foreach ($additionalVendorDir->getLibraries() as $library) {
246
                // prepare the DI configuration filename
247
                $diConfiguration = realpath(sprintf('%s/%s/symfony/Resources/config/services.xml', $additionalVendorDir->getVendorDir(), $library));
248
                // try to load the filename
249
                if (file_exists($diConfiguration)) {
250
                    $customLoader->load($diConfiguration);
251
                } else {
252
                    throw new \Exception(
253
                        sprintf(
254
                            'Can\'t load DI configuration for library %s',
255
                            $diConfiguration
256
                        )
257
                    );
258
                }
259
            }
260
        }
261
262
        // add the configuration as well as input/outut instances to the DI container
263
        $container->set(SynteticServiceKeys::INPUT, $input);
264
        $container->set(SynteticServiceKeys::OUTPUT, $output);
265
        $container->set(SynteticServiceKeys::CONFIGURATION, $configuration);
266
        $container->set(SynteticServiceKeys::APPLICATION, $this->getApplication());
0 ignored issues
show
Bug introduced by
It seems like $this->getApplication() can be null; however, set() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
267
268
        // initialize the PDO connection
269
        $dsn = $configuration->getDatabase()->getDsn();
270
        $username = $configuration->getDatabase()->getUsername();
271
        $password = $configuration->getDatabase()->getPassword();
272
        $connection = new \PDO($dsn, $username, $password);
273
        $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
274
275
        // add the PDO connection to the DI container
276
        $container->set(SynteticServiceKeys::CONNECTION, $connection);
277
278
        // initialize the system logger
279
        $loggers = array();
280
281
        // initialize the default system logger
282
        $systemLogger = new Logger('techdivision/import');
283
        $systemLogger->pushHandler(
284
            new ErrorLogHandler(
285
                ErrorLogHandler::OPERATING_SYSTEM,
286
                $configuration->getLogLevel()
287
            )
288
        );
289
290
        // add it to the array
291
        $loggers[LoggerKeys::SYSTEM] = $systemLogger;
292
293
        // append the configured loggers or override the default one
294
        foreach ($configuration->getLoggers() as $loggerConfiguration) {
295
            // load the factory class that creates the logger instance
296
            $loggerFactory = $loggerConfiguration->getFactory();
297
            // create the logger instance and add it to the available loggers
298
            $loggers[$loggerConfiguration->getName()] = $loggerFactory::factory($loggerConfiguration);
299
        }
300
301
        // add the system loggers to the DI container
302
        $container->set(SynteticServiceKeys::LOGGERS, $loggers);
0 ignored issues
show
Documentation introduced by
$loggers is of type array<integer|string,object<Monolog\Logger>>, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
303
304
        // start the import process
305
        $container->get(SynteticServiceKeys::SIMPLE)->process();
306
    }
307
308
    /**
309
     * Return's the array with the magento specific extension libraries.
310
     *
311
     * @param \TechDivision\Import\ConfigurationInterface $configuration The configuration instance
312
     *
313
     * @return array The magento edition specific extension libraries
314
     */
315
    public function getExtensionLibraries(ConfigurationInterface $configuration)
316
    {
317
318
        // return the array with the Magento Edition specific libraries
319
        return array_merge(
320
            Simple::getDefaultLibraries($configuration->getMagentoEdition()),
321
            $configuration->getExtensionLibraries()
322
        );
323
    }
324
325
    /**
326
     * Return's the absolute path to the actual vendor directory.
327
     *
328
     * @return string The absolute path to the actual vendor directory
329
     * @throws \Exception Is thrown, if none of the possible vendor directories can be found
330
     */
331
    public function getVendorDir()
332
    {
333
334
        // the possible paths to the vendor directory
335
        $possibleVendorDirectories = array(
336
            dirname(dirname(dirname(dirname(dirname(__DIR__))))) . DIRECTORY_SEPARATOR . 'vendor',
337
            dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'vendor'
338
        );
339
340
        // try to find the path to the JMS Serializer annotations
341
        foreach ($possibleVendorDirectories as $possibleVendorDirectory) {
342
            // return the directory as vendor directory if available
343
            if (is_dir($possibleVendorDirectory)) {
344
                return $possibleVendorDirectory;
345
            }
346
        }
347
348
        // stop processing, if NO vendor directory is available
349
        throw new \Exception(
350
            sprintf(
351
                'None of the possible vendor directories %s is available',
352
                implode(', ', $possibleVendorDirectories)
353
            )
354
        );
355
    }
356
357
    /**
358
     * Return's the given entity type's specific default configuration file.
359
     *
360
     * @return string The name of the library to query for the default configuration file
361
     * @throws \Exception Is thrown, if no default configuration for the passed entity type is available
362
     */
363
    public function getDefaultImportDir()
364
    {
365
        return sprintf('%s/var/importexport', $this->getMagentoInstallationDir());
0 ignored issues
show
Bug introduced by
The method getMagentoInstallationDir() does not seem to exist on object<TechDivision\Impo...\AbstractImportCommand>.

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...
366
    }
367
368
    /**
369
     * Return's the given entity type's specific default configuration file.
370
     *
371
     * @return string The name of the library to query for the default configuration file
372
     * @throws \Exception Is thrown, if no default configuration for the passed entity type is available
373
     */
374
    public function getDefaultConfiguration()
375
    {
376
        return sprintf(
377
            '%s/%s/etc/techdivision-import.json',
378
            $this->getVendorDir(),
379
            Simple::getDefaultConfiguration($this->getEntityTypeCode())
380
        );
381
    }
382
383
    /**
384
     * Return's the default operation.
385
     *
386
     * @return string The default operation
387
     */
388
    public function getDefaultOperation()
389
    {
390
        return OperationKeys::ADD_UPDATE;
391
    }
392
}
393