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

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