Completed
Push — master ( 75ab86...b134a3 )
by Tim
11s
created

Simple::getRegistryProcessor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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
    protected 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
    protected function getSourceDir()
207
    {
208
        return $this->getConfiguration()->getSourceDir();
209
    }
210
211
    /**
212
     * Parse the temporary upload directory for new files to be imported.
213
     *
214
     * @return void
215
     * @throws \Exception Is thrown, if the import process can't be finished successfully
216
     */
217
    public function import()
218
    {
219
220
        // track the start time
221
        $startTime = microtime(true);
222
223
        try {
224
            // generate the serial for the new job
225
            $this->setSerial(Uuid::uuid4()->__toString());
226
227
            // prepare the global data for the import process
228
            $this->start();
229
            $this->setUp();
230
            $this->processSubjects();
231
            $this->archive();
232
            $this->tearDown();
233
            $this->finish();
234
235
            // track the time needed for the import in seconds
236
            $endTime = microtime(true) - $startTime;
237
238
            // log a message that import has been finished
239
            $this->getSystemLogger()->info(sprintf('Successfully finished import with serial %s in %f s', $this->getSerial(), $endTime));
240
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
241
        } catch (\Exception $e) {
242
            // tear down
243
            $this->tearDown();
244
            $this->finish();
245
246
            // track the time needed for the import in seconds
247
            $endTime = microtime(true) - $startTime;
248
249
            // log a message that the file import failed
250
            $this->getSystemLogger()->error($e->__toString());
251
252
            // log a message that import has been finished
253
            $this->getSystemLogger()->error(sprintf('Can\'t finish import with serial %s in %f s', $this->getSerial(), $endTime));
254
255
            // re-throw the exception
256
            throw $e;
257
        }
258
    }
259
260
    /**
261
     * This method start's the import process by initializing
262
     * the status and appends it to the registry.
263
     *
264
     * @return void
265
     */
266
    protected function start()
267
    {
268
269
        // log a message that import has been started
270
        $this->getSystemLogger()->info(sprintf('Now start import with serial %s', $this->getSerial()));
271
272
        // initialize the status
273
        $status = array(
274
            RegistryKeys::STATUS => 1,
275
            RegistryKeys::SOURCE_DIRECTORY => $this->getConfiguration()->getSourceDir()
276
        );
277
278
        // initialize the status information for the subjects */
279
        /** @var \TechDivision\Import\Configuration\SubjectInterface $subject */
280
        foreach ($this->getConfiguration()->getSubjects() as $subject) {
281
            $status[$subject->getPrefix()] = array();
282
        }
283
284
        // append it to the registry
285
        $this->getRegistryProcessor()->setAttribute($this->getSerial(), $status);
286
    }
287
288
    /**
289
     * Prepares the global data for the import process.
290
     *
291
     * @return void
292
     */
293
    protected function setUp()
294
    {
295
296
        // load the registry
297
        $importProcessor = $this->getImportProcessor();
298
        $registryProcessor = $this->getRegistryProcessor();
299
300
        // initialize the array for the global data
301
        $globalData = array();
302
303
        // initialize the global data
304
        $globalData[RegistryKeys::STORES] = $importProcessor->getStores();
305
        $globalData[RegistryKeys::LINK_TYPES] = $importProcessor->getLinkTypes();
306
        $globalData[RegistryKeys::TAX_CLASSES] = $importProcessor->getTaxClasses();
307
        $globalData[RegistryKeys::DEFAULT_STORE] = $importProcessor->getDefaultStore();
308
        $globalData[RegistryKeys::STORE_WEBSITES] = $importProcessor->getStoreWebsites();
309
        $globalData[RegistryKeys::LINK_ATTRIBUTES] = $importProcessor->getLinkAttributes();
310
        $globalData[RegistryKeys::ROOT_CATEGORIES] = $importProcessor->getRootCategories();
311
        $globalData[RegistryKeys::CORE_CONFIG_DATA] = $importProcessor->getCoreConfigData();
312
        $globalData[RegistryKeys::ATTRIBUTE_SETS] = $eavAttributeSets = $importProcessor->getEavAttributeSetsByEntityTypeId(4);
313
314
        // prepare the categories
315
        $categories = array();
316
        foreach ($importProcessor->getCategories() as $category) {
317
            // expload the entity IDs from the category path
318
            $entityIds = explode('/', $category[MemberNames::PATH]);
319
320
            // cut-off the root category
321
            array_shift($entityIds);
322
323
            // continue with the next category if no entity IDs are available
324
            if (sizeof($entityIds) === 0) {
325
                continue;
326
            }
327
328
            // initialize the array for the path elements
329
            $path = array();
330
            foreach ($importProcessor->getCategoryVarcharsByEntityIds($entityIds) as $cat) {
331
                $path[] = $cat[MemberNames::VALUE];
332
            }
333
334
            // append the catogory with the string path as key
335
            $categories[implode('/', $path)] = $category;
336
        }
337
338
        // initialize the array with the categories
339
        $globalData[RegistryKeys::CATEGORIES] = $categories;
340
341
        // prepare an array with the EAV attributes grouped by their attribute set name as keys
342
        $eavAttributes = array();
343
        foreach (array_keys($eavAttributeSets) as $eavAttributeSetName) {
344
            $eavAttributes[$eavAttributeSetName] = $importProcessor->getEavAttributesByEntityTypeIdAndAttributeSetName(4, $eavAttributeSetName);
345
        }
346
347
        // initialize the array with the EAV attributes
348
        $globalData[RegistryKeys::EAV_ATTRIBUTES] = $eavAttributes;
349
350
        // add the status with the global data
351
        $registryProcessor->mergeAttributesRecursive(
352
            $this->getSerial(),
353
            array(RegistryKeys::GLOBAL_DATA => $globalData)
354
        );
355
    }
356
357
    /**
358
     * Process all the subjects defined in the system configuration.
359
     *
360
     * @return void
361
     * @throws \Exception Is thrown, if one of the subjects can't be processed
362
     */
363
    protected function processSubjects()
364
    {
365
366
        try {
367
            // load system logger and registry
368
            $systemLogger = $this->getSystemLogger();
0 ignored issues
show
Unused Code introduced by
$systemLogger 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...
369
            $importProcessor = $this->getImportProcessor();
370
371
            // load the subjects
372
            $subjects = $this->getConfiguration()->getSubjects();
373
374
            // start the transaction
375
            $importProcessor->getConnection()->beginTransaction();
376
377
            // process all the subjects found in the system configuration
378
            foreach ($subjects as $subject) {
379
                $this->processSubject($subject);
380
            }
381
382
            // commit the transaction
383
            $importProcessor->getConnection()->commit();
384
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
385
        } catch (\Exception $e) {
386
            // rollback the transaction
387
            $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...
388
389
            // re-throw the exception
390
            throw $e;
391
        }
392
    }
393
394
    /**
395
     * Process the subject with the passed name/identifier.
396
     *
397
     * We create a new, fresh and separate subject for EVERY file here, because this would be
398
     * the starting point to parallelize the import process in a multithreaded/multiprocessed
399
     * environment.
400
     *
401
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
402
     *
403
     * @return void
404
     * @throws \Exception Is thrown, if the subject can't be processed
405
     */
406
    protected function processSubject($subject)
407
    {
408
409
        // clear the filecache
410
        clearstatcache();
411
412
        // load the system logger
413
        $systemLogger = $this->getSystemLogger();
414
415
        // load the actual status
416
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
417
418
        // query whether or not the configured source directory is available
419 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...
420
            throw new \Exception(sprintf('Configured source directory %s is not available!', $sourceDir));
421
        }
422
423
        // initialize the file iterator on source directory
424
        $fileIterator = new \FilesystemIterator($sourceDir);
425
426
        // log a debug message
427
        $systemLogger->debug(
428
            sprintf('Now checking directory %s for files to be imported', $sourceDir)
429
        );
430
431
        // iterate through all CSV files and process the subjects
432
        foreach ($fileIterator as $filename) {
433
            $this->subjectFactory($subject)->import($this->getSerial(), $filename->getPathname());
434
        }
435
436
        // and and log a message that the subject has been processed
437
        $systemLogger->info(sprintf('Successfully processed subject %s!', $subject->getClassName()));
438
    }
439
440
    /**
441
     * Factory method to create new handler instances.
442
     *
443
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
444
     *
445
     * @return object The handler instance
446
     */
447
    public function subjectFactory($subject)
448
    {
449
450
        // load the subject class name
451
        $className = $subject->getClassName();
452
453
        // the database connection to use
454
        $connection = $this->getImportProcessor()->getConnection();
455
456
        // initialize a new handler with the passed class name
457
        $instance = new $className();
458
459
        // $instance the handler instance
460
        $instance->setConfiguration($subject);
461
        $instance->setSystemLogger($this->getSystemLogger());
462
        $instance->setRegistryProcessor($this->getRegistryProcessor());
463
464
        // instanciate and set the product processor, if specified
465
        if ($processorFactory = $subject->getProcessorFactory()) {
466
            $productProcessor = $processorFactory::factory($connection, $subject);
467
            $instance->setProductProcessor($productProcessor);
468
        }
469
470
        // initialize the callbacks/visitors
471
        CallbackVisitor::get()->visit($instance);
472
        ObserverVisitor::get()->visit($instance);
473
474
        // return the subject instance
475
        return $instance;
476
    }
477
478
    /**
479
     * Lifecycle callback that will be inovked after the
480
     * import process has been finished.
481
     *
482
     * @return void
483
     * @throws \Exception Is thrown, if the
484
     */
485
    protected function archive()
486
    {
487
488
        // query whether or not, the import artefacts have to be archived
489
        if (!$this->getConfiguration()->haveArchiveArtefacts()) {
490
            return;
491
        }
492
493
        // load the system logger
494
        $systemLogger = $this->getSystemLogger();
495
496
        // clear the filecache
497
        clearstatcache();
498
499
        // load the actual status
500
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
501
502
        // query whether or not the configured source directory is available
503 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...
504
            throw new \Exception(sprintf('Configured source directory %s is not available!', $sourceDir));
505
        }
506
507
        // init file iterator on source directory
508
        $fileIterator = new \FilesystemIterator($sourceDir);
509
510
        // log the number of files that has to be archived
511
        $fileCounter = iterator_count($fileIterator);
512
513
        // if no files are available, return
514
        if ($fileCounter == 0) {
515
            // log the number of files that has to be archived
516
            $systemLogger->info(sprintf('Found no files to archive'));
517
            return;
518
        }
519
520
        // log the number of files that has to be archived
521
        $systemLogger->info(sprintf('Found %d files to archive in directory %s', $fileCounter, $sourceDir));
522
523
        // initialize the directory to create the archive in
524
        $archiveDir = sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getConfiguration()->getArchiveDir());
525
526
        // query whether or not the directory already exists
527
        if (!is_dir($archiveDir)) {
528
            mkdir($archiveDir);
529
        }
530
531
        // create the ZIP archive
532
        $archive = new \ZipArchive();
533
        $archive->open($archiveFile = sprintf('%s/%s.zip', $archiveDir, $this->getSerial()), \ZipArchive::CREATE);
534
535
        // iterate through all files and add them to the ZIP archive
536
        foreach ($fileIterator as $filename) {
537
            $archive->addFile($filename);
538
        }
539
540
        // save the ZIP archive
541
        $archive->close();
542
543
        // finally remove the directory with the imported files
544
        $this->removeDir($sourceDir);
545
546
        // and and log a message that the import artefacts have been archived
547
        $systemLogger->info(sprintf('Successfully archived imported files to %s!', $archiveFile));
548
    }
549
550
    /**
551
     * Removes the passed directory recursively.
552
     *
553
     * @param string $src Name of the directory to remove
554
     *
555
     * @return void
556
     */
557
    protected function removeDir($src)
558
    {
559
560
        // open the directory
561
        $dir = opendir($src);
562
563
        // remove files/folders recursively
564
        while (false !== ($file = readdir($dir))) {
565
            if (($file != '.') && ($file != '..')) {
566
                $full = $src . '/' . $file;
567
                if (is_dir($full)) {
568
                    Simple::removeDir($full);
569
                } else {
570
                    unlink($full);
571
                }
572
            }
573
        }
574
575
        // close handle and remove directory itself
576
        closedir($dir);
577
        rmdir($src);
578
    }
579
580
    /**
581
     * Lifecycle callback that will be inovked after the
582
     * import process has been finished.
583
     *
584
     * @return void
585
     * @throws \Exception Is thrown, if the
586
     */
587
    protected function tearDown()
588
    {
589
    }
590
591
    /**
592
     * This method finishes the import process and cleans the registry.
593
     *
594
     * @return void
595
     */
596
    protected function finish()
597
    {
598
        $this->getRegistryProcessor()->removeAttribute($this->getSerial());
599
    }
600
}
601