Completed
Push — master ( fa86d2...fbb6e1 )
by Tim
10s
created

Simple   C

Complexity

Total Complexity 61

Size/Duplication

Total Lines 798
Duplicated Lines 0.75 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 10.71%

Importance

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

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...
571
                $subjectInstance->import($this->getSerial(), $pathname);
572
573
                // query whether or not, we've to export artefacts
574
                if ($subjectInstance instanceof ExportableSubjectInterface) {
0 ignored issues
show
Bug introduced by
The class TechDivision\Import\Subj...ortableSubjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
575
                    $subjectInstance->export($this->matches[BunchKeys::FILENAME], $this->matches[BunchKeys::COUNTER]);
576
                }
577
578
                // raise the number of the imported bunches
579
                $this->bunches++;
580
            }
581
        }
582
583
        // reset the matches, because the exported artefacts
584
        $this->matches = array();
585
586
        // and and log a message that the subject has been processed
587
        $this->log(sprintf('Successfully processed subject %s with %d bunch(es)!', $subject->getClassName(), $this->bunches), LogLevel::DEBUG);
588
    }
589
590
    /**
591
     * Queries whether or not, the passed filename is part of a bunch or not.
592
     *
593
     * @param string $prefix   The prefix to query for
594
     * @param string $filename The filename to query for
595
     *
596
     * @return boolean TRUE if the filename is part, else FALSE
597
     */
598 2
    public function isPartOfBunch($prefix, $filename)
599
    {
600
601
        // initialize the pattern
602 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...
603
604
        // query whether or not, this is the first file to be processed
605 2
        if (sizeof($this->matches) === 0) {
606
            // initialize the pattern to query whether the FIRST file has to be processed or not
607 2
            $pattern = sprintf(
608 2
                '/^.*\/(?<%s>%s)_(?<%s>.*)_(?<%s>\d+)\\.csv$/',
609 2
                BunchKeys::PREFIX,
610
                $prefix,
611 2
                BunchKeys::FILENAME,
612 2
                BunchKeys::COUNTER
613
            );
614
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
615
        } else {
616
            // initialize the pattern to query whether the NEXT file is part of a bunch or not
617 2
            $pattern = sprintf(
618 2
                '/^.*\/(?<%s>%s)_(?<%s>%s)_(?<%s>\d+)\\.csv$/',
619 2
                BunchKeys::PREFIX,
620 2
                $this->matches[BunchKeys::PREFIX],
621 2
                BunchKeys::FILENAME,
622 2
                $this->matches[BunchKeys::FILENAME],
623 2
                BunchKeys::COUNTER
624
            );
625
        }
626
627
        // initialize the array for the matches
628 2
        $matches = array();
629
630
        // update the matches, if the pattern matches
631 2
        if ($result = preg_match($pattern, $filename, $matches)) {
632 2
            $this->matches = $matches;
633
        }
634
635
        // stop processing, if the filename doesn't match
636 2
        return (boolean) $result;
637
    }
638
639
    /**
640
     * Factory method to create new handler instances.
641
     *
642
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
643
     *
644
     * @return object The handler instance
645
     */
646
    public function subjectFactory($subject)
647
    {
648
649
        // load the subject class name
650
        $className = $subject->getClassName();
651
652
        // the database connection to use
653
        $connection = $this->getImportProcessor()->getConnection();
654
655
        // initialize a new handler with the passed class name
656
        $instance = new $className();
657
658
        // $instance the handler instance
659
        $instance->setConfiguration($subject);
660
        $instance->setSystemLogger($this->getSystemLogger());
661
        $instance->setRegistryProcessor($this->getRegistryProcessor());
662
663
        // instanciate and set the product processor, if specified
664
        if ($processorFactory = $subject->getProcessorFactory()) {
665
            $productProcessor = $processorFactory::factory($connection, $subject);
666
            $instance->setProductProcessor($productProcessor);
667
        }
668
669
        // initialize the callbacks/visitors
670
        CallbackVisitor::get()->visit($instance);
671
        ObserverVisitor::get()->visit($instance);
672
673
        // return the subject instance
674
        return $instance;
675
    }
676
677
    /**
678
     * Lifecycle callback that will be inovked after the
679
     * import process has been finished.
680
     *
681
     * @return void
682
     * @throws \Exception Is thrown, if the
683
     */
684
    protected function archive()
685
    {
686
687
        // query whether or not, the import artefacts have to be archived
688
        if (!$this->getConfiguration()->haveArchiveArtefacts()) {
689
            $this->log(sprintf('Archiving functionality has not been activated'), LogLevel::INFO);
690
            return;
691
        }
692
693
        // if no files have been imported, return immediately
694
        if ($this->bunches === 0) {
695
            $this->log(sprintf('Found no files to archive'), LogLevel::INFO);
696
            return;
697
        }
698
699
        // clear the filecache
700
        clearstatcache();
701
702
        // load the actual status
703
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
704
705
        // query whether or not the configured source directory is available
706 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...
707
            throw new \Exception(sprintf('Configured source directory %s is not available!', $sourceDir));
708
        }
709
710
        // init file iterator on source directory
711
        $fileIterator = new \FilesystemIterator($sourceDir);
712
713
        // log the number of files that has to be archived
714
        $this->log(sprintf('Found %d files to archive in directory %s', $fileCounter, $sourceDir), LogLevel::INFO);
0 ignored issues
show
Bug introduced by
The variable $fileCounter does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
715
716
        // initialize the directory to create the archive in
717
        $archiveDir = sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getConfiguration()->getArchiveDir());
718
719
        // query whether or not the directory already exists
720
        if (!is_dir($archiveDir)) {
721
            mkdir($archiveDir);
722
        }
723
724
        // create the ZIP archive
725
        $archive = new \ZipArchive();
726
        $archive->open($archiveFile = sprintf('%s/%s.zip', $archiveDir, $this->getSerial()), \ZipArchive::CREATE);
727
728
        // iterate through all files and add them to the ZIP archive
729
        foreach ($fileIterator as $filename) {
730
            $archive->addFile($filename);
731
        }
732
733
        // save the ZIP archive
734
        $archive->close();
735
736
        // finally remove the directory with the imported files
737
        $this->removeDir($sourceDir);
738
739
        // and and log a message that the import artefacts have been archived
740
        $this->log(sprintf('Successfully archived imported files to %s!', $archiveFile), LogLevel::INFO);
741
    }
742
743
    /**
744
     * Removes the passed directory recursively.
745
     *
746
     * @param string $src Name of the directory to remove
747
     *
748
     * @return void
749
     * @throws \Exception Is thrown, if the directory can not be removed
750
     */
751
    protected function removeDir($src)
752
    {
753
754
        // open the directory
755
        $dir = opendir($src);
756
757
        // remove files/folders recursively
758
        while (false !== ($file = readdir($dir))) {
759
            if (($file != '.') && ($file != '..')) {
760
                $full = $src . '/' . $file;
761
                if (is_dir($full)) {
762
                    $this->removeDir($full);
763
                } else {
764
                    if (!unlink($full)) {
765
                        throw new \Exception(sprintf('Can\'t remove file %s', $full));
766
                    }
767
                }
768
            }
769
        }
770
771
        // close handle and remove directory itself
772
        closedir($dir);
773
        if (!rmdir($src)) {
774
            throw new \Exception(sprintf('Can\'t remove directory %s', $src));
775
        }
776
    }
777
778
    /**
779
     * Simple method that writes the passed method the the console and the
780
     * system logger, if configured and a log level has been passed.
781
     *
782
     * @param string $msg      The message to log
783
     * @param string $logLevel The log level to use
784
     *
785
     * @return void
786
     */
787
    protected function log($msg, $logLevel = null)
788
    {
789
790
        // initialize the formatter helper
791
        $helper = new FormatterHelper();
792
793
        // map the log level to the console style
794
        $style = $this->mapLogLevelToStyle($logLevel);
795
796
        // format the message, according to the passed log level and write it to the console
797
        $this->getOutput()->writeln($logLevel ? $helper->formatBlock($msg, $style) : $msg);
798
799
        // log the message if a log level has been passed
800
        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...
801
            $systemLogger->log($logLevel, $msg);
802
        }
803
    }
804
805
    /**
806
     * Map's the passed log level to a valid symfony console style.
807
     *
808
     * @param string $logLevel The log level to map
809
     *
810
     * @return string The apropriate symfony console style
811
     */
812
    protected function mapLogLevelToStyle($logLevel)
813
    {
814
815
        // query whether or not the log level is mapped
816
        if (isset($this->logLevelStyleMapping[$logLevel])) {
817
            return $this->logLevelStyleMapping[$logLevel];
818
        }
819
820
        // return the default style => info
821
        return Simple::DEFAULT_STYLE;
822
    }
823
824
    /**
825
     * Lifecycle callback that will be inovked after the
826
     * import process has been finished.
827
     *
828
     * @return void
829
     * @throws \Exception Is thrown, if the
830
     */
831
    protected function tearDown()
832
    {
833
    }
834
835
    /**
836
     * This method finishes the import process and cleans the registry.
837
     *
838
     * @return void
839
     */
840
    protected function finish()
841
    {
842
843
        // remove the import status from the registry
844
        $this->getRegistryProcessor()->removeAttribute($this->getSerial());
845
846
        // remove the PID to the temporay directory
847
        unlink(sprintf('%s/importer.pid', sys_get_temp_dir()));
848
    }
849
}
850