Completed
Push — master ( 30c79d...a5e792 )
by Tim
9s
created

Simple::getSourceDir()   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
    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();
0 ignored issues
show
Bug introduced by
The method getDefaultStore() does not seem to exist on object<TechDivision\Impo...portProcessorInterface>.

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...
285
            $globalData[RegistryKeys::STORE_WEBSITES] = $importProcessor->getStoreWebsites();
286
            $globalData[RegistryKeys::ROOT_CATEGORIES] = $importProcessor->getRootCategories();
287
            $globalData[RegistryKeys::CORE_CONFIG_DATA] = $importProcessor->getCoreConfigData();
0 ignored issues
show
Bug introduced by
The method getCoreConfigData() does not seem to exist on object<TechDivision\Impo...portProcessorInterface>.

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