Completed
Push — master ( bf2c4c...a7caf4 )
by Tim
9s
created

AbstractSubject   C

Complexity

Total Complexity 66

Size/Duplication

Total Lines 829
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 829
c 0
b 0
f 0
wmc 66
lcom 2
cbo 12
ccs 0
cts 286
cp 0
rs 5

42 Methods

Rating   Name   Duplication   Size   Complexity  
A skipRow() 0 4 1
A getLineNumber() 0 4 1
A getOperationName() 0 4 1
A setHeaders() 0 4 1
A getHeaders() 0 4 1
A hasHeader() 0 4 1
A getHeader() 0 11 2
A addHeader() 0 9 1
A isDebugMode() 0 4 1
A setConfiguration() 0 4 1
A getConfiguration() 0 4 1
A setSystemLogger() 0 4 1
A getSystemLogger() 0 4 1
A setRootDir() 0 4 1
A getRootDir() 0 4 1
A setFilesystem() 0 4 1
A getFilesystem() 0 4 1
A setRegistryProcessor() 0 4 1
A getRegistryProcessor() 0 4 1
A setSerial() 0 4 1
A getSerial() 0 4 1
A setFilename() 0 4 1
A getFilename() 0 4 1
A getSourceDateFormat() 0 4 1
A getMultipleFieldDelimiter() 0 4 1
A getConnection() 0 4 1
A setUp() 0 18 1
A resolvePath() 0 17 3
A tearDown() 0 17 1
A getNewSourceDir() 0 4 1
A registerObserver() 0 12 2
A registerCallback() 0 12 2
A getCallbacksByType() 0 14 2
A getObservers() 0 4 1
A getCallbacks() 0 4 1
A getCallbackMappings() 0 4 1
C import() 0 85 7
A match() 0 9 1
B getLexerConfig() 0 34 6
C importRow() 0 40 7
A mapAttributeCodeByHeaderMapping() 0 11 2
A isOkFileNeeded() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractSubject often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractSubject, and based on these observations, apply Extract Interface, too.

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\RegistryKeys;
31
use TechDivision\Import\Utils\ConfigurationKeys;
32
use TechDivision\Import\Services\RegistryProcessor;
33
use TechDivision\Import\Callbacks\CallbackVisitor;
34
use TechDivision\Import\Callbacks\CallbackInterface;
35
use TechDivision\Import\Observers\ObserverVisitor;
36
use TechDivision\Import\Observers\ObserverInterface;
37
use TechDivision\Import\Services\RegistryProcessorInterface;
38
use TechDivision\Import\Configuration\SubjectInterface as SubjectConfigurationInterface;
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 root directory for the virtual filesystem.
54
     *
55
     * @var string
56
     */
57
    protected $rootDir;
58
59
    /**
60
     * The system configuration.
61
     *
62
     * @var \TechDivision\Import\Configuration\SubjectInterface
63
     */
64
    protected $configuration;
65
66
    /**
67
     * The system logger implementation.
68
     *
69
     * @var \Psr\Log\LoggerInterface
70
     */
71
    protected $systemLogger;
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 virtual filesystem instance.
124
     *
125
     * @var \League\Flysystem\FilesystemInterface
126
     */
127
    protected $filesystem;
128
129
    /**
130
     * The actual line number.
131
     *
132
     * @var integer
133
     */
134
    protected $lineNumber = 0;
135
136
    /**
137
     * The actual operation name.
138
     *
139
     * @var string
140
     */
141
    protected $operationName ;
142
143
    /**
144
     * The flag that stop's overserver execution on the actual row.
145
     *
146
     * @var boolean
147
     */
148
    protected $skipRow = false;
149
150
    /**
151
     * Mappings for attribute code => CSV column header.
152
     *
153
     * @var array
154
     */
155
    protected $headerMappings = array(
156
        'product_online' => 'status',
157
        'tax_class_name' => 'tax_class_id',
158
        'bundle_price_type' => 'price_type',
159
        'bundle_sku_type' => 'sku_type',
160
        'bundle_price_view' => 'price_view',
161
        'bundle_weight_type' => 'weight_type',
162
        'base_image' => 'image',
163
        'base_image_label' => 'image_label',
164
        'thumbnail_image' => 'thumbnail',
165
        'thumbnail_image_label'=> 'thumbnail_label',
166
        'bundle_shipment_type' => 'shipment_type'
167
    );
168
169
    /**
170
     * Stop's observer execution on the actual row.
171
     *
172
     * @return void
173
     */
174
    public function skipRow()
175
    {
176
        $this->skipRow = true;
177
    }
178
179
    /**
180
     * Return's the actual line number.
181
     *
182
     * @return integer The line number
183
     */
184
    public function getLineNumber()
185
    {
186
        return $this->lineNumber;
187
    }
188
189
    /**
190
     * Return's the actual operation name.
191
     *
192
     * @return string
193
     */
194
    public function getOperationName()
195
    {
196
        return $this->operationName;
197
    }
198
199
    /**
200
     * Set's the array containing header row.
201
     *
202
     * @param array $headers The array with the header row
203
     *
204
     * @return void
205
     */
206
    public function setHeaders(array $headers)
207
    {
208
        $this->headers = $headers;
209
    }
210
211
    /**
212
     * Return's the array containing header row.
213
     *
214
     * @return array The array with the header row
215
     */
216
    public function getHeaders()
217
    {
218
        return $this->headers;
219
    }
220
221
    /**
222
     * Queries whether or not the header with the passed name is available.
223
     *
224
     * @param string $name The header name to query
225
     *
226
     * @return boolean TRUE if the header is available, else FALSE
227
     */
228
    public function hasHeader($name)
229
    {
230
        return isset($this->headers[$name]);
231
    }
232
233
    /**
234
     * Return's the header value for the passed name.
235
     *
236
     * @param string $name The name of the header to return the value for
237
     *
238
     * @return mixed The header value
239
     * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
240
     */
241
    public function getHeader($name)
242
    {
243
244
        // query whether or not, the header is available
245
        if (isset($this->headers[$name])) {
246
            return $this->headers[$name];
247
        }
248
249
        // throw an exception, if not
250
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
251
    }
252
253
    /**
254
     * Add's the header with the passed name and position, if not NULL.
255
     *
256
     * @param string $name The header name to add
257
     *
258
     * @return integer The new headers position
259
     */
260
    public function addHeader($name)
261
    {
262
263
        // add the header
264
        $this->headers[$name] = $position = sizeof($this->headers);
265
266
        // return the new header's position
267
        return $position;
268
    }
269
270
    /**
271
     * Queries whether or not debug mode is enabled or not, default is TRUE.
272
     *
273
     * @return boolean TRUE if debug mode is enabled, else FALSE
274
     */
275
    public function isDebugMode()
276
    {
277
        return $this->getConfiguration()->isDebugMode();
278
    }
279
280
    /**
281
     * Set's the system configuration.
282
     *
283
     * @param \TechDivision\Import\Configuration\Subject $configuration The system configuration
284
     *
285
     * @return void
286
     */
287
    public function setConfiguration(SubjectConfigurationInterface $configuration)
288
    {
289
        $this->configuration = $configuration;
290
    }
291
292
    /**
293
     * Return's the system configuration.
294
     *
295
     * @return \TechDivision\Import\Configuration\SubjectInterface The system configuration
296
     */
297
    public function getConfiguration()
298
    {
299
        return $this->configuration;
300
    }
301
302
    /**
303
     * Set's the system logger.
304
     *
305
     * @param \Psr\Log\LoggerInterface $systemLogger The system logger
306
     *
307
     * @return void
308
     */
309
    public function setSystemLogger(LoggerInterface $systemLogger)
310
    {
311
        $this->systemLogger = $systemLogger;
312
    }
313
314
    /**
315
     * Return's the system logger.
316
     *
317
     * @return \Psr\Log\LoggerInterface The system logger instance
318
     */
319
    public function getSystemLogger()
320
    {
321
        return $this->systemLogger;
322
    }
323
324
    /**
325
     * Set's root directory for the virtual filesystem.
326
     *
327
     * @param string $rootDir The root directory for the virtual filesystem
328
     *
329
     * @return void
330
     */
331
    public function setRootDir($rootDir)
332
    {
333
        $this->rootDir = $rootDir;
334
    }
335
336
    /**
337
     * Return's the root directory for the virtual filesystem.
338
     *
339
     * @return string The root directory for the virtual filesystem
340
     */
341
    public function getRootDir()
342
    {
343
        return $this->rootDir;
344
    }
345
346
    /**
347
     * Set's the virtual filesystem instance.
348
     *
349
     * @param \League\Flysystem\FilesystemInterface $filesystem The filesystem instance
350
     *
351
     * @return void
352
     */
353
    public function setFilesystem(FilesystemInterface $filesystem)
354
    {
355
        $this->filesystem = $filesystem;
356
    }
357
358
    /**
359
     * Return's the virtual filesystem instance.
360
     *
361
     * @return \League\Flysystem\FilesystemInterface The filesystem instance
362
     */
363
    public function getFilesystem()
364
    {
365
        return $this->filesystem;
366
    }
367
368
    /**
369
     * Sets's the RegistryProcessor instance to handle the running threads.
370
     *
371
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor The registry processor instance
372
     *
373
     * @return void
374
     */
375
    public function setRegistryProcessor(RegistryProcessorInterface $registryProcessor)
376
    {
377
        $this->registryProcessor = $registryProcessor;
378
    }
379
380
    /**
381
     * Return's the RegistryProcessor instance to handle the running threads.
382
     *
383
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
384
     */
385
    public function getRegistryProcessor()
386
    {
387
        return $this->registryProcessor;
388
    }
389
390
    /**
391
     * Set's the unique serial for this import process.
392
     *
393
     * @param string $serial The unique serial
394
     *
395
     * @return void
396
     */
397
    public function setSerial($serial)
398
    {
399
        $this->serial = $serial;
400
    }
401
402
    /**
403
     * Return's the unique serial for this import process.
404
     *
405
     * @return string The unique serial
406
     */
407
    public function getSerial()
408
    {
409
        return $this->serial;
410
    }
411
412
    /**
413
     * Set's the name of the file to import
414
     *
415
     * @param string $filename The filename
416
     *
417
     * @return void
418
     */
419
    public function setFilename($filename)
420
    {
421
        $this->filename = $filename;
422
    }
423
424
    /**
425
     * Return's the name of the file to import.
426
     *
427
     * @return string The filename
428
     */
429
    public function getFilename()
430
    {
431
        return $this->filename;
432
    }
433
434
    /**
435
     * Return's the source date format to use.
436
     *
437
     * @return string The source date format
438
     */
439
    public function getSourceDateFormat()
440
    {
441
        return $this->getConfiguration()->getSourceDateFormat();
442
    }
443
444
    /**
445
     * Return's the multiple field delimiter character to use, default value is comma (,).
446
     *
447
     * @return string The multiple field delimiter character
448
     */
449
    public function getMultipleFieldDelimiter()
450
    {
451
        return $this->getConfiguration()->getMultipleFieldDelimiter();
0 ignored issues
show
Bug introduced by
The method getMultipleFieldDelimiter() does not seem to exist on object<TechDivision\Impo...ation\SubjectInterface>.

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...
452
    }
453
454
    /**
455
     * Return's the initialized PDO connection.
456
     *
457
     * @return \PDO The initialized PDO connection
458
     */
459
    public function getConnection()
460
    {
461
        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...
462
    }
463
464
    /**
465
     * Intializes the previously loaded global data for exactly one bunch.
466
     *
467
     * @return void
468
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
469
     */
470
    public function setUp()
471
    {
472
473
        // initialize the filesystems root directory
474
        $this->setRootDir(
475
            $this->getConfiguration()->getParam(ConfigurationKeys::ROOT_DIRECTORY, getcwd())
476
        );
477
478
        // initialize the filesystem
479
        $this->filesystem = new Filesystem(new Local($this->getRootDir()));
480
481
        // initialize the operation name
482
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();
483
484
        // initialize the callbacks/observers
485
        CallbackVisitor::get()->visit($this);
486
        ObserverVisitor::get()->visit($this);
487
    }
488
489
    /**
490
     * This method tries to resolve the passed path and returns it. If the path
491
     * is relative, the actual working directory will be prepended.
492
     *
493
     * @param string $path The path to be resolved
494
     *
495
     * @return string The resolved path
496
     * @throws \InvalidArgumentException Is thrown, if the path can not be resolved
497
     */
498
    public function resolvePath($path)
499
    {
500
        // if we've an absolute path, return it immediately
501
        if ($this->getFilesystem()->has($path)) {
502
            return $path;
503
        }
504
505
        // try to prepend the actual working directory, assuming we've a relative path
506
        if ($this->getFilesystem()->has($path = getcwd() . DIRECTORY_SEPARATOR . $path)) {
507
            return $path;
508
        }
509
510
        // throw an exception if the passed directory doesn't exists
511
        throw new \InvalidArgumentException(
512
            sprintf('Directory %s doesn\'t exist', $path)
513
        );
514
    }
515
516
    /**
517
     * Clean up the global data after importing the variants.
518
     *
519
     * @return void
520
     */
521
    public function tearDown()
522
    {
523
524
        // load the registry processor
525
        $registryProcessor = $this->getRegistryProcessor();
526
527
        // update the source directory for the next subject
528
        $registryProcessor->mergeAttributesRecursive(
529
            $this->getSerial(),
530
            array(RegistryKeys::SOURCE_DIRECTORY => $this->getNewSourceDir())
531
        );
532
533
        // log a debug message with the new source directory
534
        $this->getSystemLogger()->debug(
535
            sprintf('Subject %s successfully updated source directory to %s', __CLASS__, $this->getNewSourceDir())
536
        );
537
    }
538
539
    /**
540
     * Return's the next source directory, which will be the target directory
541
     * of this subject, in most cases.
542
     *
543
     * @return string The new source directory
544
     */
545
    protected function getNewSourceDir()
546
    {
547
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $this->getSerial());
548
    }
549
550
    /**
551
     * Register the passed observer with the specific type.
552
     *
553
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
554
     * @param string                                           $type     The type to register the observer with
555
     *
556
     * @return void
557
     */
558
    public function registerObserver(ObserverInterface $observer, $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->observers[$type])) {
564
            $this->observers[$type] = array();
565
        }
566
567
        // append the callback with the instance of the passed type
568
        $this->observers[$type][] = $observer;
569
    }
570
571
    /**
572
     * Register the passed callback with the specific type.
573
     *
574
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
575
     * @param string                                           $type     The type to register the callback with
576
     *
577
     * @return void
578
     */
579
    public function registerCallback(CallbackInterface $callback, $type)
580
    {
581
582
        // query whether or not the array with the callbacks for the
583
        // passed type has already been initialized, or not
584
        if (!isset($this->callbacks[$type])) {
585
            $this->callbacks[$type] = array();
586
        }
587
588
        // append the callback with the instance of the passed type
589
        $this->callbacks[$type][] = $callback;
590
    }
591
592
    /**
593
     * Return's the array with callbacks for the passed type.
594
     *
595
     * @param string $type The type of the callbacks to return
596
     *
597
     * @return array The callbacks
598
     */
599
    public function getCallbacksByType($type)
600
    {
601
602
        // initialize the array for the callbacks
603
        $callbacks = array();
604
605
        // query whether or not callbacks for the type are available
606
        if (isset($this->callbacks[$type])) {
607
            $callbacks = $this->callbacks[$type];
608
        }
609
610
        // return the array with the type's callbacks
611
        return $callbacks;
612
    }
613
614
    /**
615
     * Return's the array with the available observers.
616
     *
617
     * @return array The observers
618
     */
619
    public function getObservers()
620
    {
621
        return $this->observers;
622
    }
623
624
    /**
625
     * Return's the array with the available callbacks.
626
     *
627
     * @return array The callbacks
628
     */
629
    public function getCallbacks()
630
    {
631
        return $this->callbacks;
632
    }
633
634
    /**
635
     * Return's the callback mappings for this subject.
636
     *
637
     * @return array The array with the subject's callback mappings
638
     */
639
    public function getCallbackMappings()
640
    {
641
        return $this->callbackMappings;
642
    }
643
644
    /**
645
     * Imports the content of the file with the passed filename.
646
     *
647
     * @param string $serial   The unique process serial
648
     * @param string $filename The filename to process
649
     *
650
     * @return void
651
     * @throws \Exception Is thrown, if the import can't be processed
652
     */
653
    public function import($serial, $filename)
654
    {
655
656
        try {
657
            // stop processing, if the filename doesn't match
658
            if (!$this->match($filename)) {
659
                return;
660
            }
661
662
            // load the system logger instance
663
            $systemLogger = $this->getSystemLogger();
664
665
            // prepare the flag filenames
666
            $inProgressFilename = sprintf('%s.inProgress', $filename);
667
            $importedFilename = sprintf('%s.imported', $filename);
668
            $failedFilename = sprintf('%s.failed', $filename);
669
670
            // query whether or not the file has already been imported
671
            if (is_file($failedFilename) ||
672
                is_file($importedFilename) ||
673
                is_file($inProgressFilename)
674
            ) {
675
                // log a debug message and exit
676
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
677
                return;
678
            }
679
680
            // flag file as in progress
681
            touch($inProgressFilename);
682
683
            // track the start time
684
            $startTime = microtime(true);
685
686
            // initialize serial and filename
687
            $this->setSerial($serial);
688
            $this->setFilename($filename);
689
690
            // load the system logger
691
            $systemLogger = $this->getSystemLogger();
692
693
            // initialize the global global data to import a bunch
694
            $this->setUp();
695
696
            // initialize the lexer instance itself
697
            $lexer = new Lexer($this->getLexerConfig());
698
699
            // initialize the interpreter
700
            $interpreter = new Interpreter();
701
            $interpreter->addObserver(array($this, 'importRow'));
702
703
            // query whether or not we want to use the strict mode
704
            if (!$this->getConfiguration()->isStrictMode()) {
705
                $interpreter->unstrict();
706
            }
707
708
            // log a message that the file has to be imported
709
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
710
711
            // parse the CSV file to be imported
712
            $lexer->parse($filename, $interpreter);
713
714
            // track the time needed for the import in seconds
715
            $endTime = microtime(true) - $startTime;
716
717
            // clean up the data after importing the bunch
718
            $this->tearDown();
719
720
            // log a message that the file has successfully been imported
721
            $systemLogger->debug(sprintf('Succesfully imported file %s in %f s', $filename, $endTime));
722
723
            // rename flag file, because import has been successfull
724
            rename($inProgressFilename, $importedFilename);
725
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
726
        } catch (\Exception $e) {
727
            // rename the flag file, because import failed and write the stack trace
728
            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...
729
            file_put_contents($failedFilename, $e->__toString());
730
731
            // clean up the data after importing the bunch
732
            $this->tearDown();
733
734
            // re-throw the exception
735
            throw $e;
736
        }
737
    }
738
739
    /**
740
     * This method queries whether or not the passed filename matches
741
     * the pattern, based on the subjects configured prefix.
742
     *
743
     * @param string $filename The filename to match
744
     *
745
     * @return boolean TRUE if the filename matches, else FALSE
746
     */
747
    protected function match($filename)
748
    {
749
750
        // prepare the pattern to query whether the file has to be processed or not
751
        $pattern = sprintf('/^.*\/%s.*\\.csv$/', $this->getConfiguration()->getPrefix());
752
753
        // stop processing, if the filename doesn't match
754
        return (boolean) preg_match($pattern, $filename);
755
    }
756
757
    /**
758
     * Initialize and return the lexer configuration.
759
     *
760
     * @return \Goodby\CSV\Import\Standard\LexerConfig The lexer configuration
761
     */
762
    protected function getLexerConfig()
763
    {
764
765
        // initialize the lexer configuration
766
        $config = new LexerConfig();
767
768
        // query whether or not a delimiter character has been configured
769
        if ($delimiter = $this->getConfiguration()->getDelimiter()) {
770
            $config->setDelimiter($delimiter);
771
        }
772
773
        // query whether or not a custom escape character has been configured
774
        if ($escape = $this->getConfiguration()->getEscape()) {
775
            $config->setEscape($escape);
776
        }
777
778
        // query whether or not a custom enclosure character has been configured
779
        if ($enclosure = $this->getConfiguration()->getEnclosure()) {
780
            $config->setEnclosure($enclosure);
781
        }
782
783
        // query whether or not a custom source charset has been configured
784
        if ($fromCharset = $this->getConfiguration()->getFromCharset()) {
785
            $config->setFromCharset($fromCharset);
786
        }
787
788
        // query whether or not a custom target charset has been configured
789
        if ($toCharset = $this->getConfiguration()->getToCharset()) {
790
            $config->setToCharset($toCharset);
791
        }
792
793
        // return the lexer configuratio
794
        return $config;
795
    }
796
797
    /**
798
     * Imports the passed row into the database.
799
     *
800
     * If the import failed, the exception will be catched and logged,
801
     * but the import process will be continued.
802
     *
803
     * @param array $row The row with the data to be imported
804
     *
805
     * @return void
806
     */
807
    public function importRow(array $row)
808
    {
809
810
        // raise the line number and reset the skip row flag
811
        $this->lineNumber++;
812
        $this->skipRow = false;
813
814
        // initialize the headers with the columns from the first line
815
        if (sizeof($this->headers) === 0) {
816
            foreach ($row as $value => $key) {
817
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
818
            }
819
            return;
820
        }
821
822
        // process the observers
823
        foreach ($this->getObservers() as $observers) {
824
            // invoke the pre-import/import and post-import observers
825
            foreach ($observers as $observer) {
826
                // query whether or not we have to skip the row
827
                if ($this->skipRow) {
828
                    break;
829
                }
830
                // if not, process the next observer
831
                if ($observer instanceof ObserverInterface) {
832
                    $row = $observer->handle($row);
0 ignored issues
show
Bug introduced by
The method handle() does not seem to exist on object<TechDivision\Impo...vers\ObserverInterface>.

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...
833
                }
834
            }
835
        }
836
837
        // log a debug message with the actual line nr/file information
838
        $this->getSystemLogger()->debug(
839
            sprintf(
840
                'Successfully processed row (operation: %s) in file %s on line %d',
841
                $this->operationName,
842
                $this->filename,
843
                $this->lineNumber
844
            )
845
        );
846
    }
847
848
    /**
849
     * Map the passed attribute code, if a header mapping exists and return the
850
     * mapped mapping.
851
     *
852
     * @param string $attributeCode The attribute code to map
853
     *
854
     * @return string The mapped attribute code, or the original one
855
     */
856
    public function mapAttributeCodeByHeaderMapping($attributeCode)
857
    {
858
859
        // query weather or not we've a mapping, if yes, map the attribute code
860
        if (isset($this->headerMappings[$attributeCode])) {
861
            $attributeCode = $this->headerMappings[$attributeCode];
862
        }
863
864
        // return the (mapped) attribute code
865
        return $attributeCode;
866
    }
867
868
    /**
869
     * Queries whether or not that the subject needs an OK file to be processed.
870
     *
871
     * @return boolean TRUE if the subject needs an OK file, else FALSE
872
     */
873
    public function isOkFileNeeded()
874
    {
875
        return $this->getConfiguration()->isOkFileNeeded();
876
    }
877
}
878