Completed
Push — master ( 5d3d33...280f0a )
by Tim
8s
created

Simple   C

Complexity

Total Complexity 59

Size/Duplication

Total Lines 773
Duplicated Lines 0.78 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 11.34%

Importance

Changes 16
Bugs 0 Features 2
Metric Value
wmc 59
c 16
b 0
f 2
lcom 1
cbo 9
dl 6
loc 773
ccs 27
cts 238
cp 0.1134
rs 5

28 Methods

Rating   Name   Duplication   Size   Complexity  
A setSerial() 0 4 1
A getSerial() 0 4 1
A setSystemLogger() 0 4 1
A getSystemLogger() 0 4 1
A setRegistryProcessor() 0 4 1
A getRegistryProcessor() 0 4 1
A setImportProcessor() 0 4 1
A getImportProcessor() 0 4 1
A setConfiguration() 0 4 1
A getConfiguration() 0 4 1
A setInput() 0 4 1
A getInput() 0 4 1
A setOutput() 0 4 1
A getOutput() 0 4 1
A getSourceDir() 0 4 1
B import() 0 42 2
B start() 0 38 4
B setUp() 0 66 5
B processSubjects() 0 29 3
B processSubject() 3 41 4
B isPartOfBunch() 0 40 3
B subjectFactory() 0 30 2
B archive() 3 61 6
C removeDir() 0 26 7
A log() 0 17 4
A mapLogLevelToStyle() 0 11 2
A tearDown() 0 3 1
A finish() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Simple often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Simple, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * TechDivision\Import\Cli\Simple
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 Rhumsaa\Uuid\Uuid;
24
use Psr\Log\LogLevel;
25
use Psr\Log\LoggerInterface;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Helper\FormatterHelper;
29
use TechDivision\Import\Utils\MemberNames;
30
use TechDivision\Import\Utils\RegistryKeys;
31
use TechDivision\Import\ConfigurationInterface;
32
use TechDivision\Import\Subjects\SubjectInterface;
33
use TechDivision\Import\Cli\Utils\BunchKeys;
34
use TechDivision\Import\Cli\Callbacks\CallbackVisitor;
35
use TechDivision\Import\Cli\Observers\ObserverVisitor;
36
use TechDivision\Import\Services\ImportProcessorInterface;
37
use TechDivision\Import\Services\RegistryProcessorInterface;
38
39
/**
40
 * The M2IF - Console Tool implementation.
41
 *
42
 * This is a example console tool implementation that should give developers an impression
43
 * on how the M2IF could be used to implement their own Magento 2 importer.
44
 *
45
 * @author    Tim Wagner <[email protected]>
46
 * @copyright 2016 TechDivision GmbH <[email protected]>
47
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
48
 * @link      https://github.com/techdivision/import-cli-simple
49
 * @link      http://www.techdivision.com
50
 */
51
class Simple
52
{
53
54
    /**
55
     * The default style to write messages to the symfony console.
56
     *
57
     * @var string
58
     */
59
    const DEFAULT_STYLE = 'info';
60
61
    /**
62
     * The TechDivision company name as ANSI art.
63
     *
64
     * @var string
65
     */
66
    protected $ansiArt = ' _______        _     _____  _       _     _
67
|__   __|      | |   |  __ \(_)     (_)   (_)
68
   | | ___  ___| |__ | |  | |___   ___ ___ _  ___  _ __
69
   | |/ _ \/ __| \'_ \| |  | | \ \ / / / __| |/ _ \| \'_ \
70
   | |  __/ (__| | | | |__| | |\ V /| \__ \ | (_) | | | |
71
   |_|\___|\___|_| |_|_____/|_| \_/ |_|___/_|\___/|_| |_|
72
';
73
74
    /**
75
     * The log level => console style mapping.
76
     *
77
     * @var array
78
     */
79
    protected $logLevelStyleMapping = array(
80
        LogLevel::INFO      => 'info',
81
        LogLevel::DEBUG     => 'comment',
82
        LogLevel::ERROR     => 'error',
83
        LogLevel::ALERT     => 'error',
84
        LogLevel::CRITICAL  => 'error',
85
        LogLevel::EMERGENCY => 'error',
86
        LogLevel::WARNING   => 'error',
87
        LogLevel::NOTICE    => 'info'
88
    );
89
90
    /**
91
     * The actions unique serial.
92
     *
93
     * @var string
94
     */
95
    protected $serial;
96
97
    /**
98
     * The system logger implementation.
99
     *
100
     * @var \Psr\Log\LoggerInterface
101
     */
102
    protected $systemLogger;
103
104
    /**
105
     * The RegistryProcessor instance to handle running threads.
106
     *
107
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
108
     */
109
    protected $registryProcessor;
110
111
    /**
112
     * The processor to read/write the necessary import data.
113
     *
114
     * @var \TechDivision\Import\Services\ImportProcessorInterface
115
     */
116
    protected $importProcessor;
117
118
    /**
119
     * The system configuration.
120
     *
121
     * @var \TechDivision\Import\ConfigurationInterface
122
     */
123
    protected $configuration;
124
125
    /**
126
     * The input stream to read console information from.
127
     *
128
     * @var \Symfony\Component\Console\Input\InputInterface
129
     */
130
    protected $input;
131
132
    /**
133
     * The output stream to write console information to.
134
     *
135
     * @var \Symfony\Component\Console\Output\OutputInterface
136
     */
137
    protected $output;
138
139
    /**
140
     * The matches for the last processed CSV filename.
141
     *
142
     * @var array
143
     */
144
    protected $matches = array();
145
146
    /**
147
     * Set's the unique serial for this import process.
148
     *
149
     * @param string $serial The unique serial
150
     *
151
     * @return void
152
     */
153
    public function setSerial($serial)
154
    {
155
        $this->serial = $serial;
156
    }
157
158
    /**
159
     * Return's the unique serial for this import process.
160
     *
161
     * @return string The unique serial
162
     */
163
    public function getSerial()
164
    {
165
        return $this->serial;
166
    }
167
168
    /**
169
     * Set's the system logger.
170
     *
171
     * @param \Psr\Log\LoggerInterface $systemLogger The system logger
172
     *
173
     * @return void
174
     */
175
    public function setSystemLogger(LoggerInterface $systemLogger)
176
    {
177
        $this->systemLogger = $systemLogger;
178
    }
179
180
    /**
181
     * Return's the system logger.
182
     *
183
     * @return \Psr\Log\LoggerInterface The system logger instance
184
     */
185
    public function getSystemLogger()
186
    {
187
        return $this->systemLogger;
188
    }
189
190
    /**
191
     * Sets's the RegistryProcessor instance to handle the running threads.
192
     *
193
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor The registry processor instance
194
     *
195
     * @return void
196
     */
197
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
198
    {
199
        $this->registryProcessor = $registryProcessor;
200
    }
201
202
    /**
203
     * Return's the RegistryProcessor instance to handle the running threads.
204
     *
205
     * @return \TechDivision\Import\Services\RegistryProcessor The registry processor instance
206
     */
207
    public function getRegistryProcessor()
208
    {
209
        return $this->registryProcessor;
210
    }
211
212
    /**
213
     * Set's the import processor instance.
214
     *
215
     * @param \TechDivision\Import\Services\ImportProcessorInterface $importProcessor The import processor instance
216
     *
217
     * @return void
218
     */
219 1
    public function setImportProcessor(ImportProcessorInterface $importProcessor)
220
    {
221 1
        $this->importProcessor = $importProcessor;
222 1
    }
223
224
    /**
225
     * Return's the import processor instance.
226
     *
227
     * @return \TechDivision\Import\Services\ImportProcessorInterface The import processor instance
228
     */
229 1
    public function getImportProcessor()
230
    {
231 1
        return $this->importProcessor;
232
    }
233
234
    /**
235
     * Set's the system configuration.
236
     *
237
     * @param \TechDivision\Import\ConfigurationInterface $configuration The system configuration
238
     *
239
     * @return void
240
     */
241
    public function setConfiguration(ConfigurationInterface $configuration)
242
    {
243
        $this->configuration = $configuration;
244
    }
245
246
    /**
247
     * Return's the system configuration.
248
     *
249
     * @return \TechDivision\Import\ConfigurationInterface The system configuration
250
     */
251
    public function getConfiguration()
252
    {
253
        return $this->configuration;
254
    }
255
256
    /**
257
     * Set's the input stream to read console information from.
258
     *
259
     * @param \Symfony\Component\Console\Input\InputInterface $input An IutputInterface instance
260
     *
261
     * @return void
262
     */
263
    public function setInput(InputInterface $input)
264
    {
265
        $this->input = $input;
266
    }
267
268
    /**
269
     * Return's the input stream to read console information from.
270
     *
271
     * @return \Symfony\Component\Console\Input\InputInterface An IutputInterface instance
272
     */
273
    protected function getInput()
274
    {
275
        return $this->input;
276
    }
277
278
    /**
279
     * Set's the output stream to write console information to.
280
     *
281
     * @param \Symfony\Component\Console\Output\OutputInterface $output An OutputInterface instance
282
     *
283
     * @return void
284
     */
285
    public function setOutput(OutputInterface $output)
286
    {
287
        $this->output = $output;
288
    }
289
290
    /**
291
     * Return's the output stream to write console information to.
292
     *
293
     * @return \Symfony\Component\Console\Output\OutputInterface An OutputInterface instance
294
     */
295
    protected function getOutput()
296
    {
297
        return $this->output;
298
    }
299
300
    /**
301
     * Return's the source directory that has to be watched for new files.
302
     *
303
     * @return string The source directory
304
     */
305
    protected function getSourceDir()
306
    {
307
        return $this->getConfiguration()->getSourceDir();
308
    }
309
310
    /**
311
     * Parse the temporary upload directory for new files to be imported.
312
     *
313
     * @return void
314
     * @throws \Exception Is thrown if the import can't be finished successfully
315
     */
316
    public function import()
317
    {
318
319
        // track the start time
320
        $startTime = microtime(true);
321
322
        try {
323
            // generate the serial for the new job
324
            $this->setSerial(Uuid::uuid4()->__toString());
325
326
            // prepare the global data for the import process
327
            $this->start();
328
            $this->setUp();
329
            $this->processSubjects();
330
            $this->archive();
331
            $this->tearDown();
332
            $this->finish();
333
334
            // track the time needed for the import in seconds
335
            $endTime = microtime(true) - $startTime;
336
337
            // log a message that import has been finished
338
            $this->log(sprintf('Successfully finished import with serial %s in %f s', $this->getSerial(), $endTime), LogLevel::INFO);
339
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
340
        } catch (\Exception $e) {
341
            // tear down
342
            $this->tearDown();
343
            $this->finish();
344
345
            // track the time needed for the import in seconds
346
            $endTime = microtime(true) - $startTime;
347
348
            // log a message that the file import failed
349
            $this->getSystemLogger()->error($e->__toString());
350
351
            // log a message that import has been finished
352
            $this->log(sprintf('Can\'t finish import with serial %s in %f s', $this->getSerial(), $endTime), LogLevel::ERROR);
353
354
            // re-throw the exception
355
            throw $e;
356
        }
357
    }
358
359
    /**
360
     * This method start's the import process by initializing
361
     * the status and appends it to the registry.
362
     *
363
     * @return void
364
     */
365
    protected function start()
366
    {
367
368
        // write the TechDivision ANSI art icon to the console
369
        $this->log($this->ansiArt);
370
371
        // log the debug information, if debug mode is enabled
372
        if ($this->getConfiguration()->isDebugMode()) {
373
            // log the system's PHP configuration
374
            $this->log(sprintf('PHP version: %s', phpversion()), LogLevel::DEBUG);
375
            $this->log('-------------------- Loaded Extensions -----------------------', LogLevel::DEBUG);
376
            $this->log(implode(', ', $loadedExtensions = get_loaded_extensions()), LogLevel::DEBUG);
377
            $this->log('--------------------------------------------------------------', LogLevel::DEBUG);
378
379
            // write a warning for low performance, if XDebug extension is activated
380
            if (in_array('xdebug', $loadedExtensions)) {
381
                $this->log('Low performance exptected, as result of enabled XDebug extension!', LogLevel::WARNING);
382
            }
383
        }
384
385
        // log a message that import has been started
386
        $this->log(sprintf('Now start import with serial %s', $this->getSerial()), LogLevel::INFO);
387
388
        // initialize the status
389
        $status = array(
390
            RegistryKeys::STATUS => 1,
391
            RegistryKeys::SOURCE_DIRECTORY => $this->getConfiguration()->getSourceDir()
392
        );
393
394
        // initialize the status information for the subjects */
395
        /** @var \TechDivision\Import\Configuration\SubjectInterface $subject */
396
        foreach ($this->getConfiguration()->getSubjects() as $subject) {
397
            $status[$subject->getPrefix()] = array();
398
        }
399
400
        // append it to the registry
401
        $this->getRegistryProcessor()->setAttribute($this->getSerial(), $status);
402
    }
403
404
    /**
405
     * Prepares the global data for the import process.
406
     *
407
     * @return void
408
     */
409
    protected function setUp()
410
    {
411
412
        // load the registry
413
        $importProcessor = $this->getImportProcessor();
414
        $registryProcessor = $this->getRegistryProcessor();
415
416
        // initialize the array for the global data
417
        $globalData = array();
418
419
        // initialize the global data
420
        $globalData[RegistryKeys::STORES] = $importProcessor->getStores();
421
        $globalData[RegistryKeys::LINK_TYPES] = $importProcessor->getLinkTypes();
422
        $globalData[RegistryKeys::TAX_CLASSES] = $importProcessor->getTaxClasses();
423
        $globalData[RegistryKeys::DEFAULT_STORE] = $importProcessor->getDefaultStore();
424
        $globalData[RegistryKeys::STORE_WEBSITES] = $importProcessor->getStoreWebsites();
425
        $globalData[RegistryKeys::LINK_ATTRIBUTES] = $importProcessor->getLinkAttributes();
426
        $globalData[RegistryKeys::ROOT_CATEGORIES] = $importProcessor->getRootCategories();
427
        $globalData[RegistryKeys::CORE_CONFIG_DATA] = $importProcessor->getCoreConfigData();
428
        $globalData[RegistryKeys::ATTRIBUTE_SETS] = $eavAttributeSets = $importProcessor->getEavAttributeSetsByEntityTypeId(4);
429
430
        // prepare the categories
431
        $categories = array();
432
        foreach ($importProcessor->getCategories() as $category) {
433
            // expload the entity IDs from the category path
434
            $entityIds = explode('/', $category[MemberNames::PATH]);
435
436
            // cut-off the root category
437
            array_shift($entityIds);
438
439
            // continue with the next category if no entity IDs are available
440
            if (sizeof($entityIds) === 0) {
441
                continue;
442
            }
443
444
            // initialize the array for the path elements
445
            $path = array();
446
            foreach ($importProcessor->getCategoryVarcharsByEntityIds($entityIds) as $cat) {
447
                $path[] = $cat[MemberNames::VALUE];
448
            }
449
450
            // append the catogory with the string path as key
451
            $categories[implode('/', $path)] = $category;
452
        }
453
454
        // initialize the array with the categories
455
        $globalData[RegistryKeys::CATEGORIES] = $categories;
456
457
        // prepare an array with the EAV attributes grouped by their attribute set name as keys
458
        $eavAttributes = array();
459
        foreach (array_keys($eavAttributeSets) as $eavAttributeSetName) {
460
            $eavAttributes[$eavAttributeSetName] = $importProcessor->getEavAttributesByEntityTypeIdAndAttributeSetName(4, $eavAttributeSetName);
461
        }
462
463
        // initialize the array with the EAV attributes
464
        $globalData[RegistryKeys::EAV_ATTRIBUTES] = $eavAttributes;
465
466
        // add the status with the global data
467
        $registryProcessor->mergeAttributesRecursive(
468
            $this->getSerial(),
469
            array(RegistryKeys::GLOBAL_DATA => $globalData)
470
        );
471
472
        // log a message that the global data has been prepared
473
        $this->log(sprintf('Successfully prepared global data for import with serial %s', $this->getSerial()), LogLevel::INFO);
474
    }
475
476
    /**
477
     * Process all the subjects defined in the system configuration.
478
     *
479
     * @return void
480
     * @throws \Exception Is thrown, if one of the subjects can't be processed
481
     */
482
    protected function processSubjects()
483
    {
484
485
        try {
486
            // load system logger and registry
487
            $importProcessor = $this->getImportProcessor();
488
489
            // load the subjects
490
            $subjects = $this->getConfiguration()->getSubjects();
491
492
            // start the transaction
493
            $importProcessor->getConnection()->beginTransaction();
494
495
            // process all the subjects found in the system configuration
496
            foreach ($subjects as $subject) {
497
                $this->processSubject($subject);
498
            }
499
500
            // commit the transaction
501
            $importProcessor->getConnection()->commit();
502
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
503
        } catch (\Exception $e) {
504
            // rollback the transaction
505
            $importProcessor->getConnection()->rollBack();
506
507
            // re-throw the exception
508
            throw $e;
509
        }
510
    }
511
512
    /**
513
     * Process the subject with the passed name/identifier.
514
     *
515
     * We create a new, fresh and separate subject for EVERY file here, because this would be
516
     * the starting point to parallelize the import process in a multithreaded/multiprocessed
517
     * environment.
518
     *
519
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
520
     *
521
     * @return void
522
     * @throws \Exception Is thrown, if the subject can't be processed
523
     */
524
    protected function processSubject($subject)
525
    {
526
527
        // clear the filecache
528
        clearstatcache();
529
530
        // load the actual status
531
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
532
533
        // query whether or not the configured source directory is available
534 View Code Duplication
        if (!is_dir($sourceDir = $status[RegistryKeys::SOURCE_DIRECTORY])) {
0 ignored issues
show
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...
535
            throw new \Exception(sprintf('Configured source directory %s is not available!', $sourceDir));
536
        }
537
538
        // initialize the file iterator on source directory
539
        $fileIterator = new \FilesystemIterator($sourceDir);
540
541
        // log a debug message
542
        $this->log(sprintf('Now checking directory %s for files to be imported', $sourceDir), LogLevel::DEBUG);
543
544
        // initialize the counter for the bunches that has been imported
545
        $bunches = 0;
546
547
        // iterate through all CSV files and process the subjects
548
        foreach ($fileIterator as $filename) {
549
            // initialize prefix + pathname
550
            $prefix = $subject->getPrefix();
551
            $pathname = $filename->getPathname();
552
553
            // query whether or not we've a file that is part of a bunch here
554
            if ($this->isPartOfBunch($prefix, $pathname)) {
555
                // import the bunch
556
                $this->subjectFactory($subject)->import($this->getSerial(), $pathname);
557
                // raise the number of imported bunches
558
                $bunches++;
559
            }
560
        }
561
562
        // and and log a message that the subject has been processed
563
        $this->log(sprintf('Successfully processed subject %s with %d bunch(es)!', $subject->getClassName(), $bunches), LogLevel::DEBUG);
564
    }
565
566
    /**
567
     * Queries whether or not, the passed filename is part of a bunch or not.
568
     *
569
     * @param string $prefix   The prefix to query for
570
     * @param string $filename The filename to query for
571
     *
572
     * @return boolean TRUE if the filename is part, else FALSE
573
     */
574 2
    public function isPartOfBunch($prefix, $filename)
575
    {
576
577
        // initialize the pattern
578 2
        $pattern = '';
0 ignored issues
show
Unused Code introduced by
$pattern is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
579
580
        // query whether or not, this is the first file to be processed
581 2
        if (sizeof($this->matches) === 0) {
582
            // initialize the pattern to query whether the FIRST file has to be processed or not
583 2
            $pattern = sprintf(
584 2
                '/^.*\/(?<%s>%s)_(?<%s>.*)_(?<%s>\d+)\\.csv$/',
585 2
                BunchKeys::PREFIX,
586 2
                $prefix,
587 2
                BunchKeys::FILENAME,
588
                BunchKeys::COUNTER
589 2
            );
590
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
591 2
        } else {
592
            // initialize the pattern to query whether the NEXT file is part of a bunch or not
593 2
            $pattern = sprintf(
594 2
                '/^.*\/(?<%s>%s)_(?<%s>%s)_(?<%s>\d+)\\.csv$/',
595 2
                BunchKeys::PREFIX,
596 2
                $this->matches[BunchKeys::PREFIX],
597 2
                BunchKeys::FILENAME,
598 2
                $this->matches[BunchKeys::FILENAME],
599
                BunchKeys::COUNTER
600 2
            );
601
        }
602
603
        // initialize the array for the matches
604 2
        $matches = array();
605
606
        // update the matches, if the pattern matches
607 2
        if ($result = preg_match($pattern, $filename, $matches)) {
608 2
            $this->matches = $matches;
609 2
        }
610
611
        // stop processing, if the filename doesn't match
612 2
        return (boolean) $result;
613
    }
614
615
    /**
616
     * Factory method to create new handler instances.
617
     *
618
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
619
     *
620
     * @return object The handler instance
621
     */
622
    public function subjectFactory($subject)
623
    {
624
625
        // load the subject class name
626
        $className = $subject->getClassName();
627
628
        // the database connection to use
629
        $connection = $this->getImportProcessor()->getConnection();
630
631
        // initialize a new handler with the passed class name
632
        $instance = new $className();
633
634
        // $instance the handler instance
635
        $instance->setConfiguration($subject);
636
        $instance->setSystemLogger($this->getSystemLogger());
637
        $instance->setRegistryProcessor($this->getRegistryProcessor());
638
639
        // instanciate and set the product processor, if specified
640
        if ($processorFactory = $subject->getProcessorFactory()) {
641
            $productProcessor = $processorFactory::factory($connection, $subject);
642
            $instance->setProductProcessor($productProcessor);
643
        }
644
645
        // initialize the callbacks/visitors
646
        CallbackVisitor::get()->visit($instance);
647
        ObserverVisitor::get()->visit($instance);
648
649
        // return the subject instance
650
        return $instance;
651
    }
652
653
    /**
654
     * Lifecycle callback that will be inovked after the
655
     * import process has been finished.
656
     *
657
     * @return void
658
     * @throws \Exception Is thrown, if the
659
     */
660
    protected function archive()
661
    {
662
663
        // query whether or not, the import artefacts have to be archived
664
        if (!$this->getConfiguration()->haveArchiveArtefacts()) {
665
            return;
666
        }
667
668
        // clear the filecache
669
        clearstatcache();
670
671
        // load the actual status
672
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
673
674
        // query whether or not the configured source directory is available
675 View Code Duplication
        if (!is_dir($sourceDir = $status[RegistryKeys::SOURCE_DIRECTORY])) {
0 ignored issues
show
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...
676
            throw new \Exception(sprintf('Configured source directory %s is not available!', $sourceDir));
677
        }
678
679
        // init file iterator on source directory
680
        $fileIterator = new \FilesystemIterator($sourceDir);
681
682
        // log the number of files that has to be archived
683
        $fileCounter = iterator_count($fileIterator);
684
685
        // if no files are available, return
686
        if ($fileCounter == 0) {
687
            // log the number of files that has to be archived
688
            $this->log(sprintf('Found no files to archive'), LogLevel::INFO);
689
            return;
690
        }
691
692
        // log the number of files that has to be archived
693
        $this->log(sprintf('Found %d files to archive in directory %s', $fileCounter, $sourceDir), LogLevel::INFO);
694
695
        // initialize the directory to create the archive in
696
        $archiveDir = sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getConfiguration()->getArchiveDir());
697
698
        // query whether or not the directory already exists
699
        if (!is_dir($archiveDir)) {
700
            mkdir($archiveDir);
701
        }
702
703
        // create the ZIP archive
704
        $archive = new \ZipArchive();
705
        $archive->open($archiveFile = sprintf('%s/%s.zip', $archiveDir, $this->getSerial()), \ZipArchive::CREATE);
706
707
        // iterate through all files and add them to the ZIP archive
708
        foreach ($fileIterator as $filename) {
709
            $archive->addFile($filename);
710
        }
711
712
        // save the ZIP archive
713
        $archive->close();
714
715
        // finally remove the directory with the imported files
716
        $this->removeDir($sourceDir);
717
718
        // and and log a message that the import artefacts have been archived
719
        $this->log(sprintf('Successfully archived imported files to %s!', $archiveFile), LogLevel::INFO);
720
    }
721
722
    /**
723
     * Removes the passed directory recursively.
724
     *
725
     * @param string $src Name of the directory to remove
726
     *
727
     * @return void
728
     * @throws \Exception Is thrown, if the directory can not be removed
729
     */
730
    protected function removeDir($src)
731
    {
732
733
        // open the directory
734
        $dir = opendir($src);
735
736
        // remove files/folders recursively
737
        while (false !== ($file = readdir($dir))) {
738
            if (($file != '.') && ($file != '..')) {
739
                $full = $src . '/' . $file;
740
                if (is_dir($full)) {
741
                    $this->removeDir($full);
742
                } else {
743
                    if (!unlink($full)) {
744
                        throw new \Exception(sprintf('Can\'t remove file %s', $full));
745
                    }
746
                }
747
            }
748
        }
749
750
        // close handle and remove directory itself
751
        closedir($dir);
752
        if (!rmdir($src)) {
753
            throw new \Exception(sprintf('Can\'t remove directory %s', $src));
754
        }
755
    }
756
757
    /**
758
     * Simple method that writes the passed method the the console and the
759
     * system logger, if configured and a log level has been passed.
760
     *
761
     * @param string $msg      The message to log
762
     * @param string $logLevel The log level to use
763
     *
764
     * @return void
765
     */
766
    protected function log($msg, $logLevel = null)
767
    {
768
769
        // initialize the formatter helper
770
        $helper = new FormatterHelper();
771
772
        // map the log level to the console style
773
        $style = $this->mapLogLevelToStyle($logLevel);
774
775
        // format the message, according to the passed log level and write it to the console
776
        $this->getOutput()->writeln($logLevel ? $helper->formatBlock($msg, $style) : $msg);
777
778
        // log the message if a log level has been passed
779
        if ($logLevel && $systemLogger = $this->getSystemLogger()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $logLevel 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...
780
            $systemLogger->log($logLevel, $msg);
781
        }
782
    }
783
784
    /**
785
     * Map's the passed log level to a valid symfony console style.
786
     *
787
     * @param string $logLevel The log level to map
788
     *
789
     * @return string The apropriate symfony console style
790
     */
791
    protected function mapLogLevelToStyle($logLevel)
792
    {
793
794
        // query whether or not the log level is mapped
795
        if (isset($this->logLevelStyleMapping[$logLevel])) {
796
            return $this->logLevelStyleMapping[$logLevel];
797
        }
798
799
        // return the default style => info
800
        return Simple::DEFAULT_STYLE;
801
    }
802
803
    /**
804
     * Lifecycle callback that will be inovked after the
805
     * import process has been finished.
806
     *
807
     * @return void
808
     * @throws \Exception Is thrown, if the
809
     */
810
    protected function tearDown()
811
    {
812
    }
813
814
    /**
815
     * This method finishes the import process and cleans the registry.
816
     *
817
     * @return void
818
     */
819
    protected function finish()
820
    {
821
        $this->getRegistryProcessor()->removeAttribute($this->getSerial());
822
    }
823
}
824