Completed
Pull Request — master (#37)
by Tim
03:54
created

AbstractSubject::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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...
841
                }
842
            }
843
        }
844
845
        // log a debug message with the actual line nr/file information
846
        $this->getSystemLogger()->debug(
847
            sprintf(
848
                'Successfully processed row (operation: %s) in file %s on line %d',
849
                $this->operationName,
850
                $this->filename,
851
                $this->lineNumber
852
            )
853
        );
854
    }
855
856
    /**
857
     * Map the passed attribute code, if a header mapping exists and return the
858
     * mapped mapping.
859
     *
860
     * @param string $attributeCode The attribute code to map
861
     *
862
     * @return string The mapped attribute code, or the original one
863
     */
864
    public function mapAttributeCodeByHeaderMapping($attributeCode)
865
    {
866
867
        // query weather or not we've a mapping, if yes, map the attribute code
868
        if (isset($this->headerMappings[$attributeCode])) {
869
            $attributeCode = $this->headerMappings[$attributeCode];
870
        }
871
872
        // return the (mapped) attribute code
873
        return $attributeCode;
874
    }
875
876
    /**
877
     * Queries whether or not that the subject needs an OK file to be processed.
878
     *
879
     * @return boolean TRUE if the subject needs an OK file, else FALSE
880
     */
881
    public function isOkFileNeeded()
882
    {
883
        return $this->getConfiguration()->isOkFileNeeded();
884
    }
885
886
    /**
887
     * Return's the entity type code to be used.
888
     *
889
     * @return string The entity type code to be used
890
     */
891
    public function getEntityTypeCode()
892
    {
893
        return $this->getConfiguration()->getConfiguration()->getEntityTypeCode();
894
    }
895
896
    /**
897
     * Set's the attribute set of the product that has to be created.
898
     *
899
     * @param array $attributeSet The attribute set
900
     *
901
     * @return void
902
     */
903
    public function setAttributeSet(array $attributeSet)
904
    {
905
        $this->attributeSet = $attributeSet;
906
    }
907
908
    /**
909
     * Return's the attribute set of the product that has to be created.
910
     *
911
     * @return array The attribute set
912
     */
913
    public function getAttributeSet()
914
    {
915
        return $this->attributeSet;
916
    }
917
918
    /**
919
     * Return's the attribute set with the passed attribute set name.
920
     *
921
     * @param string $attributeSetName The name of the requested attribute set
922
     *
923
     * @return array The attribute set data
924
     * @throws \Exception Is thrown, if the attribute set with the passed name is not available
925
     */
926 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...
927
    {
928
929
        // query whether or not attribute sets for the actualy entity type code are available
930
        if (isset($this->attributeSets[$entityTypeCode = $this->getEntityTypeCode()])) {
931
            // load the attribute sets for the actualy entity type code
932
            $attributSets = $this->attributeSets[$entityTypeCode];
933
934
            // query whether or not, the requested attribute set is available
935
            if (isset($attributSets[$attributeSetName])) {
936
                return $attributSets[$attributeSetName];
937
            }
938
        }
939
940
        // throw an exception, if not
941
        throw new \Exception(
942
            sprintf(
943
                'Found invalid attribute set name %s in file %s on line %d',
944
                $attributeSetName,
945
                $this->getFilename(),
946
                $this->getLineNumber()
947
            )
948
        );
949
    }
950
951
    /**
952
     * Return's the attributes for the attribute set of the product that has to be created.
953
     *
954
     * @return array The attributes
955
     * @throws \Exception Is thrown if the attributes for the actual attribute set are not available
956
     */
957 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...
958
    {
959
960
        // query whether or not, the requested EAV attributes are available
961
        if (isset($this->attributes[$entityTypeCode = $this->getEntityTypeCode()])) {
962
            // load the attributes for the entity type code
963
            $attributes = $this->attributes[$entityTypeCode];
964
965
            // query whether or not attributes for the actual attribute set name
966
            if (isset($attributes[$attributeSetName = $this->attributeSet[MemberNames::ATTRIBUTE_SET_NAME]])) {
967
                return $attributes[$attributeSetName];
968
            }
969
        }
970
971
        // throw an exception, if not
972
        throw new \Exception(
973
            sprintf(
974
                'Found invalid attribute set name "%s" in file %s on line %d',
975
                $attributeSetName,
976
                $this->getFilename(),
977
                $this->getLineNumber()
978
            )
979
        );
980
    }
981
982
    /**
983
     * Return's the EAV attribute with the passed attribute code.
984
     *
985
     * @param string $attributeCode The attribute code
986
     *
987
     * @return array The array with the EAV attribute
988
     * @throws \Exception Is thrown if the attribute with the passed code is not available
989
     */
990
    public function getEavAttributeByAttributeCode($attributeCode)
991
    {
992
993
        // load the attributes
994
        $attributes = $this->getAttributes();
995
996
        // query whether or not the attribute exists
997
        if (isset($attributes[$attributeCode])) {
998
            return $attributes[$attributeCode];
999
        }
1000
1001
        // throw an exception if the requested attribute is not available
1002
        throw new \Exception(
1003
            sprintf(
1004
                'Can\'t load attribute with code "%s" in file %s and line %d',
1005
                $attributeCode,
1006
                $this->getFilename(),
1007
                $this->getLineNumber()
1008
            )
1009
        );
1010
    }
1011
}
1012