Completed
Pull Request — master (#27)
by Tim
02:52
created

Simple   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 765
Duplicated Lines 0.78 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 10.75%

Importance

Changes 14
Bugs 0 Features 2
Metric Value
wmc 57
c 14
b 0
f 2
lcom 1
cbo 9
dl 6
loc 765
ccs 23
cts 214
cp 0.1075
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 37 3
B subjectFactory() 0 30 2
B archive() 3 61 6
B removeDir() 0 22 5
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\Callbacks\CallbackVisitor;
34
use TechDivision\Import\Cli\Observers\ObserverVisitor;
35
use TechDivision\Import\Services\ImportProcessorInterface;
36
use TechDivision\Import\Services\RegistryProcessorInterface;
37
use TechDivision\Import\Cli\Utils\BunchKeys;
38
39
/**
40
 * A SLSB that handles the product import process.
41
 *
42
 * @author    Tim Wagner <[email protected]>
43
 * @copyright 2016 TechDivision GmbH <[email protected]>
44
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
45
 * @link      https://github.com/techdivision/import-cli-simple
46
 * @link      http://www.techdivision.com
47
 */
48
class Simple
49
{
50
51
    /**
52
     * The default style to write messages to the symfony console.
53
     *
54
     * @var string
55
     */
56
    const DEFAULT_STYLE = 'info';
57
58
    /**
59
     * The TechDivision company name as ANSI art.
60
     *
61
     * @var string
62
     */
63
    protected $ansiArt = ' _______        _     _____  _       _     _
64
|__   __|      | |   |  __ \(_)     (_)   (_)
65
   | | ___  ___| |__ | |  | |___   ___ ___ _  ___  _ __
66
   | |/ _ \/ __| \'_ \| |  | | \ \ / / / __| |/ _ \| \'_ \
67
   | |  __/ (__| | | | |__| | |\ V /| \__ \ | (_) | | | |
68
   |_|\___|\___|_| |_|_____/|_| \_/ |_|___/_|\___/|_| |_|
69
';
70
71
    /**
72
     * The log level => console style mapping.
73
     *
74
     * @var array
75
     */
76
    protected $logLevelStyleMapping = array(
77
        LogLevel::INFO      => 'info',
78
        LogLevel::DEBUG     => 'comment',
79
        LogLevel::ERROR     => 'error',
80
        LogLevel::ALERT     => 'error',
81
        LogLevel::CRITICAL  => 'error',
82
        LogLevel::EMERGENCY => 'error',
83
        LogLevel::WARNING   => 'error',
84
        LogLevel::NOTICE    => 'info'
85
    );
86
87
    /**
88
     * The actions unique serial.
89
     *
90
     * @var string
91
     */
92
    protected $serial;
93
94
    /**
95
     * The system logger implementation.
96
     *
97
     * @var \Psr\Log\LoggerInterface
98
     */
99
    protected $systemLogger;
100
101
    /**
102
     * The RegistryProcessor instance to handle running threads.
103
     *
104
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
105
     */
106
    protected $registryProcessor;
107
108
    /**
109
     * The processor to read/write the necessary import data.
110
     *
111
     * @var \TechDivision\Import\Services\ImportProcessorInterface
112
     */
113
    protected $importProcessor;
114
115
    /**
116
     * The system configuration.
117
     *
118
     * @var \TechDivision\Import\ConfigurationInterface
119
     */
120
    protected $configuration;
121
122
    /**
123
     * The input stream to read console information from.
124
     *
125
     * @var \Symfony\Component\Console\Input\InputInterface
126
     */
127
    protected $input;
128
129
    /**
130
     * The output stream to write console information to.
131
     *
132
     * @var \Symfony\Component\Console\Output\OutputInterface
133
     */
134
    protected $output;
135
136
    /**
137
     * The matches for the last processed CSV filename.
138
     *
139
     * @var array
140
     */
141
    protected $matches = array();
142
143
    /**
144
     * Set's the unique serial for this import process.
145
     *
146
     * @param string $serial The unique serial
147
     *
148
     * @return void
149
     */
150
    public function setSerial($serial)
151
    {
152
        $this->serial = $serial;
153
    }
154
155
    /**
156
     * Return's the unique serial for this import process.
157
     *
158
     * @return string The unique serial
159
     */
160
    public function getSerial()
161
    {
162
        return $this->serial;
163
    }
164
165
    /**
166
     * Set's the system logger.
167
     *
168
     * @param \Psr\Log\LoggerInterface $systemLogger The system logger
169
     *
170
     * @return void
171
     */
172
    public function setSystemLogger(LoggerInterface $systemLogger)
173
    {
174
        $this->systemLogger = $systemLogger;
175
    }
176
177
    /**
178
     * Return's the system logger.
179
     *
180
     * @return \Psr\Log\LoggerInterface The system logger instance
181
     */
182
    public function getSystemLogger()
183
    {
184
        return $this->systemLogger;
185
    }
186
187
    /**
188
     * Sets's the RegistryProcessor instance to handle the running threads.
189
     *
190
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor The registry processor instance
191
     *
192
     * @return void
193
     */
194
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
195
    {
196
        $this->registryProcessor = $registryProcessor;
197
    }
198
199
    /**
200
     * Return's the RegistryProcessor instance to handle the running threads.
201
     *
202
     * @return \TechDivision\Import\Services\RegistryProcessor The registry processor instance
203
     */
204
    public function getRegistryProcessor()
205
    {
206
        return $this->registryProcessor;
207
    }
208
209
    /**
210
     * Set's the import processor instance.
211
     *
212
     * @param \TechDivision\Import\Services\ImportProcessorInterface $importProcessor The import processor instance
213
     *
214
     * @return void
215
     */
216 1
    public function setImportProcessor(ImportProcessorInterface $importProcessor)
217
    {
218 1
        $this->importProcessor = $importProcessor;
219 1
    }
220
221
    /**
222
     * Return's the import processor instance.
223
     *
224
     * @return \TechDivision\Import\Services\ImportProcessorInterface The import processor instance
225
     */
226 1
    public function getImportProcessor()
227
    {
228 1
        return $this->importProcessor;
229
    }
230
231
    /**
232
     * Set's the system configuration.
233
     *
234
     * @param \TechDivision\Import\ConfigurationInterface $configuration The system configuration
235
     *
236
     * @return void
237
     */
238
    public function setConfiguration(ConfigurationInterface $configuration)
239
    {
240
        $this->configuration = $configuration;
241
    }
242
243
    /**
244
     * Return's the system configuration.
245
     *
246
     * @return \TechDivision\Import\ConfigurationInterface The system configuration
247
     */
248
    public function getConfiguration()
249
    {
250
        return $this->configuration;
251
    }
252
253
    /**
254
     * Set's the input stream to read console information from.
255
     *
256
     * @param \Symfony\Component\Console\Input\InputInterface $input An IutputInterface instance
257
     *
258
     * @return void
259
     */
260
    public function setInput(InputInterface $input)
261
    {
262
        $this->input = $input;
263
    }
264
265
    /**
266
     * Return's the input stream to read console information from.
267
     *
268
     * @return \Symfony\Component\Console\Input\InputInterface An IutputInterface instance
269
     */
270
    protected function getInput()
271
    {
272
        return $this->input;
273
    }
274
275
    /**
276
     * Set's the output stream to write console information to.
277
     *
278
     * @param \Symfony\Component\Console\Output\OutputInterface $output An OutputInterface instance
279
     *
280
     * @return void
281
     */
282
    public function setOutput(OutputInterface $output)
283
    {
284
        $this->output = $output;
285
    }
286
287
    /**
288
     * Return's the output stream to write console information to.
289
     *
290
     * @return \Symfony\Component\Console\Output\OutputInterface An OutputInterface instance
291
     */
292
    protected function getOutput()
293
    {
294
        return $this->output;
295
    }
296
297
    /**
298
     * Return's the source directory that has to be watched for new files.
299
     *
300
     * @return string The source directory
301
     */
302
    protected function getSourceDir()
303
    {
304
        return $this->getConfiguration()->getSourceDir();
305
    }
306
307
    /**
308
     * Parse the temporary upload directory for new files to be imported.
309
     *
310
     * @return void
311
     * @throws \Exception Is thrown if the import can't be finished successfully
312
     */
313
    public function import()
314
    {
315
316
        // track the start time
317
        $startTime = microtime(true);
318
319
        try {
320
            // generate the serial for the new job
321
            $this->setSerial(Uuid::uuid4()->__toString());
322
323
            // prepare the global data for the import process
324
            $this->start();
325
            $this->setUp();
326
            $this->processSubjects();
327
            $this->archive();
328
            $this->tearDown();
329
            $this->finish();
330
331
            // track the time needed for the import in seconds
332
            $endTime = microtime(true) - $startTime;
333
334
            // log a message that import has been finished
335
            $this->log(sprintf('Successfully finished import with serial %s in %f s', $this->getSerial(), $endTime), LogLevel::INFO);
336
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
337
        } catch (\Exception $e) {
338
            // tear down
339
            $this->tearDown();
340
            $this->finish();
341
342
            // track the time needed for the import in seconds
343
            $endTime = microtime(true) - $startTime;
344
345
            // log a message that the file import failed
346
            $this->getSystemLogger()->error($e->__toString());
347
348
            // log a message that import has been finished
349
            $this->log(sprintf('Can\'t finish import with serial %s in %f s', $this->getSerial(), $endTime), LogLevel::ERROR);
350
351
            // re-throw the exception
352
            throw $e;
353
        }
354
    }
355
356
    /**
357
     * This method start's the import process by initializing
358
     * the status and appends it to the registry.
359
     *
360
     * @return void
361
     */
362
    protected function start()
363
    {
364
365
        // write the TechDivision ANSI art icon to the console
366
        $this->log($this->ansiArt);
367
368
        // log the debug information, if debug mode is enabled
369
        if ($this->getConfiguration()->isDebugMode()) {
370
            // log the system's PHP configuration
371
            $this->log(sprintf('PHP version: %s', phpversion()), LogLevel::DEBUG);
372
            $this->log('-------------------- Loaded Extensions -----------------------', LogLevel::DEBUG);
373
            $this->log(implode(', ', $loadedExtensions = get_loaded_extensions()), LogLevel::DEBUG);
374
            $this->log('--------------------------------------------------------------', LogLevel::DEBUG);
375
376
            // write a warning for low performance, if XDebug extension is activated
377
            if (in_array('xdebug', $loadedExtensions)) {
378
                $this->log('Low performance exptected, as result of enabled XDebug extension!', LogLevel::WARNING);
379
            }
380
        }
381
382
        // log a message that import has been started
383
        $this->log(sprintf('Now start import with serial %s', $this->getSerial()), LogLevel::INFO);
384
385
        // initialize the status
386
        $status = array(
387
            RegistryKeys::STATUS => 1,
388
            RegistryKeys::SOURCE_DIRECTORY => $this->getConfiguration()->getSourceDir()
389
        );
390
391
        // initialize the status information for the subjects */
392
        /** @var \TechDivision\Import\Configuration\SubjectInterface $subject */
393
        foreach ($this->getConfiguration()->getSubjects() as $subject) {
394
            $status[$subject->getPrefix()] = array();
395
        }
396
397
        // append it to the registry
398
        $this->getRegistryProcessor()->setAttribute($this->getSerial(), $status);
399
    }
400
401
    /**
402
     * Prepares the global data for the import process.
403
     *
404
     * @return void
405
     */
406
    protected function setUp()
407
    {
408
409
        // load the registry
410
        $importProcessor = $this->getImportProcessor();
411
        $registryProcessor = $this->getRegistryProcessor();
412
413
        // initialize the array for the global data
414
        $globalData = array();
415
416
        // initialize the global data
417
        $globalData[RegistryKeys::STORES] = $importProcessor->getStores();
418
        $globalData[RegistryKeys::LINK_TYPES] = $importProcessor->getLinkTypes();
419
        $globalData[RegistryKeys::TAX_CLASSES] = $importProcessor->getTaxClasses();
420
        $globalData[RegistryKeys::DEFAULT_STORE] = $importProcessor->getDefaultStore();
421
        $globalData[RegistryKeys::STORE_WEBSITES] = $importProcessor->getStoreWebsites();
422
        $globalData[RegistryKeys::LINK_ATTRIBUTES] = $importProcessor->getLinkAttributes();
423
        $globalData[RegistryKeys::ROOT_CATEGORIES] = $importProcessor->getRootCategories();
424
        $globalData[RegistryKeys::CORE_CONFIG_DATA] = $importProcessor->getCoreConfigData();
425
        $globalData[RegistryKeys::ATTRIBUTE_SETS] = $eavAttributeSets = $importProcessor->getEavAttributeSetsByEntityTypeId(4);
426
427
        // prepare the categories
428
        $categories = array();
429
        foreach ($importProcessor->getCategories() as $category) {
430
            // expload the entity IDs from the category path
431
            $entityIds = explode('/', $category[MemberNames::PATH]);
432
433
            // cut-off the root category
434
            array_shift($entityIds);
435
436
            // continue with the next category if no entity IDs are available
437
            if (sizeof($entityIds) === 0) {
438
                continue;
439
            }
440
441
            // initialize the array for the path elements
442
            $path = array();
443
            foreach ($importProcessor->getCategoryVarcharsByEntityIds($entityIds) as $cat) {
444
                $path[] = $cat[MemberNames::VALUE];
445
            }
446
447
            // append the catogory with the string path as key
448
            $categories[implode('/', $path)] = $category;
449
        }
450
451
        // initialize the array with the categories
452
        $globalData[RegistryKeys::CATEGORIES] = $categories;
453
454
        // prepare an array with the EAV attributes grouped by their attribute set name as keys
455
        $eavAttributes = array();
456
        foreach (array_keys($eavAttributeSets) as $eavAttributeSetName) {
457
            $eavAttributes[$eavAttributeSetName] = $importProcessor->getEavAttributesByEntityTypeIdAndAttributeSetName(4, $eavAttributeSetName);
458
        }
459
460
        // initialize the array with the EAV attributes
461
        $globalData[RegistryKeys::EAV_ATTRIBUTES] = $eavAttributes;
462
463
        // add the status with the global data
464
        $registryProcessor->mergeAttributesRecursive(
465
            $this->getSerial(),
466
            array(RegistryKeys::GLOBAL_DATA => $globalData)
467
        );
468
469
        // log a message that the global data has been prepared
470
        $this->log(sprintf('Successfully prepared global data for import with serial %s', $this->getSerial()), LogLevel::INFO);
471
    }
472
473
    /**
474
     * Process all the subjects defined in the system configuration.
475
     *
476
     * @return void
477
     * @throws \Exception Is thrown, if one of the subjects can't be processed
478
     */
479
    protected function processSubjects()
480
    {
481
482
        try {
483
            // load system logger and registry
484
            $importProcessor = $this->getImportProcessor();
485
486
            // load the subjects
487
            $subjects = $this->getConfiguration()->getSubjects();
488
489
            // start the transaction
490
            $importProcessor->getConnection()->beginTransaction();
491
492
            // process all the subjects found in the system configuration
493
            foreach ($subjects as $subject) {
494
                $this->processSubject($subject);
495
            }
496
497
            // commit the transaction
498
            $importProcessor->getConnection()->commit();
499
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
500
        } catch (\Exception $e) {
501
            // rollback the transaction
502
            $importProcessor->getConnection()->rollBack();
503
504
            // re-throw the exception
505
            throw $e;
506
        }
507
    }
508
509
    /**
510
     * Process the subject with the passed name/identifier.
511
     *
512
     * We create a new, fresh and separate subject for EVERY file here, because this would be
513
     * the starting point to parallelize the import process in a multithreaded/multiprocessed
514
     * environment.
515
     *
516
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
517
     *
518
     * @return void
519
     * @throws \Exception Is thrown, if the subject can't be processed
520
     */
521
    protected function processSubject($subject)
522
    {
523
524
        // clear the filecache
525
        clearstatcache();
526
527
        // load the actual status
528
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
529
530
        // query whether or not the configured source directory is available
531 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...
532
            throw new \Exception(sprintf('Configured source directory %s is not available!', $sourceDir));
533
        }
534
535
        // initialize the file iterator on source directory
536
        $fileIterator = new \FilesystemIterator($sourceDir);
537
538
        // log a debug message
539
        $this->log(sprintf('Now checking directory %s for files to be imported', $sourceDir), LogLevel::DEBUG);
540
541
        // initialize the counter for the bunches that has been imported
542
        $bunches = 0;
543
544
        // iterate through all CSV files and process the subjects
545
        foreach ($fileIterator as $filename) {
546
            // initialize prefix + pathname
547
            $prefix = $subject->getPrefix();
548
            $pathname = $filename->getPathname();
549
550
            // query whether or not we've a file that is part of a bunch here
551
            if ($this->isPartOfBunch($prefix, $pathname)) {
552
                // import the bunch
553
                $this->subjectFactory($subject)->import($this->getSerial(), $pathname);
554
                // raise the number of imported bunches
555
                $bunches++;
556
            }
557
        }
558
559
        // and and log a message that the subject has been processed
560
        $this->log(sprintf('Successfully processed subject %s with %d bunch(es)!', $subject->getClassName(), $bunches), LogLevel::DEBUG);
561
    }
562
563
    /**
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter "$prefix" missing
Loading history...
564
     * Queries whether or not, the passed filename is part of a bunch or not.
565
     *
566
     * @param string $prefix   The prefix of the files to import
567
     * @param string $filename The filename to query
0 ignored issues
show
Coding Style introduced by
Doc comment for parameter $filename does not match actual variable name $prefix
Loading history...
568
     *
569
     * @return boolean TRUE if the filename is part, else FALSE
570
     */
571 2
    public function isPartOfBunch($prefix, $filename)
572
    {
573
574
        // initialize the pattern
575 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...
576
577
        // query whether or not, this is the first file to be processed
578 2
        if (sizeof($this->matches) === 0) {
579
            // initialize the pattern to query whether the FIRST file has to be processed or not
580 2
            $pattern = sprintf(
581 2
                '/^.*\/(?<%s>%s)_(?<%s>.*)_(?<%s>\d+)\\.csv$/',
582 2
                BunchKeys::PREFIX,
583
                $prefix,
584 2
                BunchKeys::FILENAME,
585 2
                BunchKeys::COUNTER
586
            );
587
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
588
        } else {
589
            // initialize the pattern to query whether the NEXT file is part of a bunch or not
590 2
            $pattern = sprintf(
591 2
                '/^.*\/(?<%s>%s)_(?<%s>%s)_(?<%s>\d+)\\.csv$/',
592 2
                BunchKeys::PREFIX,
593 2
                $this->matches[BunchKeys::PREFIX],
594 2
                BunchKeys::FILENAME,
595 2
                $this->matches[BunchKeys::FILENAME],
596 2
                BunchKeys::COUNTER
597
            );
598
        }
599
600
        // update the matches, if the pattern matches
601 2
        if ($result = preg_match($pattern, $filename, $matches)) {
602 2
            $this->matches = $matches;
603
        }
604
605
        // stop processing, if the filename doesn't match
606 2
        return (boolean) $result;
607
    }
608
609
    /**
610
     * Factory method to create new handler instances.
611
     *
612
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
613
     *
614
     * @return object The handler instance
615
     */
616
    public function subjectFactory($subject)
617
    {
618
619
        // load the subject class name
620
        $className = $subject->getClassName();
621
622
        // the database connection to use
623
        $connection = $this->getImportProcessor()->getConnection();
624
625
        // initialize a new handler with the passed class name
626
        $instance = new $className();
627
628
        // $instance the handler instance
629
        $instance->setConfiguration($subject);
630
        $instance->setSystemLogger($this->getSystemLogger());
631
        $instance->setRegistryProcessor($this->getRegistryProcessor());
632
633
        // instanciate and set the product processor, if specified
634
        if ($processorFactory = $subject->getProcessorFactory()) {
635
            $productProcessor = $processorFactory::factory($connection, $subject);
636
            $instance->setProductProcessor($productProcessor);
637
        }
638
639
        // initialize the callbacks/visitors
640
        CallbackVisitor::get()->visit($instance);
641
        ObserverVisitor::get()->visit($instance);
642
643
        // return the subject instance
644
        return $instance;
645
    }
646
647
    /**
648
     * Lifecycle callback that will be inovked after the
649
     * import process has been finished.
650
     *
651
     * @return void
652
     * @throws \Exception Is thrown, if the
653
     */
654
    protected function archive()
655
    {
656
657
        // query whether or not, the import artefacts have to be archived
658
        if (!$this->getConfiguration()->haveArchiveArtefacts()) {
659
            return;
660
        }
661
662
        // clear the filecache
663
        clearstatcache();
664
665
        // load the actual status
666
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
667
668
        // query whether or not the configured source directory is available
669 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...
670
            throw new \Exception(sprintf('Configured source directory %s is not available!', $sourceDir));
671
        }
672
673
        // init file iterator on source directory
674
        $fileIterator = new \FilesystemIterator($sourceDir);
675
676
        // log the number of files that has to be archived
677
        $fileCounter = iterator_count($fileIterator);
678
679
        // if no files are available, return
680
        if ($fileCounter == 0) {
681
            // log the number of files that has to be archived
682
            $this->log(sprintf('Found no files to archive'), LogLevel::INFO);
683
            return;
684
        }
685
686
        // log the number of files that has to be archived
687
        $this->log(sprintf('Found %d files to archive in directory %s', $fileCounter, $sourceDir), LogLevel::INFO);
688
689
        // initialize the directory to create the archive in
690
        $archiveDir = sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getConfiguration()->getArchiveDir());
691
692
        // query whether or not the directory already exists
693
        if (!is_dir($archiveDir)) {
694
            mkdir($archiveDir);
695
        }
696
697
        // create the ZIP archive
698
        $archive = new \ZipArchive();
699
        $archive->open($archiveFile = sprintf('%s/%s.zip', $archiveDir, $this->getSerial()), \ZipArchive::CREATE);
700
701
        // iterate through all files and add them to the ZIP archive
702
        foreach ($fileIterator as $filename) {
703
            $archive->addFile($filename);
704
        }
705
706
        // save the ZIP archive
707
        $archive->close();
708
709
        // finally remove the directory with the imported files
710
        $this->removeDir($sourceDir);
711
712
        // and and log a message that the import artefacts have been archived
713
        $this->log(sprintf('Successfully archived imported files to %s!', $archiveFile), LogLevel::INFO);
714
    }
715
716
    /**
717
     * Removes the passed directory recursively.
718
     *
719
     * @param string $src Name of the directory to remove
720
     *
721
     * @return void
722
     */
723
    protected function removeDir($src)
724
    {
725
726
        // open the directory
727
        $dir = opendir($src);
728
729
        // remove files/folders recursively
730
        while (false !== ($file = readdir($dir))) {
731
            if (($file != '.') && ($file != '..')) {
732
                $full = $src . '/' . $file;
733
                if (is_dir($full)) {
734
                    Simple::removeDir($full);
735
                } else {
736
                    unlink($full);
737
                }
738
            }
739
        }
740
741
        // close handle and remove directory itself
742
        closedir($dir);
743
        rmdir($src);
744
    }
745
746
    /**
747
     * Simple method that writes the passed method the the console and the
748
     * system logger, if configured and a log level has been passed.
749
     *
750
     * @param string $msg      The message to log
751
     * @param string $logLevel The log level to use
752
     *
753
     * @return void
754
     */
755
    protected function log($msg, $logLevel = null)
756
    {
757
758
        // initialize the formatter helper
759
        $helper = new FormatterHelper();
760
761
        // map the log level to the console style
762
        $style = $this->mapLogLevelToStyle($logLevel);
763
764
        // format the message, according to the passed log level and write it to the console
765
        $this->getOutput()->writeln($logLevel ? $helper->formatBlock($msg, $style) : $msg);
766
767
        // log the message if a log level has been passed
768
        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...
769
            $systemLogger->log($logLevel, $msg);
770
        }
771
    }
772
773
    /**
774
     * Map's the passed log level to a valid symfony console style.
775
     *
776
     * @param string $logLevel The log level to map
777
     *
778
     * @return string The apropriate symfony console style
779
     */
780
    protected function mapLogLevelToStyle($logLevel)
781
    {
782
783
        // query whether or not the log level is mapped
784
        if (isset($this->logLevelStyleMapping[$logLevel])) {
785
            return $this->logLevelStyleMapping[$logLevel];
786
        }
787
788
        // return the default style => info
789
        return Simple::DEFAULT_STYLE;
790
    }
791
792
    /**
793
     * Lifecycle callback that will be inovked after the
794
     * import process has been finished.
795
     *
796
     * @return void
797
     * @throws \Exception Is thrown, if the
798
     */
799
    protected function tearDown()
800
    {
801
    }
802
803
    /**
804
     * This method finishes the import process and cleans the registry.
805
     *
806
     * @return void
807
     */
808
    protected function finish()
809
    {
810
        $this->getRegistryProcessor()->removeAttribute($this->getSerial());
811
    }
812
}
813