Completed
Push — master ( 38c615...0ce8a4 )
by Tim
14s
created

AbstractSubject::getSystemLoggers()   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
c 0
b 0
f 0
ccs 0
cts 4
cp 0
rs 10
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
use TechDivision\Import\Utils\ScopeKeys;
37
use TechDivision\Import\Utils\Generators\GeneratorInterface;
38
use TechDivision\Import\Utils\LoggerKeys;
39
40
/**
41
 * An abstract subject implementation.
42
 *
43
 * @author    Tim Wagner <[email protected]>
44
 * @copyright 2016 TechDivision GmbH <[email protected]>
45
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
46
 * @link      https://github.com/techdivision/import
47
 * @link      http://www.techdivision.com
48
 */
49
abstract class AbstractSubject implements SubjectInterface
50
{
51
52
    /**
53
     * The trait that provides basic filesystem handling functionality.
54
     *
55
     * @var TechDivision\Import\Subjects\FilesystemTrait
56
     */
57
    use FilesystemTrait;
58
59
    /**
60
     * The system configuration.
61
     *
62
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
63
     */
64
    protected $configuration;
65
66
    /**
67
     * The array with the system logger instances.
68
     *
69
     * @var array
70
     */
71
    protected $systemLoggers = array();
72
73
    /**
74
     * The RegistryProcessor instance to handle running threads.
75
     *
76
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
77
     */
78
    protected $registryProcessor;
79
80
    /**
81
     * The actions unique serial.
82
     *
83
     * @var string
84
     */
85
    protected $serial;
86
87
    /**
88
     * The name of the file to be imported.
89
     *
90
     * @var string
91
     */
92
    protected $filename;
93
94
    /**
95
     * Array with the subject's observers.
96
     *
97
     * @var array
98
     */
99
    protected $observers = array();
100
101
    /**
102
     * Array with the subject's callbacks.
103
     *
104
     * @var array
105
     */
106
    protected $callbacks = array();
107
108
    /**
109
     * The subject's callback mappings.
110
     *
111
     * @var array
112
     */
113
    protected $callbackMappings = array();
114
115
    /**
116
     * Contain's the column names from the header line.
117
     *
118
     * @var array
119
     */
120
    protected $headers = array();
121
122
    /**
123
     * The actual line number.
124
     *
125
     * @var integer
126
     */
127
    protected $lineNumber = 0;
128
129
    /**
130
     * The actual operation name.
131
     *
132
     * @var string
133
     */
134
    protected $operationName ;
135
136
    /**
137
     * The flag that stop's overserver execution on the actual row.
138
     *
139
     * @var boolean
140
     */
141
    protected $skipRow = false;
142
143
    /**
144
     * The available root categories.
145
     *
146
     * @var array
147
     */
148
    protected $rootCategories = array();
149
150
    /**
151
     * The Magento configuration.
152
     *
153
     * @var array
154
     */
155
    protected $coreConfigData = array();
156
157
    /**
158
     * The available stores.
159
     *
160
     * @var array
161
     */
162
    protected $stores = array();
163
164
    /**
165
     * The default store.
166
     *
167
     * @var array
168
     */
169
    protected $defaultStore;
170
171
    /**
172
     * The store view code the create the product/attributes for.
173
     *
174
     * @var string
175
     */
176
    protected $storeViewCode;
177
178
    /**
179
     * The UID generator for the core config data.
180
     *
181
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
182
     */
183
    protected $coreConfigDataUidGenerator;
184
185
    /**
186
     * Initialize the subject instance.
187
     *
188
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration              The subject configuration instance
189
     * @param \TechDivision\Import\Services\RegistryProcessorInterface         $registryProcessor          The registry processor instance
190
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface         $coreConfigDataUidGenerator The UID generator for the core config data
191
     * @param array                                                            $systemLoggers              The array with the system loggers instances
192
     */
193
    public function __construct(
194
        SubjectConfigurationInterface $configuration,
195
        RegistryProcessorInterface $registryProcessor,
196
        GeneratorInterface $coreConfigDataUidGenerator,
197
        array $systemLoggers
198
    ) {
199
        $this->configuration = $configuration;
200
        $this->registryProcessor = $registryProcessor;
201
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
202
        $this->systemLoggers = $systemLoggers;
203
    }
204
205
    /**
206
     * Return's the default callback mappings.
207
     *
208
     * @return array The default callback mappings
209
     */
210
    public function getDefaultCallbackMappings()
211
    {
212
        return array();
213
    }
214
215
    /**
216
     * Stop's observer execution on the actual row.
217
     *
218
     * @return void
219
     */
220
    public function skipRow()
221
    {
222
        $this->skipRow = true;
223
    }
224
225
    /**
226
     * Return's the actual line number.
227
     *
228
     * @return integer The line number
229
     */
230
    public function getLineNumber()
231
    {
232
        return $this->lineNumber;
233
    }
234
235
    /**
236
     * Return's the actual operation name.
237
     *
238
     * @return string
239
     */
240
    public function getOperationName()
241
    {
242
        return $this->operationName;
243
    }
244
245
    /**
246
     * Set's the array containing header row.
247
     *
248
     * @param array $headers The array with the header row
249
     *
250
     * @return void
251
     */
252
    public function setHeaders(array $headers)
253
    {
254
        $this->headers = $headers;
255
    }
256
257
    /**
258
     * Return's the array containing header row.
259
     *
260
     * @return array The array with the header row
261
     */
262
    public function getHeaders()
263
    {
264
        return $this->headers;
265
    }
266
267
    /**
268
     * Queries whether or not the header with the passed name is available.
269
     *
270
     * @param string $name The header name to query
271
     *
272
     * @return boolean TRUE if the header is available, else FALSE
273
     */
274
    public function hasHeader($name)
275
    {
276
        return isset($this->headers[$name]);
277
    }
278
279
    /**
280
     * Return's the header value for the passed name.
281
     *
282
     * @param string $name The name of the header to return the value for
283
     *
284
     * @return mixed The header value
285
     * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
286
     */
287
    public function getHeader($name)
288
    {
289
290
        // query whether or not, the header is available
291
        if (isset($this->headers[$name])) {
292
            return $this->headers[$name];
293
        }
294
295
        // throw an exception, if not
296
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
297
    }
298
299
    /**
300
     * Add's the header with the passed name and position, if not NULL.
301
     *
302
     * @param string $name The header name to add
303
     *
304
     * @return integer The new headers position
305
     */
306
    public function addHeader($name)
307
    {
308
309
        // add the header
310
        $this->headers[$name] = $position = sizeof($this->headers);
311
312
        // return the new header's position
313
        return $position;
314
    }
315
316
    /**
317
     * Queries whether or not debug mode is enabled or not, default is TRUE.
318
     *
319
     * @return boolean TRUE if debug mode is enabled, else FALSE
320
     */
321
    public function isDebugMode()
322
    {
323
        return $this->getConfiguration()->isDebugMode();
324
    }
325
326
    /**
327
     * Return's the system configuration.
328
     *
329
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The system configuration
330
     */
331
    public function getConfiguration()
332
    {
333
        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...
334
    }
335
336
    /**
337
     * Return's the logger with the passed name, by default the system logger.
338
     *
339
     * @param string $name The name of the requested system logger
340
     *
341
     * @return \Psr\Log\LoggerInterface The logger instance
342
     * @throws \Exception Is thrown, if the requested logger is NOT available
343
     */
344
    public function getSystemLogger($name = LoggerKeys::SYSTEM)
345
    {
346
347
        // query whether or not, the requested logger is available
348
        if (isset($this->systemLoggers[$name])) {
349
            return $this->systemLoggers[$name];
350
        }
351
352
        // throw an exception if the requested logger is NOT available
353
        throw new \Exception(sprintf('The requested logger \'%s\' is not available', $name));
354
    }
355
356
    /**
357
     * Return's the array with the system logger instances.
358
     *
359
     * @return array The logger instance
360
     */
361
    public function getSystemLoggers()
362
    {
363
        return $this->systemLoggers;
364
    }
365
366
    /**
367
     * Return's the RegistryProcessor instance to handle the running threads.
368
     *
369
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
370
     */
371
    public function getRegistryProcessor()
372
    {
373
        return $this->registryProcessor;
374
    }
375
376
    /**
377
     * Set's the unique serial for this import process.
378
     *
379
     * @param string $serial The unique serial
380
     *
381
     * @return void
382
     */
383
    public function setSerial($serial)
384
    {
385
        $this->serial = $serial;
386
    }
387
388
    /**
389
     * Return's the unique serial for this import process.
390
     *
391
     * @return string The unique serial
392
     */
393
    public function getSerial()
394
    {
395
        return $this->serial;
396
    }
397
398
    /**
399
     * Set's the name of the file to import
400
     *
401
     * @param string $filename The filename
402
     *
403
     * @return void
404
     */
405
    public function setFilename($filename)
406
    {
407
        $this->filename = $filename;
408
    }
409
410
    /**
411
     * Return's the name of the file to import.
412
     *
413
     * @return string The filename
414
     */
415
    public function getFilename()
416
    {
417
        return $this->filename;
418
    }
419
420
    /**
421
     * Return's the source date format to use.
422
     *
423
     * @return string The source date format
424
     */
425
    public function getSourceDateFormat()
426
    {
427
        return $this->getConfiguration()->getSourceDateFormat();
428
    }
429
430
    /**
431
     * Return's the multiple field delimiter character to use, default value is comma (,).
432
     *
433
     * @return string The multiple field delimiter character
434
     */
435
    public function getMultipleFieldDelimiter()
436
    {
437
        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...
438
    }
439
440
    /**
441
     * Return's the initialized PDO connection.
442
     *
443
     * @return \PDO The initialized PDO connection
444
     */
445
    public function getConnection()
446
    {
447
        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...
448
    }
449
450
    /**
451
     * Intializes the previously loaded global data for exactly one bunch.
452
     *
453
     * @return void
454
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
455
     */
456
    public function setUp()
457
    {
458
459
        // load the status of the actual import
460
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
461
462
        // load the global data we've prepared initially
463
        $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
464
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
465
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
466
        $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
467
468
        // initialize the operation name
469
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();
470
471
        // merge the callback mappings with the mappings from the child instance
472
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
473
474
        // merge the callback mappings the the one from the configuration file
475
        foreach ($this->getConfiguration()->getCallbacks() as $callbackMappings) {
476
            foreach ($callbackMappings as $attributeCode => $mappings) {
477
                // write a log message, that default callback configuration will
478
                // be overwritten with the one from the configuration file
479
                if (isset($this->callbackMappings[$attributeCode])) {
480
                    $this->getSystemLogger()->notice(
481
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
482
                    );
483
                }
484
485
                // override the attributes callbacks
486
                $this->callbackMappings[$attributeCode] = $mappings;
487
            }
488
        }
489
490
        // initialize the callbacks/observers
491
        CallbackVisitor::get()->visit($this);
492
        ObserverVisitor::get()->visit($this);
493
    }
494
495
    /**
496
     * Clean up the global data after importing the variants.
497
     *
498
     * @return void
499
     */
500
    public function tearDown()
501
    {
502
503
        // load the registry processor
504
        $registryProcessor = $this->getRegistryProcessor();
505
506
        // update the source directory for the next subject
507
        $registryProcessor->mergeAttributesRecursive(
508
            $this->getSerial(),
509
            array(RegistryKeys::SOURCE_DIRECTORY => $this->getNewSourceDir())
510
        );
511
512
        // log a debug message with the new source directory
513
        $this->getSystemLogger()->debug(
514
            sprintf('Subject %s successfully updated source directory to %s', __CLASS__, $this->getNewSourceDir())
515
        );
516
    }
517
518
    /**
519
     * Return's the next source directory, which will be the target directory
520
     * of this subject, in most cases.
521
     *
522
     * @return string The new source directory
523
     */
524
    protected function getNewSourceDir()
525
    {
526
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getSerial());
527
    }
528
529
    /**
530
     * Register the passed observer with the specific type.
531
     *
532
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
533
     * @param string                                           $type     The type to register the observer with
534
     *
535
     * @return void
536
     */
537
    public function registerObserver(ObserverInterface $observer, $type)
538
    {
539
540
        // query whether or not the array with the callbacks for the
541
        // passed type has already been initialized, or not
542
        if (!isset($this->observers[$type])) {
543
            $this->observers[$type] = array();
544
        }
545
546
        // append the callback with the instance of the passed type
547
        $this->observers[$type][] = $observer;
548
    }
549
550
    /**
551
     * Register the passed callback with the specific type.
552
     *
553
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
554
     * @param string                                           $type     The type to register the callback with
555
     *
556
     * @return void
557
     */
558
    public function registerCallback(CallbackInterface $callback, $type)
559
    {
560
561
        // query whether or not the array with the callbacks for the
562
        // passed type has already been initialized, or not
563
        if (!isset($this->callbacks[$type])) {
564
            $this->callbacks[$type] = array();
565
        }
566
567
        // append the callback with the instance of the passed type
568
        $this->callbacks[$type][] = $callback;
569
    }
570
571
    /**
572
     * Return's the array with callbacks for the passed type.
573
     *
574
     * @param string $type The type of the callbacks to return
575
     *
576
     * @return array The callbacks
577
     */
578
    public function getCallbacksByType($type)
579
    {
580
581
        // initialize the array for the callbacks
582
        $callbacks = array();
583
584
        // query whether or not callbacks for the type are available
585
        if (isset($this->callbacks[$type])) {
586
            $callbacks = $this->callbacks[$type];
587
        }
588
589
        // return the array with the type's callbacks
590
        return $callbacks;
591
    }
592
593
    /**
594
     * Return's the array with the available observers.
595
     *
596
     * @return array The observers
597
     */
598
    public function getObservers()
599
    {
600
        return $this->observers;
601
    }
602
603
    /**
604
     * Return's the array with the available callbacks.
605
     *
606
     * @return array The callbacks
607
     */
608
    public function getCallbacks()
609
    {
610
        return $this->callbacks;
611
    }
612
613
    /**
614
     * Return's the callback mappings for this subject.
615
     *
616
     * @return array The array with the subject's callback mappings
617
     */
618
    public function getCallbackMappings()
619
    {
620
        return $this->callbackMappings;
621
    }
622
623
    /**
624
     * Imports the content of the file with the passed filename.
625
     *
626
     * @param string $serial   The unique process serial
627
     * @param string $filename The filename to process
628
     *
629
     * @return void
630
     * @throws \Exception Is thrown, if the import can't be processed
631
     */
632
    public function import($serial, $filename)
633
    {
634
635
        try {
636
            // stop processing, if the filename doesn't match
637
            if (!$this->match($filename)) {
638
                return;
639
            }
640
641
            // load the system logger instance
642
            $systemLogger = $this->getSystemLogger();
643
644
            // prepare the flag filenames
645
            $inProgressFilename = sprintf('%s.inProgress', $filename);
646
            $importedFilename = sprintf('%s.imported', $filename);
647
            $failedFilename = sprintf('%s.failed', $filename);
648
649
            // query whether or not the file has already been imported
650
            if (is_file($failedFilename) ||
651
                is_file($importedFilename) ||
652
                is_file($inProgressFilename)
653
            ) {
654
                // log a debug message and exit
655
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
656
                return;
657
            }
658
659
            // flag file as in progress
660
            touch($inProgressFilename);
661
662
            // track the start time
663
            $startTime = microtime(true);
664
665
            // initialize serial and filename
666
            $this->setSerial($serial);
667
            $this->setFilename($filename);
668
669
            // load the system logger
670
            $systemLogger = $this->getSystemLogger();
671
672
            // initialize the global global data to import a bunch
673
            $this->setUp();
674
675
            // initialize the lexer instance itself
676
            $lexer = new Lexer($this->getLexerConfig());
677
678
            // initialize the interpreter
679
            $interpreter = new Interpreter();
680
            $interpreter->addObserver(array($this, 'importRow'));
681
682
            // query whether or not we want to use the strict mode
683
            if (!$this->getConfiguration()->isStrictMode()) {
684
                $interpreter->unstrict();
685
            }
686
687
            // log a message that the file has to be imported
688
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
689
690
            // parse the CSV file to be imported
691
            $lexer->parse($filename, $interpreter);
692
693
            // track the time needed for the import in seconds
694
            $endTime = microtime(true) - $startTime;
695
696
            // clean up the data after importing the bunch
697
            $this->tearDown();
698
699
            // log a message that the file has successfully been imported
700
            $systemLogger->debug(sprintf('Succesfully imported file %s in %f s', $filename, $endTime));
701
702
            // rename flag file, because import has been successfull
703
            rename($inProgressFilename, $importedFilename);
704
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
705
        } catch (\Exception $e) {
706
            // rename the flag file, because import failed and write the stack trace
707
            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...
708
            file_put_contents($failedFilename, $e->__toString());
709
710
            // clean up the data after importing the bunch
711
            $this->tearDown();
712
713
            // re-throw the exception
714
            throw $e;
715
        }
716
    }
717
718
    /**
719
     * This method queries whether or not the passed filename matches
720
     * the pattern, based on the subjects configured prefix.
721
     *
722
     * @param string $filename The filename to match
723
     *
724
     * @return boolean TRUE if the filename matches, else FALSE
725
     */
726
    protected function match($filename)
727
    {
728
729
        // prepare the pattern to query whether the file has to be processed or not
730
        $pattern = sprintf('/^.*\/%s.*\\.csv$/', $this->getConfiguration()->getPrefix());
731
732
        // stop processing, if the filename doesn't match
733
        return (boolean) preg_match($pattern, $filename);
734
    }
735
736
    /**
737
     * Initialize and return the lexer configuration.
738
     *
739
     * @return \Goodby\CSV\Import\Standard\LexerConfig The lexer configuration
740
     */
741
    protected function getLexerConfig()
742
    {
743
744
        // initialize the lexer configuration
745
        $config = new LexerConfig();
746
747
        // query whether or not a delimiter character has been configured
748
        if ($delimiter = $this->getConfiguration()->getDelimiter()) {
749
            $config->setDelimiter($delimiter);
750
        }
751
752
        // query whether or not a custom escape character has been configured
753
        if ($escape = $this->getConfiguration()->getEscape()) {
754
            $config->setEscape($escape);
755
        }
756
757
        // query whether or not a custom enclosure character has been configured
758
        if ($enclosure = $this->getConfiguration()->getEnclosure()) {
759
            $config->setEnclosure($enclosure);
760
        }
761
762
        // query whether or not a custom source charset has been configured
763
        if ($fromCharset = $this->getConfiguration()->getFromCharset()) {
764
            $config->setFromCharset($fromCharset);
765
        }
766
767
        // query whether or not a custom target charset has been configured
768
        if ($toCharset = $this->getConfiguration()->getToCharset()) {
769
            $config->setToCharset($toCharset);
770
        }
771
772
        // return the lexer configuratio
773
        return $config;
774
    }
775
776
    /**
777
     * Imports the passed row into the database.
778
     *
779
     * If the import failed, the exception will be catched and logged,
780
     * but the import process will be continued.
781
     *
782
     * @param array $row The row with the data to be imported
783
     *
784
     * @return void
785
     */
786
    public function importRow(array $row)
787
    {
788
789
        // raise the line number and reset the skip row flag
790
        $this->lineNumber++;
791
        $this->skipRow = false;
792
793
        // initialize the headers with the columns from the first line
794
        if (sizeof($this->headers) === 0) {
795
            foreach ($row as $value => $key) {
796
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
797
            }
798
            return;
799
        }
800
801
        // process the observers
802
        foreach ($this->getObservers() as $observers) {
803
            // invoke the pre-import/import and post-import observers
804
            foreach ($observers as $observer) {
805
                // query whether or not we have to skip the row
806
                if ($this->skipRow) {
807
                    break;
808
                }
809
                // if not, process the next observer
810
                if ($observer instanceof ObserverInterface) {
811
                    $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...
812
                }
813
            }
814
        }
815
816
        // log a debug message with the actual line nr/file information
817
        $this->getSystemLogger()->debug(
818
            sprintf(
819
                'Successfully processed row (operation: %s) in file %s on line %d',
820
                $this->operationName,
821
                $this->filename,
822
                $this->lineNumber
823
            )
824
        );
825
    }
826
827
    /**
828
     * Map the passed attribute code, if a header mapping exists and return the
829
     * mapped mapping.
830
     *
831
     * @param string $attributeCode The attribute code to map
832
     *
833
     * @return string The mapped attribute code, or the original one
834
     */
835
    public function mapAttributeCodeByHeaderMapping($attributeCode)
836
    {
837
838
        // load the header mappings
839
        $headerMappings = $this->getHeaderMappings();
840
841
        // query weather or not we've a mapping, if yes, map the attribute code
842
        if (isset($headerMappings[$attributeCode])) {
843
            $attributeCode = $headerMappings[$attributeCode];
844
        }
845
846
        // return the (mapped) attribute code
847
        return $attributeCode;
848
    }
849
850
    /**
851
     * Queries whether or not that the subject needs an OK file to be processed.
852
     *
853
     * @return boolean TRUE if the subject needs an OK file, else FALSE
854
     */
855
    public function isOkFileNeeded()
856
    {
857
        return $this->getConfiguration()->isOkFileNeeded();
858
    }
859
860
    /**
861
     * Return's the default store.
862
     *
863
     * @return array The default store
864
     */
865
    public function getDefaultStore()
866
    {
867
        return $this->defaultStore;
868
    }
869
870
    /**
871
     * Set's the store view code the create the product/attributes for.
872
     *
873
     * @param string $storeViewCode The store view code
874
     *
875
     * @return void
876
     */
877
    public function setStoreViewCode($storeViewCode)
878
    {
879
        $this->storeViewCode = $storeViewCode;
880
    }
881
882
    /**
883
     * Return's the store view code the create the product/attributes for.
884
     *
885
     * @param string|null $default The default value to return, if the store view code has not been set
886
     *
887
     * @return string The store view code
888
     */
889
    public function getStoreViewCode($default = null)
890
    {
891
892
        // return the store view code, if available
893
        if ($this->storeViewCode != null) {
894
            return $this->storeViewCode;
895
        }
896
897
        // if NOT and a default code is available
898
        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...
899
            // return the default value
900
            return $default;
901
        }
902
    }
903
904
    /**
905
     * Return's the root category for the actual view store.
906
     *
907
     * @return array The store's root category
908
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
909
     */
910
    public function getRootCategory()
911
    {
912
913
        // load the default store
914
        $defaultStore = $this->getDefaultStore();
915
916
        // load the actual store view code
917
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
918
919
        // query weather or not we've a root category or not
920
        if (isset($this->rootCategories[$storeViewCode])) {
921
            return $this->rootCategories[$storeViewCode];
922
        }
923
924
        // throw an exception if the root category is NOT available
925
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
926
    }
927
928
    /**
929
     * Return's the Magento configuration value.
930
     *
931
     * @param string  $path    The Magento path of the requested configuration value
932
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
933
     * @param string  $scope   The scope the configuration value has been set
934
     * @param integer $scopeId The scope ID the configuration value has been set
935
     *
936
     * @return mixed The configuration value
937
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
938
     */
939
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
940
    {
941
942
        // initialize the core config data
943
        $coreConfigData = array(
944
            MemberNames::PATH => $path,
945
            MemberNames::SCOPE => $scope,
946
            MemberNames::SCOPE_ID => $scopeId
947
        );
948
949
        // generate the UID from the passed data
950
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
951
952
        // iterate over the core config data and try to find the requested configuration value
953
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
954
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
955
        }
956
957
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
958
        if ($scope === ScopeKeys::SCOPE_STORES && isset($this->stores[$scopeId])) {
959
            // replace scope with 'websites' and website ID
960
            $coreConfigData = array_merge(
961
                $coreConfigData,
962
                array(
963
                    MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
964
                    MemberNames::SCOPE_ID => $this->stores[$scopeId][MemberNames::WEBSITE_ID]
965
                )
966
            );
967
968
            // generate the UID from the passed data, merged with the 'websites' scope and ID
969
            $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
970
971
            // query whether or not, the configuration value on 'websites' level
972 View Code Duplication
            if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
973
                return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
974
            }
975
        }
976
977
        // replace scope with 'default' and scope ID '0'
978
        $coreConfigData = array_merge(
979
            $coreConfigData,
980
            array(
981
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
982
                MemberNames::SCOPE_ID => 0
983
            )
984
        );
985
986
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
987
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
988
989
        // query whether or not, the configuration value on 'default' level
990 View Code Duplication
        if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
991
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
992
        }
993
994
        // if not, return the passed default value
995
        if ($default !== null) {
996
            return $default;
997
        }
998
999
        // throw an exception if no value can be found
1000
        // in the Magento configuration
1001
        throw new \Exception(
1002
            sprintf(
1003
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1004
                $path,
1005
                $scope,
1006
                $scopeId
1007
            )
1008
        );
1009
    }
1010
}
1011