Completed
Push — master ( cb9b12...0b79a8 )
by Tim
8s
created

Simple   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 463
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 3.4%

Importance

Changes 6
Bugs 0 Features 2
Metric Value
wmc 33
c 6
b 0
f 2
lcom 1
cbo 7
dl 0
loc 463
ccs 5
cts 147
cp 0.034
rs 9.3999

20 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 getPrefix() 0 4 1
A getSourceDir() 0 4 1
A import() 0 22 1
A start() 0 18 2
B setUp() 0 68 6
B processSubjects() 0 30 3
B processSubject() 0 66 6
B subjectFactory() 0 29 1
A tearDown() 0 4 1
A finish() 0 4 1
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\LoggerInterface;
25
use TechDivision\Import\Utils\MemberNames;
26
use TechDivision\Import\Utils\RegistryKeys;
27
use TechDivision\Import\ConfigurationInterface;
28
use TechDivision\Import\Subjects\SubjectInterface;
29
use TechDivision\Import\Cli\Callbacks\CallbackVisitor;
30
use TechDivision\Import\Cli\Observers\ObserverVisitor;
31
use TechDivision\Import\Services\ImportProcessorInterface;
32
use TechDivision\Import\Services\RegistryProcessorInterface;
33
34
/**
35
 * A SLSB that handles the product import process.
36
 *
37
 * @author    Tim Wagner <[email protected]>
38
 * @copyright 2016 TechDivision GmbH <[email protected]>
39
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
40
 * @link      https://github.com/techdivision/import-cli-simple
41
 * @link      http://www.techdivision.com
42
 */
43
class Simple
44
{
45
46
    /**
47
     * The actions unique serial.
48
     *
49
     * @var string
50
     */
51
    protected $serial;
52
53
    /**
54
     * The system logger implementation.
55
     *
56
     * @var \Psr\Log\LoggerInterface
57
     */
58
    protected $systemLogger;
59
60
    /**
61
     * The RegistryProcessor instance to handle running threads.
62
     *
63
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
64
     */
65
    protected $registryProcessor;
66
67
    /**
68
     * The processor to read/write the necessary import data.
69
     *
70
     * @var \TechDivision\Import\Services\ImportProcessorInterface
71
     */
72
    protected $importProcessor;
73
74
    /**
75
     * The system configuration.
76
     *
77
     * @var \TechDivision\Import\ConfigurationInterface
78
     */
79
    protected $configuration;
80
81
    /**
82
     * Set's the unique serial for this import process.
83
     *
84
     * @param string $serial The unique serial
85
     *
86
     * @return void
87
     */
88
    public function setSerial($serial)
89
    {
90
        $this->serial = $serial;
91
    }
92
93
    /**
94
     * Return's the unique serial for this import process.
95
     *
96
     * @return string The unique serial
97
     */
98
    public function getSerial()
99
    {
100
        return $this->serial;
101
    }
102
103
    /**
104
     * Set's the system logger.
105
     *
106
     * @param \Psr\Log\LoggerInterface $systemLogger The system logger
107
     *
108
     * @return void
109
     */
110
    public function setSystemLogger(LoggerInterface $systemLogger)
111
    {
112
        $this->systemLogger = $systemLogger;
113
    }
114
115
    /**
116
     * Return's the system logger.
117
     *
118
     * @return \Psr\Log\LoggerInterface The system logger instance
119
     */
120
    public function getSystemLogger()
121
    {
122
        return $this->systemLogger;
123
    }
124
125
    /**
126
     * Sets's the RegistryProcessor instance to handle the running threads.
127
     *
128
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor The registry processor instance
129
     *
130
     * @return void
131
     */
132
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
133
    {
134
        $this->registryProcessor = $registryProcessor;
135
    }
136
137
    /**
138
     * Return's the RegistryProcessor instance to handle the running threads.
139
     *
140
     * @return \TechDivision\Import\Services\RegistryProcessor The registry processor instance
141
     */
142
    public function getRegistryProcessor()
143
    {
144
        return $this->registryProcessor;
145
    }
146
147
    /**
148
     * Set's the import processor instance.
149
     *
150
     * @param \TechDivision\Import\Services\ImportProcessorInterface $importProcessor The import processor instance
151
     *
152
     * @return void
153
     */
154 1
    public function setImportProcessor(ImportProcessorInterface $importProcessor)
155
    {
156 1
        $this->importProcessor = $importProcessor;
157 1
    }
158
159
    /**
160
     * Return's the import processor instance.
161
     *
162
     * @return \TechDivision\Import\Services\ImportProcessorInterface The import processor instance
163
     */
164 1
    public function getImportProcessor()
165
    {
166 1
        return $this->importProcessor;
167
    }
168
169
    /**
170
     * Set's the system configuration.
171
     *
172
     * @param \TechDivision\Import\ConfigurationInterface $configuration The system configuration
173
     *
174
     * @return void
175
     */
176
    public function setConfiguration(ConfigurationInterface $configuration)
177
    {
178
        $this->configuration = $configuration;
179
    }
180
181
    /**
182
     * Return's the system configuration.
183
     *
184
     * @return \TechDivision\Import\ConfigurationInterface The system configuration
185
     */
186
    public function getConfiguration()
187
    {
188
        return $this->configuration;
189
    }
190
191
    /**
192
     * Return's the prefix for the import files.
193
     *
194
     * @return string The prefix
195
     */
196
    public function getPrefix()
197
    {
198
        return $this->getConfiguration()->getPrefix();
0 ignored issues
show
Bug introduced by
The method getPrefix() does not seem to exist on object<TechDivision\Impo...ConfigurationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
199
    }
200
201
    /**
202
     * Return's the source directory that has to be watched for new files.
203
     *
204
     * @return string The source directory
205
     */
206
    public function getSourceDir()
207
    {
208
        return $this->getConfiguration()->getSourceDir();
0 ignored issues
show
Bug introduced by
The method getSourceDir() does not seem to exist on object<TechDivision\Impo...ConfigurationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
209
    }
210
211
    /**
212
     * Parse the temporary upload directory for new files to be imported.
213
     *
214
     * @return void
215
     */
216
    public function import()
217
    {
218
219
        // track the start time
220
        $startTime = microtime(true);
221
222
        // generate the serial for the new job
223
        $this->setSerial(Uuid::uuid4()->__toString());
224
225
        // prepare the global data for the import process
226
        $this->start();
227
        $this->setUp();
228
        $this->processSubjects();
229
        $this->tearDown();
230
        $this->finish();
231
232
        // track the time needed for the import in seconds
233
        $endTime = microtime(true) - $startTime;
234
235
        // log a message that import has been finished
236
        $this->getSystemLogger()->info(sprintf('Successfully finished import with serial %s in %f s', $this->getSerial(), $endTime));
237
    }
238
239
    /**
240
     * This method start's the import process by initializing
241
     * the status and appends it to the registry.
242
     *
243
     * @return void
244
     */
245
    public function start()
246
    {
247
248
        // log a message that import has been started
249
        $this->getSystemLogger()->info(sprintf('Now start import with serial %s', $this->getSerial()));
250
251
        // initialize the status
252
        $status = array(RegistryKeys::STATUS => 1);
253
254
        // initialize the status information for the subjects */
255
        /** @var \TechDivision\Import\Configuration\SubjectInterface $subject */
256
        foreach ($this->getConfiguration()->getSubjects() as $subject) {
257
            $status[$subject->getPrefix()] = array();
258
        }
259
260
        // append it to the registry
261
        $this->getRegistryProcessor()->setAttribute($this->getSerial(), $status);
262
    }
263
264
    /**
265
     * Prepares the global data for the import process.
266
     *
267
     * @return void
268
     */
269
    public function setUp()
270
    {
271
272
        try {
273
            // load the registry
274
            $importProcessor = $this->getImportProcessor();
275
            $registryProcessor = $this->getRegistryProcessor();
276
277
            // initialize the array for the global data
278
            $globalData = array();
279
280
            // initialize the global data
281
            $globalData[RegistryKeys::STORES] = $importProcessor->getStores();
282
            $globalData[RegistryKeys::LINK_TYPES] = $importProcessor->getLinkTypes();
283
            $globalData[RegistryKeys::TAX_CLASSES] = $importProcessor->getTaxClasses();
284
            $globalData[RegistryKeys::DEFAULT_STORE] = $importProcessor->getDefaultStore();
285
            $globalData[RegistryKeys::STORE_WEBSITES] = $importProcessor->getStoreWebsites();
286
            $globalData[RegistryKeys::LINK_ATTRIBUTES] = $importProcessor->getLinkAttributes();
287
            $globalData[RegistryKeys::ROOT_CATEGORIES] = $importProcessor->getRootCategories();
288
            $globalData[RegistryKeys::CORE_CONFIG_DATA] = $importProcessor->getCoreConfigData();
289
            $globalData[RegistryKeys::ATTRIBUTE_SETS] = $eavAttributeSets = $importProcessor->getEavAttributeSetsByEntityTypeId(4);
290
291
            // prepare the categories
292
            $categories = array();
293
            foreach ($importProcessor->getCategories() as $category) {
294
                // expload the entity IDs from the category path
295
                $entityIds = explode('/', $category[MemberNames::PATH]);
296
297
                // cut-off the root category
298
                array_shift($entityIds);
299
300
                // continue with the next category if no entity IDs are available
301
                if (sizeof($entityIds) === 0) {
302
                    continue;
303
                }
304
305
                // initialize the array for the path elements
306
                $path = array();
307
                foreach ($importProcessor->getCategoryVarcharsByEntityIds($entityIds) as $cat) {
308
                    $path[] = $cat[MemberNames::VALUE];
309
                }
310
311
                // append the catogory with the string path as key
312
                $categories[implode('/', $path)] = $category;
313
            }
314
315
            // initialize the array with the categories
316
            $globalData[RegistryKeys::CATEGORIES] = $categories;
317
318
            // prepare an array with the EAV attributes grouped by their attribute set name as keys
319
            $eavAttributes = array();
320
            foreach (array_keys($eavAttributeSets) as $eavAttributeSetName) {
321
                $eavAttributes[$eavAttributeSetName] = $importProcessor->getEavAttributesByEntityTypeIdAndAttributeSetName(4, $eavAttributeSetName);
322
            }
323
324
            // initialize the array with the EAV attributes
325
            $globalData[RegistryKeys::EAV_ATTRIBUTES] = $eavAttributes;
326
327
            // add the status with the global data
328
            $registryProcessor->mergeAttributesRecursive(
329
                $this->getSerial(),
330
                array(RegistryKeys::GLOBAL_DATA => $globalData)
331
            );
332
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
333
        } catch (\Exception $e) {
334
            $this->getSystemLogger()->error($e->__toString());
335
        }
336
    }
337
338
    /**
339
     * Process all the subjects defined in the system configuration.
340
     *
341
     * @return void
342
     */
343
    public function processSubjects()
344
    {
345
346
        try {
347
            // load system logger and registry
348
            $systemLogger = $this->getSystemLogger();
349
            $importProcessor = $this->getImportProcessor();
350
351
            // load the subjects
352
            $subjects = $this->getConfiguration()->getSubjects();
353
354
            // start the transaction
355
            $importProcessor->getConnection()->beginTransaction();
356
357
            // process all the subjects found in the system configuration
358
            foreach ($subjects as $subject) {
359
                $this->processSubject($subject);
360
            }
361
362
            // commit the transaction
363
            $importProcessor->getConnection()->commit();
364
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
365
        } catch (\Exception $e) {
366
            // log a message with the stack trace
367
            $systemLogger->error($e->__toString());
368
369
            // rollback the transaction
370
            $importProcessor->getConnection()->rollBack();
0 ignored issues
show
Bug introduced by
The variable $importProcessor does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
371
        }
372
    }
373
374
    /**
375
     * Process the subject with the passed name/identifier.
376
     *
377
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
378
     *
379
     * @return void
380
     */
381
    public function processSubject($subject)
382
    {
383
384
        // load the system logger
385
        $systemLogger = $this->getSystemLogger();
386
387
        // init file iterator on deployment directory
388
        $fileIterator = new \FilesystemIterator($sourceDir = $subject->getSourceDir());
389
390
        // clear the filecache
391
        clearstatcache();
392
393
        // prepare the regex to find the files to be imported
394
        $regex = sprintf('/^.*\/%s.*\\.csv$/', $subject->getPrefix());
395
396
        // log a debug message
397
        $systemLogger->debug(
398
            sprintf('Now checking directory %s for files with regex %s to import', $sourceDir, $regex)
399
        );
400
401
        // iterate through all CSV files and start import process
402
        foreach (new \RegexIterator($fileIterator, $regex) as $filename) {
403
            try {
404
                // prepare the flag filenames
405
                $inProgressFilename = sprintf('%s.inProgress', $filename);
406
                $importedFilename = sprintf('%s.imported', $filename);
407
                $failedFilename = sprintf('%s.failed', $filename);
408
409
                // query whether or not the file has already been imported
410
                if (is_file($failedFilename) ||
411
                    is_file($importedFilename) ||
412
                    is_file($inProgressFilename)
413
                ) {
414
                    // log a debug message
415
                    $systemLogger->debug(
416
                        sprintf('Import running, found inProgress file %s', $inProgressFilename)
417
                    );
418
419
                    // ignore the file
420
                    continue;
421
                }
422
423
                // flag file as in progress
424
                touch($inProgressFilename);
425
426
                // process the subject
427
                $this->subjectFactory($subject)->import($this->getSerial(), $filename->getPathname());
428
429
                // rename flag file, because import has been successfull
430
                rename($inProgressFilename, $importedFilename);
431
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
432
            } catch (\Exception $e) {
433
                // rename the flag file, because import failed
434
                rename($inProgressFilename, $failedFilename);
0 ignored issues
show
Bug introduced by
The variable $failedFilename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
435
436
                // log a message that the file import failed
437
                $this->getSystemLogger()->error($e->__toString());
438
439
                // re-throw the exception
440
                throw $e;
441
            }
442
        }
443
444
        // and and log a message that the subject has been processed
445
        $systemLogger->info(sprintf('Successfully processed subject %s!', $subject->getClassName()));
446
    }
447
448
    /**
449
     * Factory method to create new handler instances.
450
     *
451
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
452
     *
453
     * @return object The handler instance
454
     */
455
    public function subjectFactory($subject)
456
    {
457
458
        // load the subject class name
459
        $className = $subject->getClassName();
460
461
        // the database connection to use
462
        $connection = $this->getImportProcessor()->getConnection();
463
464
        // initialize a new handler with the passed class name
465
        $instance = new $className();
466
467
        // $instance the handler instance
468
        $instance->setConfiguration($subject);
469
        $instance->setSystemLogger($this->getSystemLogger());
470
        $instance->setRegistryProcessor($this->getRegistryProcessor());
471
472
        // instanciate and set the product processor
473
        $processorFactory = $subject->getProcessorFactory();
474
        $productProcessor = $processorFactory::factory($connection, $subject);
475
        $instance->setProductProcessor($productProcessor);
476
477
        // initialize the callbacks/visitors
478
        CallbackVisitor::get()->visit($instance);
479
        ObserverVisitor::get()->visit($instance);
480
481
        // return the subject instance
482
        return $instance;
483
    }
484
485
    /**
486
     * Lifecycle callback that will be inovked after the
487
     * import process has been finished.
488
     *
489
     * @return void
490
     */
491
    public function tearDown()
492
    {
493
        // clean up here
494
    }
495
496
    /**
497
     * This method finishes the import process and cleans the registry.
498
     *
499
     * @return void
500
     */
501
    public function finish()
502
    {
503
        $this->getRegistryProcessor()->removeAttribute($this->getSerial());
504
    }
505
}
506