Completed
Push — master ( 4142c5...662dc7 )
by Tim
12s
created

AbstractSubject   F

Complexity

Total Complexity 114

Size/Duplication

Total Lines 1301
Duplicated Lines 3.84 %

Coupling/Cohesion

Components 2
Dependencies 11

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 114
c 0
b 0
f 0
lcom 2
cbo 11
dl 50
loc 1301
ccs 0
cts 485
cp 0
rs 3.9999

57 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A getDefaultCallbackMappings() 0 4 1
A getRow() 0 4 1
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 hasValue() 15 15 3
A setValue() 0 4 1
C getValue() 29 29 8
A formatDate() 0 11 2
A explode() 0 11 2
A isDebugMode() 0 4 1
A getConfiguration() 0 4 1
A getSystemLogger() 0 11 2
A getSystemLoggers() 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
B setUp() 0 38 4
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 44 7
A mapAttributeCodeByHeaderMapping() 0 14 2
A isOkFileNeeded() 0 4 1
A getDefaultStore() 0 4 1
A setStoreViewCode() 0 4 1
A getStoreViewCode() 0 14 3
A prepareStoreViewCode() 0 11 2
A getRootCategory() 0 17 2
C getCoreConfigData() 6 71 7
A resolveOriginalColumnName() 0 19 4
A getOriginalData() 0 15 2
A hasOriginalData() 0 4 2
B wrapException() 0 35 3
A stripExceptionSuffix() 0 4 1
A appendExceptionSuffix() 0 21 4
A raiseCounter() 0 9 1
A mergeAttributesRecursive() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
950
                }
951
            }
952
        }
953
954
        // log a debug message with the actual line nr/file information
955
        $this->getSystemLogger()->debug(
956
            sprintf(
957
                'Successfully processed row (operation: %s) in file %s on line %d',
958
                $this->operationName,
959
                $this->filename,
960
                $this->lineNumber
961
            )
962
        );
963
    }
964
965
    /**
966
     * Map the passed attribute code, if a header mapping exists and return the
967
     * mapped mapping.
968
     *
969
     * @param string $attributeCode The attribute code to map
970
     *
971
     * @return string The mapped attribute code, or the original one
972
     */
973
    public function mapAttributeCodeByHeaderMapping($attributeCode)
974
    {
975
976
        // load the header mappings
977
        $headerMappings = $this->getHeaderMappings();
978
979
        // query weather or not we've a mapping, if yes, map the attribute code
980
        if (isset($headerMappings[$attributeCode])) {
981
            $attributeCode = $headerMappings[$attributeCode];
982
        }
983
984
        // return the (mapped) attribute code
985
        return $attributeCode;
986
    }
987
988
    /**
989
     * Queries whether or not that the subject needs an OK file to be processed.
990
     *
991
     * @return boolean TRUE if the subject needs an OK file, else FALSE
992
     */
993
    public function isOkFileNeeded()
994
    {
995
        return $this->getConfiguration()->isOkFileNeeded();
996
    }
997
998
    /**
999
     * Return's the default store.
1000
     *
1001
     * @return array The default store
1002
     */
1003
    public function getDefaultStore()
1004
    {
1005
        return $this->defaultStore;
1006
    }
1007
1008
    /**
1009
     * Set's the store view code the create the product/attributes for.
1010
     *
1011
     * @param string $storeViewCode The store view code
1012
     *
1013
     * @return void
1014
     */
1015
    public function setStoreViewCode($storeViewCode)
1016
    {
1017
        $this->storeViewCode = $storeViewCode;
1018
    }
1019
1020
    /**
1021
     * Return's the store view code the create the product/attributes for.
1022
     *
1023
     * @param string|null $default The default value to return, if the store view code has not been set
1024
     *
1025
     * @return string The store view code
1026
     */
1027
    public function getStoreViewCode($default = null)
1028
    {
1029
1030
        // return the store view code, if available
1031
        if ($this->storeViewCode != null) {
1032
            return $this->storeViewCode;
1033
        }
1034
1035
        // if NOT and a default code is available
1036
        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...
1037
            // return the default value
1038
            return $default;
1039
        }
1040
    }
1041
1042
    /**
1043
     * Prepare's the store view code in the subject.
1044
     *
1045
     * @return void
1046
     */
1047
    public function prepareStoreViewCode()
1048
    {
1049
1050
        // re-set the store view code
1051
        $this->setStoreViewCode(null);
1052
1053
        // initialize the store view code
1054
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
1055
            $this->setStoreViewCode($storeViewCode);
1056
        }
1057
    }
1058
1059
    /**
1060
     * Return's the root category for the actual view store.
1061
     *
1062
     * @return array The store's root category
1063
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
1064
     */
1065
    public function getRootCategory()
1066
    {
1067
1068
        // load the default store
1069
        $defaultStore = $this->getDefaultStore();
1070
1071
        // load the actual store view code
1072
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
1073
1074
        // query weather or not we've a root category or not
1075
        if (isset($this->rootCategories[$storeViewCode])) {
1076
            return $this->rootCategories[$storeViewCode];
1077
        }
1078
1079
        // throw an exception if the root category is NOT available
1080
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1081
    }
1082
1083
    /**
1084
     * Return's the Magento configuration value.
1085
     *
1086
     * @param string  $path    The Magento path of the requested configuration value
1087
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1088
     * @param string  $scope   The scope the configuration value has been set
1089
     * @param integer $scopeId The scope ID the configuration value has been set
1090
     *
1091
     * @return mixed The configuration value
1092
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1093
     */
1094
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1095
    {
1096
1097
        // initialize the core config data
1098
        $coreConfigData = array(
1099
            MemberNames::PATH => $path,
1100
            MemberNames::SCOPE => $scope,
1101
            MemberNames::SCOPE_ID => $scopeId
1102
        );
1103
1104
        // generate the UID from the passed data
1105
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1106
1107
        // iterate over the core config data and try to find the requested configuration value
1108
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1109
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1110
        }
1111
1112
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1113
        if ($scope === ScopeKeys::SCOPE_STORES && isset($this->stores[$scopeId])) {
1114
            // replace scope with 'websites' and website ID
1115
            $coreConfigData = array_merge(
1116
                $coreConfigData,
1117
                array(
1118
                    MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1119
                    MemberNames::SCOPE_ID => $this->stores[$scopeId][MemberNames::WEBSITE_ID]
1120
                )
1121
            );
1122
1123
            // generate the UID from the passed data, merged with the 'websites' scope and ID
1124
            $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1125
1126
            // query whether or not, the configuration value on 'websites' level
1127 View Code Duplication
            if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1128
                return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1129
            }
1130
        }
1131
1132
        // replace scope with 'default' and scope ID '0'
1133
        $coreConfigData = array_merge(
1134
            $coreConfigData,
1135
            array(
1136
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1137
                MemberNames::SCOPE_ID => 0
1138
            )
1139
        );
1140
1141
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1142
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1143
1144
        // query whether or not, the configuration value on 'default' level
1145 View Code Duplication
        if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1146
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1147
        }
1148
1149
        // if not, return the passed default value
1150
        if ($default !== null) {
1151
            return $default;
1152
        }
1153
1154
        // throw an exception if no value can be found
1155
        // in the Magento configuration
1156
        throw new \Exception(
1157
            sprintf(
1158
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1159
                $path,
1160
                $scope,
1161
                $scopeId
1162
            )
1163
        );
1164
    }
1165
1166
    /**
1167
     * Resolve the original column name for the passed one.
1168
     *
1169
     * @param string $columnName The column name that has to be resolved
1170
     *
1171
     * @return string|null The original column name
1172
     */
1173
    public function resolveOriginalColumnName($columnName)
1174
    {
1175
1176
        // try to load the original data
1177
        $originalData = $this->getOriginalData();
1178
1179
        // query whether or not original data is available
1180
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1181
            // query whether or not the original column name is available
1182
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1183
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1184
            }
1185
1186
            // query whether or a wildcard column name is available
1187
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1188
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1189
            }
1190
        }
1191
    }
1192
1193
    /**
1194
     * Return's the original data if available, or an empty array.
1195
     *
1196
     * @return array The original data
1197
     */
1198
    public function getOriginalData()
1199
    {
1200
1201
        // initialize the array for the original data
1202
        $originalData = array();
1203
1204
        // query whether or not the column contains original data
1205
        if ($this->hasOriginalData()) {
1206
            // unerialize the original data from the column
1207
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1208
        }
1209
1210
        // return an empty array, if not
1211
        return $originalData;
1212
    }
1213
1214
    /**
1215
     * Query's whether or not the actual column contains original data like
1216
     * filename, line number and column names.
1217
     *
1218
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1219
     */
1220
    public function hasOriginalData()
1221
    {
1222
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1223
    }
1224
1225
    /**
1226
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1227
     * line number and column names and use it for a detailed exception message.
1228
     *
1229
     * @param array      $columnNames The column names that should be resolved and wrapped
1230
     * @param \Exception $parent      The exception we want to wrap
1231
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1232
     *
1233
     * @return \Exception the wrapped exception
1234
     */
1235
    public function wrapException(
1236
        array $columnNames,
1237
        \Exception $parent = null,
1238
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1239
    ) {
1240
1241
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1242
        if ($this->hasOriginalData()) {
1243
            // load the original data
1244
            $originalData = $this->getOriginalData();
1245
1246
            // replace old filename and line number with original data
1247
            $message = $this->appendExceptionSuffix(
1248
                $this->stripExceptionSuffix($parent->getMessage()),
0 ignored issues
show
Bug introduced by
It seems like $parent is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
1249
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1250
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1251
            );
1252
1253
            // prepare the original column names
1254
            $originalColumnNames = array();
1255
            foreach ($columnNames as $columnName) {
1256
                $originalColumnNames = $this->resolveOriginalColumnName($columnName);
1257
            }
1258
1259
            // append the column information
1260
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1261
1262
            // create a new exception and wrap the parent one
1263
            return new $className($message, null, $parent);
1264
        }
1265
1266
        // simply return the parent exception, because
1267
        // we can't find any original information
1268
        return $parent;
1269
    }
1270
1271
    /**
1272
     * Strip's the exception suffix containing filename and line number from the
1273
     * passed message.
1274
     *
1275
     * @param string $message The message to strip the exception suffix from
1276
     *
1277
     * @return mixed The message without the exception suffix
1278
     */
1279
    public function stripExceptionSuffix($message)
1280
    {
1281
        return str_replace($this->appendExceptionSuffix(), '', $message);
1282
    }
1283
1284
    /**
1285
     * Append's the exception suffix containing filename and line number to the
1286
     * passed message. If no message has been passed, only the suffix will be
1287
     * returned
1288
     *
1289
     * @param string|null $message    The message to append the exception suffix to
1290
     * @param string|null $filename   The filename used to create the suffix
1291
     * @param string|null $lineNumber The line number used to create the suffx
1292
     *
1293
     * @return string The message with the appended exception suffix
1294
     */
1295
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1296
    {
1297
1298
        // query whether or not a filename has been passed
1299
        if ($filename === null) {
1300
            $filename = $this->getFilename();
1301
        }
1302
1303
        // query whether or not a line number has been passed
1304
        if ($lineNumber === null) {
1305
            $lineNumber = $this->getLineNumber();
1306
        }
1307
1308
        // if no message has been passed, only return the suffix
1309
        if ($message === null) {
1310
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1311
        }
1312
1313
        // concatenate the message with the suffix and return it
1314
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1315
    }
1316
1317
    /**
1318
     * Raises the value for the counter with the passed key by one.
1319
     *
1320
     * @param mixed $counterName The name of the counter to raise
1321
     *
1322
     * @return integer The counter's new value
1323
     */
1324
    public function raiseCounter($counterName)
1325
    {
1326
1327
        // raise the counter with the passed name
1328
        return $this->getRegistryProcessor()->raiseCounter(
1329
            $this->getSerial(),
1330
            $counterName
1331
        );
1332
    }
1333
1334
    /**
1335
     * Merge the passed array into the status of the actual import.
1336
     *
1337
     * @param array $status The status information to be merged
1338
     *
1339
     * @return void
1340
     */
1341
    public function mergeAttributesRecursive(array $status)
1342
    {
1343
1344
        // merge the passed status
1345
        $this->getRegistryProcessor()->mergeAttributesRecursive(
1346
            $this->getSerial(),
1347
            $status
1348
        );
1349
    }
1350
}
1351