Completed
Pull Request — master (#75)
by Tim
05:17
created

AbstractImportCommand::getExtensionLibraries()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
ccs 0
cts 7
cp 0
cc 1
eloc 4
nc 1
nop 1
crap 2
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_OPTIONAL,
82
            'The Magento installation directory to which the files has to be imported',
83
            $this->getMagentoInstallationDir()
84
        )
85
        ->addOption(
86
            InputOptionKeys::ENTITY_TYPE_CODE,
87
            null,
88
            InputOption::VALUE_REQUIRED,
89
            'Specify the entity type code to use'
90
        )
91
        ->addOption(
92
            InputOptionKeys::SOURCE_DIR,
93
            null,
94
            InputOption::VALUE_REQUIRED,
95
            'The directory that has to be watched for new files'
96
        )
97
        ->addOption(
98
            InputOptionKeys::TARGET_DIR,
99
            null,
100
            InputOption::VALUE_REQUIRED,
101
            'The target directory with the files that has been imported'
102
        )
103
        ->addOption(
104
            InputOptionKeys::UTILITY_CLASS_NAME,
105
            null,
106
            InputOption::VALUE_REQUIRED,
107
            'The utility class name with the SQL statements'
108
        )
109
        ->addOption(
110
            InputOptionKeys::PREFIX,
111
            null,
112
            InputOption::VALUE_REQUIRED,
113
            'The prefix of the CSV source file(s) that has/have to be imported'
114
        )
115
        ->addOption(
116
            InputOptionKeys::MAGENTO_EDITION,
117
            null,
118
            InputOption::VALUE_REQUIRED,
119
            'The Magento edition to be used, either one of CE or EE'
120
        )
121
        ->addOption(
122
            InputOptionKeys::MAGENTO_VERSION,
123
            null,
124
            InputOption::VALUE_REQUIRED,
125
            'The Magento version to be used, e. g. 2.1.2'
126
        )
127
        ->addOption(
128
            InputOptionKeys::SOURCE_DATE_FORMAT,
129
            null,
130
            InputOption::VALUE_REQUIRED,
131
            'The date format used in the CSV file(s)'
132
        )
133
        ->addOption(
134
            InputOptionKeys::USE_DB_ID,
135
            null,
136
            InputOption::VALUE_REQUIRED,
137
            'The explicit database ID used for the actual import process'
138
        )
139
        ->addOption(
140
            InputOptionKeys::DB_PDO_DSN,
141
            null,
142
            InputOption::VALUE_REQUIRED,
143
            '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'
144
        )
145
        ->addOption(
146
            InputOptionKeys::DB_USERNAME,
147
            null,
148
            InputOption::VALUE_REQUIRED,
149
            'The username used to connect to the Magento database'
150
        )
151
        ->addOption(
152
            InputOptionKeys::DB_PASSWORD,
153
            null,
154
            InputOption::VALUE_REQUIRED,
155
            'The password used to connect to the Magento database'
156
        )
157
        ->addOption(
158
            InputOptionKeys::LOG_LEVEL,
159
            null,
160
            InputOption::VALUE_REQUIRED,
161
            'The log level to use'
162
        )
163
        ->addOption(
164
            InputOptionKeys::DEBUG_MODE,
165
            null,
166
            InputOption::VALUE_REQUIRED,
167
            'Whether use the debug mode or not'
168
        )
169
        ->addOption(
170
            InputOptionKeys::PID_FILENAME,
171
            null,
172
            InputOption::VALUE_REQUIRED,
173
            'The explicit PID filename to use',
174
            sprintf('%s/%s', sys_get_temp_dir(), Configuration::PID_FILENAME)
175
        );
176
    }
177
178
    /**
179
     * Executes the current command.
180
     *
181
     * This method is not abstract because you can use this class
182
     * as a concrete class. In this case, instead of defining the
183
     * execute() method, you set the code to execute by passing
184
     * a Closure to the setCode() method.
185
     *
186
     * @param \Symfony\Component\Console\Input\InputInterface   $input  An InputInterface instance
187
     * @param \Symfony\Component\Console\Output\OutputInterface $output An OutputInterface instance
188
     *
189
     * @return null|int null or 0 if everything went fine, or an error code
190
     * @throws \LogicException When this abstract method is not implemented
191
     * @see \Symfony\Component\Console\Command\Command::execute()
192
     */
193
    protected function execute(InputInterface $input, OutputInterface $output)
194
    {
195
196
        // load the actual vendor directory
197
        $vendorDirectory = $this->getVendorDir();
198
199
        // the path of the JMS serializer directory, relative to the vendor directory
200
        $jmsDirectory = DIRECTORY_SEPARATOR . 'jms' . DIRECTORY_SEPARATOR . 'serializer' . DIRECTORY_SEPARATOR . 'src';
201
202
        // try to find the path to the JMS Serializer annotations
203
        if (!file_exists($annotationDirectory = $vendorDirectory . DIRECTORY_SEPARATOR . $jmsDirectory)) {
204
            // stop processing, if the JMS annotations can't be found
205
            throw new \Exception(
206
                sprintf(
207
                    'The jms/serializer libarary can not be found in one of %s',
208
                    implode(', ', $this->getVendorDir())
209
                )
210
            );
211
        }
212
213
        // register the autoloader for the JMS serializer annotations
214
        \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
215
            'JMS\Serializer\Annotation',
216
            $annotationDirectory
217
        );
218
219
        // load the importer configuration and set the entity type code
220
        $configuration = ConfigurationFactory::load($input);
221
222
        // initialize the DI container
223
        $container = new ContainerBuilder();
224
225
        // initialize the default loader and load the DI configuration for the this library
226
        $defaultLoader = new XmlFileLoader($container, new FileLocator($vendorDirectory));
227
228
        // load the DI configuration for all the extension libraries
229
        foreach ($this->getExtensionLibraries($configuration) as $library) {
230
            if (file_exists($diConfiguration = sprintf('%s/%s/symfony/Resources/config/services.xml', $vendorDirectory, $library))) {
231
                $defaultLoader->load($diConfiguration);
232
            }
233
        }
234
235
        // register autoloaders for additional vendor directories
236
        $customLoader = new XmlFileLoader($container, new FileLocator());
237
        foreach ($configuration->getAdditionalVendorDirs() as $additionalVendorDir) {
238
            // load the vendor directory's auto loader
239
            if (file_exists($autoLoader = $additionalVendorDir->getVendorDir() . '/autoload.php')) {
240
                require $autoLoader;
241
            } else {
242
                throw new \Exception(
243
                    sprintf(
244
                        'Can\'t find autoloader in configured additional vendor directory %s',
245
                        $additionalVendorDir->getVendorDir()
246
                    )
247
                );
248
            }
249
250
            // try to load the DI configuration for the configured extension libraries
251
            foreach ($additionalVendorDir->getLibraries() as $library) {
252
                // prepare the DI configuration filename
253
                $diConfiguration = realpath(sprintf('%s/%s/symfony/Resources/config/services.xml', $additionalVendorDir->getVendorDir(), $library));
254
                // try to load the filename
255
                if (file_exists($diConfiguration)) {
256
                    $customLoader->load($diConfiguration);
257
                }
258
            }
259
        }
260
261
        // add the configuration as well as input/outut instances to the DI container
262
        $container->set(SynteticServiceKeys::INPUT, $input);
263
        $container->set(SynteticServiceKeys::OUTPUT, $output);
264
        $container->set(SynteticServiceKeys::CONFIGURATION, $configuration);
265
        $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...
266
267
        // initialize the PDO connection
268
        $dsn = $configuration->getDatabase()->getDsn();
269
        $username = $configuration->getDatabase()->getUsername();
270
        $password = $configuration->getDatabase()->getPassword();
271
        $connection = new \PDO($dsn, $username, $password);
272
        $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
273
274
        // add the PDO connection to the DI container
275
        $container->set(SynteticServiceKeys::CONNECTION, $connection);
276
277
        // initialize the system logger
278
        $loggers = array();
279
280
        // initialize the default system logger
281
        $systemLogger = new Logger('techdivision/import');
282
        $systemLogger->pushHandler(
283
            new ErrorLogHandler(
284
                ErrorLogHandler::OPERATING_SYSTEM,
285
                $configuration->getLogLevel()
286
            )
287
        );
288
289
        // add it to the array
290
        $loggers[LoggerKeys::SYSTEM] = $systemLogger;
291
292
        // append the configured loggers or override the default one
293
        foreach ($configuration->getLoggers() as $loggerConfiguration) {
294
            // load the factory class that creates the logger instance
295
            $loggerFactory = $loggerConfiguration->getFactory();
296
            // create the logger instance and add it to the available loggers
297
            $loggers[$loggerConfiguration->getName()] = $loggerFactory::factory($loggerConfiguration);
298
        }
299
300
        // add the system loggers to the DI container
301
        $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...
302
303
        // start the import process
304
        $container->get(SynteticServiceKeys::SIMPLE)->process();
305
    }
306
307
    /**
308
     * Return's the array with the magento specific extension libraries.
309
     *
310
     * @param \TechDivision\Import\ConfigurationInterface $configuration The configuration instance
311
     *
312
     * @return array The magento edition specific extension libraries
313
     */
314
    public function getExtensionLibraries(ConfigurationInterface $configuration)
315
    {
316
317
        // return the array with the Magento Edition specific libraries
318
        return array_merge(
319
            Simple::getDefaultLibraries($configuration->getMagentoEdition()),
320
            $configuration->getExtensionLibraries()
321
        );
322
    }
323
324
    /**
325
     * Return's the absolute path to the actual vendor directory.
326
     *
327
     * @return string The absolute path to the actual vendor directory
328
     * @throws \Exception Is thrown, if none of the possible vendor directories can be found
329
     */
330
    public function getVendorDir()
331
    {
332
333
        // the possible paths to the vendor directory
334
        $possibleVendorDirectories = array(
335
            dirname(dirname(dirname(dirname(dirname(__DIR__))))) . DIRECTORY_SEPARATOR . 'vendor',
336
            dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'vendor'
337
        );
338
339
        // try to find the path to the JMS Serializer annotations
340
        foreach ($possibleVendorDirectories as $possibleVendorDirectory) {
341
            // return the directory as vendor directory if available
342
            if (is_dir($possibleVendorDirectory)) {
343
                return $possibleVendorDirectory;
344
            }
345
        }
346
347
        // stop processing, if NO vendor directory is available
348
        throw new \Exception(
349
            sprintf(
350
                'None of the possible vendor directories %s is available',
351
                implode(', ', $possibleVendorDirectories)
352
            )
353
        );
354
    }
355
356
    /**
357
     * Return's the Magento installation directory, assuming that this is the
358
     * actual directory.
359
     *
360
     * @return string The Magento installation directory
361
     */
362
    public function getMagentoInstallationDir()
363
    {
364
        return getcwd();
365
    }
366
367
    /**
368
     * Return's the given entity type's specific default configuration file.
369
     *
370
     * @return string The name of the library to query for the default configuration file
371
     * @throws \Exception Is thrown, if no default configuration for the passed entity type is available
372
     */
373
    public function getDefaultImportDir()
374
    {
375
        return sprintf('%s/var/importexport', $this->getMagentoInstallationDir());
376
    }
377
378
    /**
379
     * Return's the given entity type's specific default configuration file.
380
     *
381
     * @return string The name of the library to query for the default configuration file
382
     * @throws \Exception Is thrown, if no default configuration for the passed entity type is available
383
     */
384
    public function getDefaultConfiguration()
385
    {
386
        return sprintf(
387
            '%s/%s/etc/techdivision-import.json',
388
            $this->getVendorDir(),
389
            Simple::getDefaultConfiguration($this->getEntityTypeCode())
390
        );
391
    }
392
393
    /**
394
     * Return's the default operation.
395
     *
396
     * @return string The default operation
397
     */
398
    public function getDefaultOperation()
399
    {
400
        return OperationKeys::ADD_UPDATE;
401
    }
402
}
403