Completed
Push — master ( f3fc16...c60f27 )
by Tim
9s
created

Simple::setSystemLogger()   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
    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
     * @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
    public 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(RegistryKeys::STATUS => 1);
273
274
        // initialize the status information for the subjects */
275
        /** @var \TechDivision\Import\Configuration\SubjectInterface $subject */
276
        foreach ($this->getConfiguration()->getSubjects() as $subject) {
277
            $status[$subject->getPrefix()] = array();
278
        }
279
280
        // append it to the registry
281
        $this->getRegistryProcessor()->setAttribute($this->getSerial(), $status);
282
    }
283
284
    /**
285
     * Prepares the global data for the import process.
286
     *
287
     * @return void
288
     */
289
    public function setUp()
290
    {
291
292
        // load the registry
293
        $importProcessor = $this->getImportProcessor();
294
        $registryProcessor = $this->getRegistryProcessor();
295
296
        // initialize the array for the global data
297
        $globalData = array();
298
299
        // initialize the global data
300
        $globalData[RegistryKeys::STORES] = $importProcessor->getStores();
301
        $globalData[RegistryKeys::LINK_TYPES] = $importProcessor->getLinkTypes();
302
        $globalData[RegistryKeys::TAX_CLASSES] = $importProcessor->getTaxClasses();
303
        $globalData[RegistryKeys::DEFAULT_STORE] = $importProcessor->getDefaultStore();
304
        $globalData[RegistryKeys::STORE_WEBSITES] = $importProcessor->getStoreWebsites();
305
        $globalData[RegistryKeys::LINK_ATTRIBUTES] = $importProcessor->getLinkAttributes();
306
        $globalData[RegistryKeys::ROOT_CATEGORIES] = $importProcessor->getRootCategories();
307
        $globalData[RegistryKeys::CORE_CONFIG_DATA] = $importProcessor->getCoreConfigData();
308
        $globalData[RegistryKeys::ATTRIBUTE_SETS] = $eavAttributeSets = $importProcessor->getEavAttributeSetsByEntityTypeId(4);
309
310
        // prepare the categories
311
        $categories = array();
312
        foreach ($importProcessor->getCategories() as $category) {
313
            // expload the entity IDs from the category path
314
            $entityIds = explode('/', $category[MemberNames::PATH]);
315
316
            // cut-off the root category
317
            array_shift($entityIds);
318
319
            // continue with the next category if no entity IDs are available
320
            if (sizeof($entityIds) === 0) {
321
                continue;
322
            }
323
324
            // initialize the array for the path elements
325
            $path = array();
326
            foreach ($importProcessor->getCategoryVarcharsByEntityIds($entityIds) as $cat) {
327
                $path[] = $cat[MemberNames::VALUE];
328
            }
329
330
            // append the catogory with the string path as key
331
            $categories[implode('/', $path)] = $category;
332
        }
333
334
        // initialize the array with the categories
335
        $globalData[RegistryKeys::CATEGORIES] = $categories;
336
337
        // prepare an array with the EAV attributes grouped by their attribute set name as keys
338
        $eavAttributes = array();
339
        foreach (array_keys($eavAttributeSets) as $eavAttributeSetName) {
340
            $eavAttributes[$eavAttributeSetName] = $importProcessor->getEavAttributesByEntityTypeIdAndAttributeSetName(4, $eavAttributeSetName);
341
        }
342
343
        // initialize the array with the EAV attributes
344
        $globalData[RegistryKeys::EAV_ATTRIBUTES] = $eavAttributes;
345
346
        // add the status with the global data
347
        $registryProcessor->mergeAttributesRecursive(
348
            $this->getSerial(),
349
            array(RegistryKeys::GLOBAL_DATA => $globalData)
350
        );
351
    }
352
353
    /**
354
     * Process all the subjects defined in the system configuration.
355
     *
356
     * @return void
357
     * @throws \Exception Is thrown, if one of the subjects can't be processed
358
     */
359
    public function processSubjects()
360
    {
361
362
        try {
363
            // load system logger and registry
364
            $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...
365
            $importProcessor = $this->getImportProcessor();
366
367
            // load the subjects
368
            $subjects = $this->getConfiguration()->getSubjects();
369
370
            // start the transaction
371
            $importProcessor->getConnection()->beginTransaction();
372
373
            // process all the subjects found in the system configuration
374
            foreach ($subjects as $subject) {
375
                $this->processSubject($subject);
376
            }
377
378
            // commit the transaction
379
            $importProcessor->getConnection()->commit();
380
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
381
        } catch (\Exception $e) {
382
            // rollback the transaction
383
            $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...
384
385
            // re-throw the exception
386
            throw $e;
387
        }
388
    }
389
390
    /**
391
     * Process the subject with the passed name/identifier.
392
     *
393
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
394
     *
395
     * @return void
396
     * @throws \Exception Is thrown, if the subject can't be processed
397
     */
398
    public function processSubject($subject)
399
    {
400
401
        // load the system logger
402
        $systemLogger = $this->getSystemLogger();
403
404
        // init file iterator on deployment directory
405
        $fileIterator = new \FilesystemIterator($sourceDir = $subject->getSourceDir());
406
407
        // clear the filecache
408
        clearstatcache();
409
410
        // prepare the regex to find the files to be imported
411
        $regex = sprintf('/^.*\/%s.*\\.csv$/', $subject->getPrefix());
412
413
        // log a debug message
414
        $systemLogger->debug(
415
            sprintf('Now checking directory %s for files with regex %s to import', $sourceDir, $regex)
416
        );
417
418
        // iterate through all CSV files and start import process
419
        foreach (new \RegexIterator($fileIterator, $regex) as $filename) {
420
            try {
421
                // prepare the flag filenames
422
                $inProgressFilename = sprintf('%s.inProgress', $filename);
423
                $importedFilename = sprintf('%s.imported', $filename);
424
                $failedFilename = sprintf('%s.failed', $filename);
425
426
                // query whether or not the file has already been imported
427
                if (is_file($failedFilename) ||
428
                    is_file($importedFilename) ||
429
                    is_file($inProgressFilename)
430
                ) {
431
                    // log a debug message
432
                    $systemLogger->debug(
433
                        sprintf('Import running, found inProgress file %s', $inProgressFilename)
434
                    );
435
436
                    // ignore the file
437
                    continue;
438
                }
439
440
                // flag file as in progress
441
                touch($inProgressFilename);
442
443
                // process the subject
444
                $this->subjectFactory($subject)->import($this->getSerial(), $filename->getPathname());
445
446
                // rename flag file, because import has been successfull
447
                rename($inProgressFilename, $importedFilename);
448
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
449
            } catch (\Exception $e) {
450
                // rename the flag file, because import failed and write the stack trace
451
                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...
452
                file_put_contents($failedFilename, $e->__toString());
453
454
                // re-throw the exception
455
                throw $e;
456
            }
457
        }
458
459
        // and and log a message that the subject has been processed
460
        $systemLogger->info(sprintf('Successfully processed subject %s!', $subject->getClassName()));
461
    }
462
463
    /**
464
     * Factory method to create new handler instances.
465
     *
466
     * @param \TechDivision\Import\Configuration\Subject $subject The subject configuration
467
     *
468
     * @return object The handler instance
469
     */
470
    public function subjectFactory($subject)
471
    {
472
473
        // load the subject class name
474
        $className = $subject->getClassName();
475
476
        // the database connection to use
477
        $connection = $this->getImportProcessor()->getConnection();
478
479
        // initialize a new handler with the passed class name
480
        $instance = new $className();
481
482
        // $instance the handler instance
483
        $instance->setConfiguration($subject);
484
        $instance->setSystemLogger($this->getSystemLogger());
485
        $instance->setRegistryProcessor($this->getRegistryProcessor());
486
487
        // instanciate and set the product processor
488
        $processorFactory = $subject->getProcessorFactory();
489
        $productProcessor = $processorFactory::factory($connection, $subject);
490
        $instance->setProductProcessor($productProcessor);
491
492
        // initialize the callbacks/visitors
493
        CallbackVisitor::get()->visit($instance);
494
        ObserverVisitor::get()->visit($instance);
495
496
        // return the subject instance
497
        return $instance;
498
    }
499
500
    /**
501
     * Lifecycle callback that will be inovked after the
502
     * import process has been finished.
503
     *
504
     * @return void
505
     */
506
    public function tearDown()
507
    {
508
        // clean up here
509
    }
510
511
    /**
512
     * This method finishes the import process and cleans the registry.
513
     *
514
     * @return void
515
     */
516
    public function finish()
517
    {
518
        $this->getRegistryProcessor()->removeAttribute($this->getSerial());
519
    }
520
}
521