Completed
Pull Request — master (#38)
by Tim
10:16
created

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

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...
864
                }
865
            }
866
        }
867
868
        // log a debug message with the actual line nr/file information
869
        $this->getSystemLogger()->debug(
870
            sprintf(
871
                'Successfully processed row (operation: %s) in file %s on line %d',
872
                $this->operationName,
873
                $this->filename,
874
                $this->lineNumber
875
            )
876
        );
877
    }
878
879
    /**
880
     * Map the passed attribute code, if a header mapping exists and return the
881
     * mapped mapping.
882
     *
883
     * @param string $attributeCode The attribute code to map
884
     *
885
     * @return string The mapped attribute code, or the original one
886
     */
887
    public function mapAttributeCodeByHeaderMapping($attributeCode)
888
    {
889
890
        // query weather or not we've a mapping, if yes, map the attribute code
891
        if (isset($this->headerMappings[$attributeCode])) {
892
            $attributeCode = $this->headerMappings[$attributeCode];
893
        }
894
895
        // return the (mapped) attribute code
896
        return $attributeCode;
897
    }
898
899
    /**
900
     * Queries whether or not that the subject needs an OK file to be processed.
901
     *
902
     * @return boolean TRUE if the subject needs an OK file, else FALSE
903
     */
904
    public function isOkFileNeeded()
905
    {
906
        return $this->getConfiguration()->isOkFileNeeded();
907
    }
908
909
    /**
910
     * Return's the entity type code to be used.
911
     *
912
     * @return string The entity type code to be used
913
     */
914
    public function getEntityTypeCode()
915
    {
916
        return $this->getConfiguration()->getConfiguration()->getEntityTypeCode();
917
    }
918
919
    /**
920
     * Set's the attribute set of the product that has to be created.
921
     *
922
     * @param array $attributeSet The attribute set
923
     *
924
     * @return void
925
     */
926
    public function setAttributeSet(array $attributeSet)
927
    {
928
        $this->attributeSet = $attributeSet;
929
    }
930
931
    /**
932
     * Return's the attribute set of the product that has to be created.
933
     *
934
     * @return array The attribute set
935
     */
936
    public function getAttributeSet()
937
    {
938
        return $this->attributeSet;
939
    }
940
941
    /**
942
     * Return's the attribute set with the passed attribute set name.
943
     *
944
     * @param string $attributeSetName The name of the requested attribute set
945
     *
946
     * @return array The attribute set data
947
     * @throws \Exception Is thrown, if the attribute set with the passed name is not available
948
     */
949 View Code Duplication
    public function getAttributeSetByAttributeSetName($attributeSetName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
950
    {
951
952
        // query whether or not attribute sets for the actualy entity type code are available
953
        if (isset($this->attributeSets[$entityTypeCode = $this->getEntityTypeCode()])) {
954
            // load the attribute sets for the actualy entity type code
955
            $attributSets = $this->attributeSets[$entityTypeCode];
956
957
            // query whether or not, the requested attribute set is available
958
            if (isset($attributSets[$attributeSetName])) {
959
                return $attributSets[$attributeSetName];
960
            }
961
        }
962
963
        // throw an exception, if not
964
        throw new \Exception(
965
            sprintf(
966
                'Found invalid attribute set name %s in file %s on line %d',
967
                $attributeSetName,
968
                $this->getFilename(),
969
                $this->getLineNumber()
970
            )
971
        );
972
    }
973
974
    /**
975
     * Return's the attributes for the attribute set of the product that has to be created.
976
     *
977
     * @return array The attributes
978
     * @throws \Exception Is thrown if the attributes for the actual attribute set are not available
979
     */
980 View Code Duplication
    public function getAttributes()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
981
    {
982
983
        // query whether or not, the requested EAV attributes are available
984
        if (isset($this->attributes[$entityTypeCode = $this->getEntityTypeCode()])) {
985
            // load the attributes for the entity type code
986
            $attributes = $this->attributes[$entityTypeCode];
987
988
            // query whether or not attributes for the actual attribute set name
989
            if (isset($attributes[$attributeSetName = $this->attributeSet[MemberNames::ATTRIBUTE_SET_NAME]])) {
990
                return $attributes[$attributeSetName];
991
            }
992
        }
993
994
        // throw an exception, if not
995
        throw new \Exception(
996
            sprintf(
997
                'Found invalid attribute set name "%s" in file %s on line %d',
998
                $attributeSetName,
999
                $this->getFilename(),
1000
                $this->getLineNumber()
1001
            )
1002
        );
1003
    }
1004
1005
    /**
1006
     * Return's the EAV attribute with the passed attribute code.
1007
     *
1008
     * @param string $attributeCode The attribute code
1009
     *
1010
     * @return array The array with the EAV attribute
1011
     * @throws \Exception Is thrown if the attribute with the passed code is not available
1012
     */
1013
    public function getEavAttributeByAttributeCode($attributeCode)
1014
    {
1015
1016
        // load the attributes
1017
        $attributes = $this->getAttributes();
1018
1019
        // query whether or not the attribute exists
1020
        if (isset($attributes[$attributeCode])) {
1021
            return $attributes[$attributeCode];
1022
        }
1023
1024
        // throw an exception if the requested attribute is not available
1025
        throw new \Exception(
1026
            sprintf(
1027
                'Can\'t load attribute with code "%s" in file %s and line %d',
1028
                $attributeCode,
1029
                $this->getFilename(),
1030
                $this->getLineNumber()
1031
            )
1032
        );
1033
    }
1034
1035
    /**
1036
     * Return's the default store.
1037
     *
1038
     * @return array The default store
1039
     */
1040
    public function getDefaultStore()
1041
    {
1042
        return $this->defaultStore;
1043
    }
1044
1045
    /**
1046
     * Set's the store view code the create the product/attributes for.
1047
     *
1048
     * @param string $storeViewCode The store view code
1049
     *
1050
     * @return void
1051
     */
1052
    public function setStoreViewCode($storeViewCode)
1053
    {
1054
        $this->storeViewCode = $storeViewCode;
1055
    }
1056
1057
    /**
1058
     * Return's the store view code the create the product/attributes for.
1059
     *
1060
     * @param string|null $default The default value to return, if the store view code has not been set
1061
     *
1062
     * @return string The store view code
1063
     */
1064
    public function getStoreViewCode($default = null)
1065
    {
1066
1067
        // return the store view code, if available
1068
        if ($this->storeViewCode != null) {
1069
            return $this->storeViewCode;
1070
        }
1071
1072
        // if NOT and a default code is available
1073
        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...
1074
            // return the default value
1075
            return $default;
1076
        }
1077
    }
1078
1079
    /**
1080
     * Return's the root category for the actual view store.
1081
     *
1082
     * @return array The store's root category
1083
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
1084
     */
1085
    public function getRootCategory()
1086
    {
1087
1088
        // load the default store
1089
        $defaultStore = $this->getDefaultStore();
1090
1091
        // load the actual store view code
1092
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
1093
1094
        // query weather or not we've a root category or not
1095
        if (isset($this->rootCategories[$storeViewCode])) {
1096
            return $this->rootCategories[$storeViewCode];
1097
        }
1098
1099
        // throw an exception if the root category is NOT available
1100
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1101
    }
1102
}
1103