Completed
Push — master ( d5f4e5...716f0e )
by Tim
12s
created

AbstractSubject::getDefaultCallbackMappings()   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 4
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\Subjects\AbstractSubject
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
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Subjects;
22
23
use Psr\Log\LoggerInterface;
24
use Goodby\CSV\Import\Standard\Lexer;
25
use Goodby\CSV\Import\Standard\LexerConfig;
26
use Goodby\CSV\Import\Standard\Interpreter;
27
use TechDivision\Import\Utils\MemberNames;
28
use TechDivision\Import\Utils\RegistryKeys;
29
use TechDivision\Import\Services\RegistryProcessor;
30
use TechDivision\Import\Callbacks\CallbackVisitor;
31
use TechDivision\Import\Callbacks\CallbackInterface;
32
use TechDivision\Import\Observers\ObserverVisitor;
33
use TechDivision\Import\Observers\ObserverInterface;
34
use TechDivision\Import\Services\RegistryProcessorInterface;
35
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
36
37
/**
38
 * An abstract subject implementation.
39
 *
40
 * @author    Tim Wagner <[email protected]>
41
 * @copyright 2016 TechDivision GmbH <[email protected]>
42
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
43
 * @link      https://github.com/techdivision/import
44
 * @link      http://www.techdivision.com
45
 */
46
abstract class AbstractSubject implements SubjectInterface
47
{
48
49
    /**
50
     * The trait that provides basic filesystem handling functionality.
51
     *
52
     * @var TechDivision\Import\Subjects\FilesystemTrait
53
     */
54
    use FilesystemTrait;
55
56
    /**
57
     * The system configuration.
58
     *
59
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
60
     */
61
    protected $configuration;
62
63
    /**
64
     * The system logger implementation.
65
     *
66
     * @var \Psr\Log\LoggerInterface
67
     */
68
    protected $systemLogger;
69
70
    /**
71
     * The RegistryProcessor instance to handle running threads.
72
     *
73
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
74
     */
75
    protected $registryProcessor;
76
77
    /**
78
     * The actions unique serial.
79
     *
80
     * @var string
81
     */
82
    protected $serial;
83
84
    /**
85
     * The name of the file to be imported.
86
     *
87
     * @var string
88
     */
89
    protected $filename;
90
91
    /**
92
     * Array with the subject's observers.
93
     *
94
     * @var array
95
     */
96
    protected $observers = array();
97
98
    /**
99
     * Array with the subject's callbacks.
100
     *
101
     * @var array
102
     */
103
    protected $callbacks = array();
104
105
    /**
106
     * The subject's callback mappings.
107
     *
108
     * @var array
109
     */
110
    protected $callbackMappings = array();
111
112
    /**
113
     * Contain's the column names from the header line.
114
     *
115
     * @var array
116
     */
117
    protected $headers = array();
118
119
    /**
120
     * The actual line number.
121
     *
122
     * @var integer
123
     */
124
    protected $lineNumber = 0;
125
126
    /**
127
     * The actual operation name.
128
     *
129
     * @var string
130
     */
131
    protected $operationName ;
132
133
    /**
134
     * The flag that stop's overserver execution on the actual row.
135
     *
136
     * @var boolean
137
     */
138
    protected $skipRow = false;
139
140
    /**
141
     * The available root categories.
142
     *
143
     * @var array
144
     */
145
    protected $rootCategories = array();
146
147
    /**
148
     * The default store.
149
     *
150
     * @var array
151
     */
152
    protected $defaultStore;
153
154
    /**
155
     * The store view code the create the product/attributes for.
156
     *
157
     * @var string
158
     */
159
    protected $storeViewCode;
160
161
    /**
162
     * Initialize the subject instance.
163
     *
164
     * @param \Psr\Log\LoggerInterface                                         $systemLogger      The system logger instance
165
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration     The subject configuration instance
166
     * @param \TechDivision\Import\Services\RegistryProcessorInterface         $registryProcessor The registry processor instance
167
     */
168
    public function __construct(
169
        LoggerInterface $systemLogger,
170
        SubjectConfigurationInterface $configuration,
171
        RegistryProcessorInterface $registryProcessor
172
    ) {
173
        $this->systemLogger = $systemLogger;
174
        $this->configuration = $configuration;
175
        $this->registryProcessor = $registryProcessor;
176
    }
177
178
    /**
179
     * Return's the default callback mappings.
180
     *
181
     * @return array The default callback mappings
182
     */
183
    public function getDefaultCallbackMappings()
184
    {
185
        return array();
186
    }
187
188
    /**
189
     * Stop's observer execution on the actual row.
190
     *
191
     * @return void
192
     */
193
    public function skipRow()
194
    {
195
        $this->skipRow = true;
196
    }
197
198
    /**
199
     * Return's the actual line number.
200
     *
201
     * @return integer The line number
202
     */
203
    public function getLineNumber()
204
    {
205
        return $this->lineNumber;
206
    }
207
208
    /**
209
     * Return's the actual operation name.
210
     *
211
     * @return string
212
     */
213
    public function getOperationName()
214
    {
215
        return $this->operationName;
216
    }
217
218
    /**
219
     * Set's the array containing header row.
220
     *
221
     * @param array $headers The array with the header row
222
     *
223
     * @return void
224
     */
225
    public function setHeaders(array $headers)
226
    {
227
        $this->headers = $headers;
228
    }
229
230
    /**
231
     * Return's the array containing header row.
232
     *
233
     * @return array The array with the header row
234
     */
235
    public function getHeaders()
236
    {
237
        return $this->headers;
238
    }
239
240
    /**
241
     * Queries whether or not the header with the passed name is available.
242
     *
243
     * @param string $name The header name to query
244
     *
245
     * @return boolean TRUE if the header is available, else FALSE
246
     */
247
    public function hasHeader($name)
248
    {
249
        return isset($this->headers[$name]);
250
    }
251
252
    /**
253
     * Return's the header value for the passed name.
254
     *
255
     * @param string $name The name of the header to return the value for
256
     *
257
     * @return mixed The header value
258
     * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
259
     */
260
    public function getHeader($name)
261
    {
262
263
        // query whether or not, the header is available
264
        if (isset($this->headers[$name])) {
265
            return $this->headers[$name];
266
        }
267
268
        // throw an exception, if not
269
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
270
    }
271
272
    /**
273
     * Add's the header with the passed name and position, if not NULL.
274
     *
275
     * @param string $name The header name to add
276
     *
277
     * @return integer The new headers position
278
     */
279
    public function addHeader($name)
280
    {
281
282
        // add the header
283
        $this->headers[$name] = $position = sizeof($this->headers);
284
285
        // return the new header's position
286
        return $position;
287
    }
288
289
    /**
290
     * Queries whether or not debug mode is enabled or not, default is TRUE.
291
     *
292
     * @return boolean TRUE if debug mode is enabled, else FALSE
293
     */
294
    public function isDebugMode()
295
    {
296
        return $this->getConfiguration()->isDebugMode();
297
    }
298
299
    /**
300
     * Return's the system configuration.
301
     *
302
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The system configuration
303
     */
304
    public function getConfiguration()
305
    {
306
        return $this->configuration;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->configuration; (TechDivision\Import\Conf...tConfigurationInterface) is incompatible with the return type declared by the interface TechDivision\Import\Subj...rface::getConfiguration of type TechDivision\Import\Configuration\SubjectInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
307
    }
308
309
    /**
310
     * Return's the system logger.
311
     *
312
     * @return \Psr\Log\LoggerInterface The system logger instance
313
     */
314
    public function getSystemLogger()
315
    {
316
        return $this->systemLogger;
317
    }
318
319
    /**
320
     * Return's the RegistryProcessor instance to handle the running threads.
321
     *
322
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
323
     */
324
    public function getRegistryProcessor()
325
    {
326
        return $this->registryProcessor;
327
    }
328
329
    /**
330
     * Set's the unique serial for this import process.
331
     *
332
     * @param string $serial The unique serial
333
     *
334
     * @return void
335
     */
336
    public function setSerial($serial)
337
    {
338
        $this->serial = $serial;
339
    }
340
341
    /**
342
     * Return's the unique serial for this import process.
343
     *
344
     * @return string The unique serial
345
     */
346
    public function getSerial()
347
    {
348
        return $this->serial;
349
    }
350
351
    /**
352
     * Set's the name of the file to import
353
     *
354
     * @param string $filename The filename
355
     *
356
     * @return void
357
     */
358
    public function setFilename($filename)
359
    {
360
        $this->filename = $filename;
361
    }
362
363
    /**
364
     * Return's the name of the file to import.
365
     *
366
     * @return string The filename
367
     */
368
    public function getFilename()
369
    {
370
        return $this->filename;
371
    }
372
373
    /**
374
     * Return's the source date format to use.
375
     *
376
     * @return string The source date format
377
     */
378
    public function getSourceDateFormat()
379
    {
380
        return $this->getConfiguration()->getSourceDateFormat();
381
    }
382
383
    /**
384
     * Return's the multiple field delimiter character to use, default value is comma (,).
385
     *
386
     * @return string The multiple field delimiter character
387
     */
388
    public function getMultipleFieldDelimiter()
389
    {
390
        return $this->getConfiguration()->getMultipleFieldDelimiter();
0 ignored issues
show
Bug introduced by
The method getMultipleFieldDelimiter() 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...
391
    }
392
393
    /**
394
     * Return's the initialized PDO connection.
395
     *
396
     * @return \PDO The initialized PDO connection
397
     */
398
    public function getConnection()
399
    {
400
        return $this->getProductProcessor()->getConnection();
0 ignored issues
show
Bug introduced by
The method getProductProcessor() does not seem to exist on object<TechDivision\Impo...bjects\AbstractSubject>.

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...
401
    }
402
403
    /**
404
     * Intializes the previously loaded global data for exactly one bunch.
405
     *
406
     * @return void
407
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
408
     */
409
    public function setUp()
410
    {
411
412
        // load the status of the actual import
413
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
414
415
        // load the global data we've prepared initially
416
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
417
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
418
419
        // initialize the operation name
420
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();
421
422
        // merge the callback mappings with the mappings from the child instance
423
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
424
425
        // merge the callback mappings the the one from the configuration file
426
        foreach ($this->getConfiguration()->getCallbacks() as $callbackMappings) {
427
            foreach ($callbackMappings as $attributeCode => $mappings) {
428
                // write a log message, that default callback configuration will
429
                // be overwritten with the one from the configuration file
430
                if (isset($this->callbackMappings[$attributeCode])) {
431
                    $this->getSystemLogger()->notice(
432
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
433
                    );
434
                }
435
436
                // override the attributes callbacks
437
                $this->callbackMappings[$attributeCode] = $mappings;
438
            }
439
        }
440
441
        // initialize the callbacks/observers
442
        CallbackVisitor::get()->visit($this);
443
        ObserverVisitor::get()->visit($this);
444
    }
445
446
    /**
447
     * Clean up the global data after importing the variants.
448
     *
449
     * @return void
450
     */
451
    public function tearDown()
452
    {
453
454
        // load the registry processor
455
        $registryProcessor = $this->getRegistryProcessor();
456
457
        // update the source directory for the next subject
458
        $registryProcessor->mergeAttributesRecursive(
459
            $this->getSerial(),
460
            array(RegistryKeys::SOURCE_DIRECTORY => $this->getNewSourceDir())
461
        );
462
463
        // log a debug message with the new source directory
464
        $this->getSystemLogger()->debug(
465
            sprintf('Subject %s successfully updated source directory to %s', __CLASS__, $this->getNewSourceDir())
466
        );
467
    }
468
469
    /**
470
     * Return's the next source directory, which will be the target directory
471
     * of this subject, in most cases.
472
     *
473
     * @return string The new source directory
474
     */
475
    protected function getNewSourceDir()
476
    {
477
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getSerial());
478
    }
479
480
    /**
481
     * Register the passed observer with the specific type.
482
     *
483
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
484
     * @param string                                           $type     The type to register the observer with
485
     *
486
     * @return void
487
     */
488
    public function registerObserver(ObserverInterface $observer, $type)
489
    {
490
491
        // query whether or not the array with the callbacks for the
492
        // passed type has already been initialized, or not
493
        if (!isset($this->observers[$type])) {
494
            $this->observers[$type] = array();
495
        }
496
497
        // append the callback with the instance of the passed type
498
        $this->observers[$type][] = $observer;
499
    }
500
501
    /**
502
     * Register the passed callback with the specific type.
503
     *
504
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
505
     * @param string                                           $type     The type to register the callback with
506
     *
507
     * @return void
508
     */
509
    public function registerCallback(CallbackInterface $callback, $type)
510
    {
511
512
        // query whether or not the array with the callbacks for the
513
        // passed type has already been initialized, or not
514
        if (!isset($this->callbacks[$type])) {
515
            $this->callbacks[$type] = array();
516
        }
517
518
        // append the callback with the instance of the passed type
519
        $this->callbacks[$type][] = $callback;
520
    }
521
522
    /**
523
     * Return's the array with callbacks for the passed type.
524
     *
525
     * @param string $type The type of the callbacks to return
526
     *
527
     * @return array The callbacks
528
     */
529
    public function getCallbacksByType($type)
530
    {
531
532
        // initialize the array for the callbacks
533
        $callbacks = array();
534
535
        // query whether or not callbacks for the type are available
536
        if (isset($this->callbacks[$type])) {
537
            $callbacks = $this->callbacks[$type];
538
        }
539
540
        // return the array with the type's callbacks
541
        return $callbacks;
542
    }
543
544
    /**
545
     * Return's the array with the available observers.
546
     *
547
     * @return array The observers
548
     */
549
    public function getObservers()
550
    {
551
        return $this->observers;
552
    }
553
554
    /**
555
     * Return's the array with the available callbacks.
556
     *
557
     * @return array The callbacks
558
     */
559
    public function getCallbacks()
560
    {
561
        return $this->callbacks;
562
    }
563
564
    /**
565
     * Return's the callback mappings for this subject.
566
     *
567
     * @return array The array with the subject's callback mappings
568
     */
569
    public function getCallbackMappings()
570
    {
571
        return $this->callbackMappings;
572
    }
573
574
    /**
575
     * Imports the content of the file with the passed filename.
576
     *
577
     * @param string $serial   The unique process serial
578
     * @param string $filename The filename to process
579
     *
580
     * @return void
581
     * @throws \Exception Is thrown, if the import can't be processed
582
     */
583
    public function import($serial, $filename)
584
    {
585
586
        try {
587
            // stop processing, if the filename doesn't match
588
            if (!$this->match($filename)) {
589
                return;
590
            }
591
592
            // load the system logger instance
593
            $systemLogger = $this->getSystemLogger();
594
595
            // prepare the flag filenames
596
            $inProgressFilename = sprintf('%s.inProgress', $filename);
597
            $importedFilename = sprintf('%s.imported', $filename);
598
            $failedFilename = sprintf('%s.failed', $filename);
599
600
            // query whether or not the file has already been imported
601
            if (is_file($failedFilename) ||
602
                is_file($importedFilename) ||
603
                is_file($inProgressFilename)
604
            ) {
605
                // log a debug message and exit
606
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
607
                return;
608
            }
609
610
            // flag file as in progress
611
            touch($inProgressFilename);
612
613
            // track the start time
614
            $startTime = microtime(true);
615
616
            // initialize serial and filename
617
            $this->setSerial($serial);
618
            $this->setFilename($filename);
619
620
            // load the system logger
621
            $systemLogger = $this->getSystemLogger();
622
623
            // initialize the global global data to import a bunch
624
            $this->setUp();
625
626
            // initialize the lexer instance itself
627
            $lexer = new Lexer($this->getLexerConfig());
628
629
            // initialize the interpreter
630
            $interpreter = new Interpreter();
631
            $interpreter->addObserver(array($this, 'importRow'));
632
633
            // query whether or not we want to use the strict mode
634
            if (!$this->getConfiguration()->isStrictMode()) {
635
                $interpreter->unstrict();
636
            }
637
638
            // log a message that the file has to be imported
639
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
640
641
            // parse the CSV file to be imported
642
            $lexer->parse($filename, $interpreter);
643
644
            // track the time needed for the import in seconds
645
            $endTime = microtime(true) - $startTime;
646
647
            // clean up the data after importing the bunch
648
            $this->tearDown();
649
650
            // log a message that the file has successfully been imported
651
            $systemLogger->debug(sprintf('Succesfully imported file %s in %f s', $filename, $endTime));
652
653
            // rename flag file, because import has been successfull
654
            rename($inProgressFilename, $importedFilename);
655
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
656
        } catch (\Exception $e) {
657
            // rename the flag file, because import failed and write the stack trace
658
            rename($inProgressFilename, $failedFilename);
0 ignored issues
show
Bug introduced by
The variable $inProgressFilename 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...
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...
659
            file_put_contents($failedFilename, $e->__toString());
660
661
            // clean up the data after importing the bunch
662
            $this->tearDown();
663
664
            // re-throw the exception
665
            throw $e;
666
        }
667
    }
668
669
    /**
670
     * This method queries whether or not the passed filename matches
671
     * the pattern, based on the subjects configured prefix.
672
     *
673
     * @param string $filename The filename to match
674
     *
675
     * @return boolean TRUE if the filename matches, else FALSE
676
     */
677
    protected function match($filename)
678
    {
679
680
        // prepare the pattern to query whether the file has to be processed or not
681
        $pattern = sprintf('/^.*\/%s.*\\.csv$/', $this->getConfiguration()->getPrefix());
682
683
        // stop processing, if the filename doesn't match
684
        return (boolean) preg_match($pattern, $filename);
685
    }
686
687
    /**
688
     * Initialize and return the lexer configuration.
689
     *
690
     * @return \Goodby\CSV\Import\Standard\LexerConfig The lexer configuration
691
     */
692
    protected function getLexerConfig()
693
    {
694
695
        // initialize the lexer configuration
696
        $config = new LexerConfig();
697
698
        // query whether or not a delimiter character has been configured
699
        if ($delimiter = $this->getConfiguration()->getDelimiter()) {
700
            $config->setDelimiter($delimiter);
701
        }
702
703
        // query whether or not a custom escape character has been configured
704
        if ($escape = $this->getConfiguration()->getEscape()) {
705
            $config->setEscape($escape);
706
        }
707
708
        // query whether or not a custom enclosure character has been configured
709
        if ($enclosure = $this->getConfiguration()->getEnclosure()) {
710
            $config->setEnclosure($enclosure);
711
        }
712
713
        // query whether or not a custom source charset has been configured
714
        if ($fromCharset = $this->getConfiguration()->getFromCharset()) {
715
            $config->setFromCharset($fromCharset);
716
        }
717
718
        // query whether or not a custom target charset has been configured
719
        if ($toCharset = $this->getConfiguration()->getToCharset()) {
720
            $config->setToCharset($toCharset);
721
        }
722
723
        // return the lexer configuratio
724
        return $config;
725
    }
726
727
    /**
728
     * Imports the passed row into the database.
729
     *
730
     * If the import failed, the exception will be catched and logged,
731
     * but the import process will be continued.
732
     *
733
     * @param array $row The row with the data to be imported
734
     *
735
     * @return void
736
     */
737
    public function importRow(array $row)
738
    {
739
740
        // raise the line number and reset the skip row flag
741
        $this->lineNumber++;
742
        $this->skipRow = false;
743
744
        // initialize the headers with the columns from the first line
745
        if (sizeof($this->headers) === 0) {
746
            foreach ($row as $value => $key) {
747
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
748
            }
749
            return;
750
        }
751
752
        // process the observers
753
        foreach ($this->getObservers() as $observers) {
754
            // invoke the pre-import/import and post-import observers
755
            foreach ($observers as $observer) {
756
                // query whether or not we have to skip the row
757
                if ($this->skipRow) {
758
                    break;
759
                }
760
                // if not, process the next observer
761
                if ($observer instanceof ObserverInterface) {
762
                    $row = $observer->handle($row);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Observers\ObserverInterface as the method handle() does only exist in the following implementations of said interface: TechDivision\Import\Obse...stractAttributeObserver, TechDivision\Import\Obse...tractFileUploadObserver, TechDivision\Import\Obse...tionalAttributeObserver, TechDivision\Import\Observers\AttributeSetObserver.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
763
                }
764
            }
765
        }
766
767
        // log a debug message with the actual line nr/file information
768
        $this->getSystemLogger()->debug(
769
            sprintf(
770
                'Successfully processed row (operation: %s) in file %s on line %d',
771
                $this->operationName,
772
                $this->filename,
773
                $this->lineNumber
774
            )
775
        );
776
    }
777
778
    /**
779
     * Map the passed attribute code, if a header mapping exists and return the
780
     * mapped mapping.
781
     *
782
     * @param string $attributeCode The attribute code to map
783
     *
784
     * @return string The mapped attribute code, or the original one
785
     */
786
    public function mapAttributeCodeByHeaderMapping($attributeCode)
787
    {
788
789
        // load the header mappings
790
        $headerMappings = $this->getHeaderMappings();
791
792
        // query weather or not we've a mapping, if yes, map the attribute code
793
        if (isset($headerMappings[$attributeCode])) {
794
            $attributeCode = $headerMappings[$attributeCode];
795
        }
796
797
        // return the (mapped) attribute code
798
        return $attributeCode;
799
    }
800
801
    /**
802
     * Queries whether or not that the subject needs an OK file to be processed.
803
     *
804
     * @return boolean TRUE if the subject needs an OK file, else FALSE
805
     */
806
    public function isOkFileNeeded()
807
    {
808
        return $this->getConfiguration()->isOkFileNeeded();
809
    }
810
811
    /**
812
     * Return's the default store.
813
     *
814
     * @return array The default store
815
     */
816
    public function getDefaultStore()
817
    {
818
        return $this->defaultStore;
819
    }
820
821
    /**
822
     * Set's the store view code the create the product/attributes for.
823
     *
824
     * @param string $storeViewCode The store view code
825
     *
826
     * @return void
827
     */
828
    public function setStoreViewCode($storeViewCode)
829
    {
830
        $this->storeViewCode = $storeViewCode;
831
    }
832
833
    /**
834
     * Return's the store view code the create the product/attributes for.
835
     *
836
     * @param string|null $default The default value to return, if the store view code has not been set
837
     *
838
     * @return string The store view code
839
     */
840
    public function getStoreViewCode($default = null)
841
    {
842
843
        // return the store view code, if available
844
        if ($this->storeViewCode != null) {
845
            return $this->storeViewCode;
846
        }
847
848
        // if NOT and a default code is available
849
        if ($default != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $default of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
850
            // return the default value
851
            return $default;
852
        }
853
    }
854
855
    /**
856
     * Return's the root category for the actual view store.
857
     *
858
     * @return array The store's root category
859
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
860
     */
861
    public function getRootCategory()
862
    {
863
864
        // load the default store
865
        $defaultStore = $this->getDefaultStore();
866
867
        // load the actual store view code
868
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
869
870
        // query weather or not we've a root category or not
871
        if (isset($this->rootCategories[$storeViewCode])) {
872
            return $this->rootCategories[$storeViewCode];
873
        }
874
875
        // throw an exception if the root category is NOT available
876
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
877
    }
878
}
879