Completed
Push — 14.x ( 94b788...855d13 )
by Tim
02:15
created

AbstractSubject   F

Complexity

Total Complexity 95

Size/Duplication

Total Lines 1238
Duplicated Lines 0.48 %

Coupling/Cohesion

Components 2
Dependencies 16

Test Coverage

Coverage 93.22%

Importance

Changes 0
Metric Value
wmc 95
lcom 2
cbo 16
dl 6
loc 1238
ccs 275
cts 295
cp 0.9322
rs 0.8
c 0
b 0
f 0

52 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A getEmitter() 0 4 1
A setFilename() 0 4 1
A getFilename() 0 4 1
A setOperationName() 0 4 1
A getOperationName() 0 4 1
A setLineNumber() 0 4 1
A getLineNumber() 0 4 1
A getDefaultCallbackMappings() 0 4 1
A formatDate() 0 4 1
A explode() 0 4 1
A isDebugMode() 0 4 1
A setConfiguration() 0 4 1
A getConfiguration() 0 4 1
A setImportAdapter() 0 4 1
A getImportAdapter() 0 4 1
A getRegistryProcessor() 0 4 1
A setSerial() 0 4 1
A getSerial() 0 4 1
A getSourceDateFormat() 0 4 1
A getMultipleFieldDelimiter() 0 4 1
A getMultipleValueDelimiter() 0 4 1
A setUp() 0 41 4
A tearDown() 0 20 1
A getTargetDir() 0 4 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
B import() 0 76 7
B importRow() 0 83 8
A isOkFileNeeded() 0 4 1
A getDefaultStore() 0 4 1
A getDefaultStoreViewCode() 0 4 1
A setStoreViewCode() 0 4 1
A getStoreViewCode() 0 17 3
A prepareStoreViewCode() 0 11 2
A getStoreId() 0 18 2
A getRowStoreId() 0 11 2
A getRootCategory() 0 14 2
B getCoreConfigData() 6 76 8
A resolveOriginalColumnName() 0 22 4
A getOriginalData() 0 15 2
A hasOriginalData() 0 4 2
A wrapException() 0 44 4
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 League\Event\EmitterInterface;
24
use Doctrine\Common\Collections\Collection;
25
use TechDivision\Import\RowTrait;
26
use TechDivision\Import\HeaderTrait;
27
use TechDivision\Import\SystemLoggerTrait;
28
use TechDivision\Import\Utils\ScopeKeys;
29
use TechDivision\Import\Utils\ColumnKeys;
30
use TechDivision\Import\Utils\EventNames;
31
use TechDivision\Import\Utils\MemberNames;
32
use TechDivision\Import\Utils\RegistryKeys;
33
use TechDivision\Import\Utils\Generators\GeneratorInterface;
34
use TechDivision\Import\Callbacks\CallbackInterface;
35
use TechDivision\Import\Observers\ObserverInterface;
36
use TechDivision\Import\Adapter\ImportAdapterInterface;
37
use TechDivision\Import\Exceptions\WrappedColumnException;
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, FilesystemSubjectInterface, DateConverterSubjectInterface
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 trait that provides basic filesystem handling functionality.
62
     *
63
     * @var \TechDivision\Import\SystemLoggerTrait
64
     */
65
    use SystemLoggerTrait;
66
67
    /**
68
     * The trait that provides date converting functionality.
69
     *
70
     * @var \TechDivision\Import\DateConverterTrait
71
     */
72
    use DateConverterTrait;
73
74
    /**
75
     * The trait that provides header handling functionality.
76
     *
77
     * @var \TechDivision\Import\HeaderTrait
78
     */
79
    use HeaderTrait;
80
81
    /**
82
     * The trait that provides row handling functionality.
83
     *
84
     * @var \TechDivision\Import\RowTrait
85
     */
86
    use RowTrait;
87
88
    /**
89
     * The name of the file to be imported.
90
     *
91
     * @var string
92
     */
93
    protected $filename;
94
95
    /**
96
     * The actual line number.
97
     *
98
     * @var integer
99
     */
100
    protected $lineNumber = 0;
101
102
    /**
103
     * The actual operation name.
104
     *
105
     * @var string
106
     */
107
    protected $operationName ;
108
109
    /**
110
     * The import adapter instance.
111
     *
112
     * @var \TechDivision\Import\Adapter\ImportAdapterInterface
113
     */
114
    protected $importAdapter;
115
116
    /**
117
     * The system configuration.
118
     *
119
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
120
     */
121
    protected $configuration;
122
123
    /**
124
     * The RegistryProcessor instance to handle running threads.
125
     *
126
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
127
     */
128
    protected $registryProcessor;
129
130
    /**
131
     * The actions unique serial.
132
     *
133
     * @var string
134
     */
135
    protected $serial;
136
137
    /**
138
     * Array with the subject's observers.
139
     *
140
     * @var array
141
     */
142
    protected $observers = array();
143
144
    /**
145
     * Array with the subject's callbacks.
146
     *
147
     * @var array
148
     */
149
    protected $callbacks = array();
150
151
    /**
152
     * The subject's callback mappings.
153
     *
154
     * @var array
155
     */
156
    protected $callbackMappings = array();
157
158
    /**
159
     * The available root categories.
160
     *
161
     * @var array
162
     */
163
    protected $rootCategories = array();
164
165
    /**
166
     * The Magento configuration.
167
     *
168
     * @var array
169
     */
170
    protected $coreConfigData = array();
171
172
    /**
173
     * The available stores.
174
     *
175
     * @var array
176
     */
177
    protected $stores = array();
178
179
    /**
180
     * The available websites.
181
     *
182
     * @var array
183
     */
184
    protected $storeWebsites = array();
185
186
    /**
187
     * The default store.
188
     *
189
     * @var array
190
     */
191
    protected $defaultStore;
192
193
    /**
194
     * The store view code the create the product/attributes for.
195
     *
196
     * @var string
197
     */
198
    protected $storeViewCode;
199
200
    /**
201
     * The UID generator for the core config data.
202
     *
203
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
204
     */
205
    protected $coreConfigDataUidGenerator;
206
207
    /**
208
     * UNIX timestamp with the date the last row counter has been logged.
209
     *
210
     * @var integer
211
     */
212
    protected $lastLog = 0;
213
214
    /**
215
     * The number of the last line that has been logged with the row counter
216
     * @var integer
217
     */
218
    protected $lastLineNumber = 0;
219
220
    /**
221
     * The event emitter instance.
222
     *
223
     * @var \League\Event\EmitterInterface
224
     */
225
    protected $emitter;
226
227
    /**
228
     * Initialize the subject instance.
229
     *
230
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
231
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
232
     * @param \Doctrine\Common\Collections\Collection                  $systemLoggers              The array with the system loggers instances
233
     * @param \League\Event\EmitterInterface                           $emitter                    The event emitter instance
234
     */
235 82
    public function __construct(
236
        RegistryProcessorInterface $registryProcessor,
237
        GeneratorInterface $coreConfigDataUidGenerator,
238
        Collection $systemLoggers,
239
        EmitterInterface $emitter
240
    ) {
241 82
        $this->emitter = $emitter;
242 82
        $this->systemLoggers = $systemLoggers;
0 ignored issues
show
Documentation Bug introduced by
It seems like $systemLoggers of type object<Doctrine\Common\Collections\Collection> is incompatible with the declared type array of property $systemLoggers.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
243 82
        $this->registryProcessor = $registryProcessor;
244 82
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
245 82
    }
246
247
    /**
248
     * Return's the event emitter instance.
249
     *
250
     * @return \League\Event\EmitterInterface The event emitter instance
251
     */
252 9
    public function getEmitter()
253
    {
254 9
        return $this->emitter;
255
    }
256
257
    /**
258
     * Set's the name of the file to import
259
     *
260
     * @param string $filename The filename
261
     *
262
     * @return void
263
     */
264 13
    public function setFilename($filename)
265
    {
266 13
        $this->filename = $filename;
267 13
    }
268
269
    /**
270
     * Return's the name of the file to import.
271
     *
272
     * @return string The filename
273
     */
274 12
    public function getFilename()
275
    {
276 12
        return $this->filename;
277
    }
278
279
    /**
280
     * Set's the actual operation name.
281
     *
282
     * @param string $operationName The actual operation name
283
     *
284
     * @return void
285
     */
286 1
    public function setOperationName($operationName)
287
    {
288 1
        $this->operationName = $operationName;
289 1
    }
290
291
    /**
292
     * Return's the actual operation name.
293
     *
294
     * @return string
295
     */
296 1
    public function getOperationName()
297
    {
298 1
        return $this->operationName;
299
    }
300
301
    /**
302
     * Set's the actual line number.
303
     *
304
     * @param integer $lineNumber The line number
305
     *
306
     * @return void
307
     */
308 1
    public function setLineNumber($lineNumber)
309
    {
310 1
        $this->lineNumber = $lineNumber;
311 1
    }
312
313
    /**
314
     * Return's the actual line number.
315
     *
316
     * @return integer The line number
317
     */
318 12
    public function getLineNumber()
319
    {
320 12
        return $this->lineNumber;
321
    }
322
323
    /**
324
     * Return's the default callback mappings.
325
     *
326
     * @return array The default callback mappings
327
     */
328 1
    public function getDefaultCallbackMappings()
329
    {
330 1
        return array();
331
    }
332
333
    /**
334
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
335
     * If the passed value is NOT a valid date, NULL will be returned.
336
     *
337
     * @param string $value The value to format
338
     *
339
     * @return string|null The formatted date or NULL if the date is not valid
340
     */
341
    public function formatDate($value)
342
    {
343
        return $this->getDateConverter()->convert($value);
344
    }
345
346
    /**
347
     * Extracts the elements of the passed value by exploding them
348
     * with the also passed delimiter.
349
     *
350
     * @param string      $value     The value to extract
351
     * @param string|null $delimiter The delimiter used to extrace the elements
352
     *
353
     * @return array The exploded values
354
     */
355
    public function explode($value, $delimiter = null)
356
    {
357
        return $this->getImportAdapter()->explode($value, $delimiter);
358
    }
359
360
    /**
361
     * Queries whether or not debug mode is enabled or not, default is TRUE.
362
     *
363
     * @return boolean TRUE if debug mode is enabled, else FALSE
364
     */
365 1
    public function isDebugMode()
366
    {
367 1
        return $this->getConfiguration()->isDebugMode();
368
    }
369
370
    /**
371
     * Set's the subject configuration.
372
     *
373
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration
374
     *
375
     * @return void
376
     */
377 82
    public function setConfiguration(SubjectConfigurationInterface $configuration)
378
    {
379 82
        $this->configuration = $configuration;
380 82
    }
381
382
    /**
383
     * Return's the subject configuration.
384
     *
385
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
386
     */
387 82
    public function getConfiguration()
388
    {
389 82
        return $this->configuration;
390
    }
391
392
    /**
393
     * Set's the import adapter instance.
394
     *
395
     * @param \TechDivision\Import\Adapter\ImportAdapterInterface $importAdapter The import adapter instance
396
     *
397
     * @return void
398
     */
399 1
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
400
    {
401 1
        $this->importAdapter = $importAdapter;
402 1
    }
403
404
    /**
405
     * Return's the import adapter instance.
406
     *
407
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
408
     */
409 1
    public function getImportAdapter()
410
    {
411 1
        return $this->importAdapter;
412
    }
413
414
    /**
415
     * Return's the RegistryProcessor instance to handle the running threads.
416
     *
417
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
418
     */
419 82
    public function getRegistryProcessor()
420
    {
421 82
        return $this->registryProcessor;
422
    }
423
424
    /**
425
     * Set's the unique serial for this import process.
426
     *
427
     * @param string $serial The unique serial
428
     *
429
     * @return void
430
     */
431 9
    public function setSerial($serial)
432
    {
433 9
        $this->serial = $serial;
434 9
    }
435
436
    /**
437
     * Return's the unique serial for this import process.
438
     *
439
     * @return string The unique serial
440
     */
441 2
    public function getSerial()
442
    {
443 2
        return $this->serial;
444
    }
445
446
    /**
447
     * Return's the source date format to use.
448
     *
449
     * @return string The source date format
450
     */
451
    public function getSourceDateFormat()
452
    {
453
        return $this->getConfiguration()->getSourceDateFormat();
0 ignored issues
show
Bug introduced by
The method getSourceDateFormat() 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...
454
    }
455
456
    /**
457
     * Return's the multiple field delimiter character to use, default value is comma (,).
458
     *
459
     * @return string The multiple field delimiter character
460
     */
461 1
    public function getMultipleFieldDelimiter()
462
    {
463 1
        return $this->getConfiguration()->getMultipleFieldDelimiter();
464
    }
465
466
    /**
467
     * Return's the multiple value delimiter character to use, default value is comma (|).
468
     *
469
     * @return string The multiple value delimiter character
470
     */
471 1
    public function getMultipleValueDelimiter()
472
    {
473 1
        return $this->getConfiguration()->getMultipleValueDelimiter();
474
    }
475
476
    /**
477
     * Intializes the previously loaded global data for exactly one bunch.
478
     *
479
     * @param string $serial The serial of the actual import
480
     *
481
     * @return void
482
     */
483 82
    public function setUp($serial)
484
    {
485
486
        // load the status of the actual import
487 82
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
488
489
        // load the global data we've prepared initially
490 82
        $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
491 82
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
492 82
        $this->storeWebsites  = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
493 82
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
494 82
        $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
495
496
        // initialize the operation name
497 82
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();
498
499
        // merge the callback mappings with the mappings from the child instance
500 82
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
501
502
        // merge the header mappings with the values found in the configuration
503 82
        $this->headerMappings = array_merge($this->headerMappings, $this->getConfiguration()->getHeaderMappings());
504
505
        // load the available callbacks from the configuration
506 82
        $availableCallbacks = $this->getConfiguration()->getCallbacks();
507
508
        // merge the callback mappings the the one from the configuration file
509 82
        foreach ($availableCallbacks as $callbackMappings) {
510 82
            foreach ($callbackMappings as $attributeCode => $mappings) {
511
                // write a log message, that default callback configuration will
512
                // be overwritten with the one from the configuration file
513 82
                if (isset($this->callbackMappings[$attributeCode])) {
514 82
                    $this->getSystemLogger()->notice(
515 82
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
516
                    );
517
                }
518
519
                // override the attributes callbacks
520 82
                $this->callbackMappings[$attributeCode] = $mappings;
521
            }
522
        }
523 82
    }
524
525
    /**
526
     * Clean up the global data after importing the variants.
527
     *
528
     * @param string $serial The serial of the actual import
529
     *
530
     * @return void
531
     */
532 1
    public function tearDown($serial)
533
    {
534
535
        // load the registry processor
536 1
        $registryProcessor = $this->getRegistryProcessor();
537
538
        // update the source directory for the next subject
539 1
        $registryProcessor->mergeAttributesRecursive(
540 1
            RegistryKeys::STATUS,
541
            array(
542 1
                RegistryKeys::SOURCE_DIRECTORY => $newSourceDir = $this->getNewSourceDir($serial),
543 1
                RegistryKeys::FILES => array($this->getFilename() => array(RegistryKeys::STATUS => 1))
544
            )
545
        );
546
547
        // log a debug message with the new source directory
548 1
        $this->getSystemLogger()->debug(
549 1
            sprintf('Subject %s successfully updated source directory to %s', get_class($this), $newSourceDir)
550
        );
551 1
    }
552
553
    /**
554
     * Return's the target directory for the artefact export.
555
     *
556
     * @return string The target directory for the artefact export
557
     */
558 1
    public function getTargetDir()
559
    {
560 1
        return $this->getNewSourceDir($this->getSerial());
561
    }
562
563
    /**
564
     * Return's the next source directory, which will be the target directory
565
     * of this subject, in most cases.
566
     *
567
     * @param string $serial The serial of the actual import
568
     *
569
     * @return string The new source directory
570
     */
571 5
    public function getNewSourceDir($serial)
572
    {
573 5
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $serial);
574
    }
575
576
    /**
577
     * Register the passed observer with the specific type.
578
     *
579
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
580
     * @param string                                           $type     The type to register the observer with
581
     *
582
     * @return void
583
     */
584 6
    public function registerObserver(ObserverInterface $observer, $type)
585
    {
586
587
        // query whether or not the array with the callbacks for the
588
        // passed type has already been initialized, or not
589 6
        if (!isset($this->observers[$type])) {
590 6
            $this->observers[$type] = array();
591
        }
592
593
        // append the callback with the instance of the passed type
594 6
        $this->observers[$type][] = $observer;
595 6
    }
596
597
    /**
598
     * Register the passed callback with the specific type.
599
     *
600
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
601
     * @param string                                           $type     The type to register the callback with
602
     *
603
     * @return void
604
     */
605 2
    public function registerCallback(CallbackInterface $callback, $type)
606
    {
607
608
        // query whether or not the array with the callbacks for the
609
        // passed type has already been initialized, or not
610 2
        if (!isset($this->callbacks[$type])) {
611 2
            $this->callbacks[$type] = array();
612
        }
613
614
        // append the callback with the instance of the passed type
615 2
        $this->callbacks[$type][] = $callback;
616 2
    }
617
618
    /**
619
     * Return's the array with callbacks for the passed type.
620
     *
621
     * @param string $type The type of the callbacks to return
622
     *
623
     * @return array The callbacks
624
     */
625 1
    public function getCallbacksByType($type)
626
    {
627
628
        // initialize the array for the callbacks
629 1
        $callbacks = array();
630
631
        // query whether or not callbacks for the type are available
632 1
        if (isset($this->callbacks[$type])) {
633 1
            $callbacks = $this->callbacks[$type];
634
        }
635
636
        // return the array with the type's callbacks
637 1
        return $callbacks;
638
    }
639
640
    /**
641
     * Return's the array with the available observers.
642
     *
643
     * @return array The observers
644
     */
645 6
    public function getObservers()
646
    {
647 6
        return $this->observers;
648
    }
649
650
    /**
651
     * Return's the array with the available callbacks.
652
     *
653
     * @return array The callbacks
654
     */
655 1
    public function getCallbacks()
656
    {
657 1
        return $this->callbacks;
658
    }
659
660
    /**
661
     * Return's the callback mappings for this subject.
662
     *
663
     * @return array The array with the subject's callback mappings
664
     */
665 2
    public function getCallbackMappings()
666
    {
667 2
        return $this->callbackMappings;
668
    }
669
670
    /**
671
     * Imports the content of the file with the passed filename.
672
     *
673
     *
674
     * @param string $serial   The serial of the actual import
675
     * @param string $filename The filename to process
676
     *
677
     * @return void
678
     * @throws \Exception Is thrown, if the import can't be processed
679
     */
680 2
    public function import($serial, $filename)
681
    {
682
683
        try {
684
            // initialize the serial/filename
685 2
            $this->setSerial($serial);
686 2
            $this->setFilename($filename);
687
688
            // invoke the events that has to be fired before the artfact will be processed
689 2
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_START, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
690
691
            // load the system logger instance
692 2
            $systemLogger = $this->getSystemLogger();
693
694
            // prepare the flag filenames
695 2
            $inProgressFilename = sprintf('%s.inProgress', $filename);
696 2
            $importedFilename = sprintf('%s.imported', $filename);
697 2
            $failedFilename = sprintf('%s.failed', $filename);
698
699
            // query whether or not the file has already been imported
700 2
            if ($this->isFile($failedFilename) ||
701 1
                $this->isFile($importedFilename) ||
702 2
                $this->isFile($inProgressFilename)
703
            ) {
704
                // log a debug message and exit
705 1
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
706 1
                return;
707
            }
708
709
            // flag file as in progress
710 1
            $this->touch($inProgressFilename);
711
712
            // track the start time
713 1
            $startTime = microtime(true);
714
715
            // initialize the last time we've logged the counter with the processed rows per minute
716 1
            $this->lastLog = time();
717
718
            // log a message that the file has to be imported
719 1
            $systemLogger->info(sprintf('Now start processing file %s', $filename));
720
721
            // let the adapter process the file
722 1
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
723
724
            // track the time needed for the import in seconds
725 1
            $endTime = microtime(true) - $startTime;
726
727
            // log a message that the file has successfully been imported
728 1
            $systemLogger->info(sprintf('Successfully processed file %s with %d lines in %f s', $filename, $this->lineNumber, $endTime));
729
730
            // rename flag file, because import has been successfull
731 1
            if ($this->getConfiguration()->isCreatingImportedFile()) {
732 1
                $this->rename($inProgressFilename, $importedFilename);
733
            } else {
734
                $this->getFilesystemAdapter()->delete($inProgressFilename);
735
            }
736
737
            // invoke the events that has to be fired when the artfact has been successfully processed
738 1
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_SUCCESS, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
739
        } catch (\Exception $e) {
740
            // rename the flag file, because import failed and write the stack trace
741
            $this->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...
742
            $this->write($failedFilename, $e->__toString());
743
744
            // invoke the events that has to be fired when the artfact can't be processed
745
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_FAILURE, $this, $e);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
746
747
            // do not wrap the exception if not already done
748
            if ($e instanceof WrappedColumnException) {
749
                throw $e;
750
            }
751
752
            // else wrap and throw the exception
753
            throw $this->wrapException(array(), $e);
754
        }
755 1
    }
756
757
    /**
758
     * Imports the passed row into the database. If the import failed, the exception
759
     * will be catched and logged, but the import process will be continued.
760
     *
761
     * @param array $row The row with the data to be imported
762
     *
763
     * @return void
764
     */
765 7
    public function importRow(array $row)
766
    {
767
768
        // initialize the row
769 7
        $this->row = $row;
770
771
        // raise the line number and reset the skip row flag
772 7
        $this->lineNumber++;
773 7
        $this->skipRow = false;
774
775
        // invoke the events that has to be fired before the artfact's row will be processed
776 7
        $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_ROW_PROCESS_START, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
777
778
        // initialize the headers with the columns from the first line
779 7
        if (sizeof($this->headers) === 0) {
780 1
            foreach ($this->row as $value => $key) {
781 1
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
782
            }
783 1
            return;
784
        }
785
786
        // load the available observers
787 6
        $availableObservers = $this->getObservers();
788
789
        // process the observers
790 6
        foreach ($availableObservers as $observers) {
791
            // invoke the pre-import/import and post-import observers
792
            /** @var \TechDivision\Import\Observers\ObserverInterface $observer */
793 6
            foreach ($observers as $observer) {
794
                // query whether or not we have to skip the row
795 6
                if ($this->skipRow) {
796
                    // log a debug message with the actual line nr/file information
797 1
                    $this->getSystemLogger()->warning(
798 1
                        $this->appendExceptionSuffix(
799 1
                            sprintf(
800 1
                                'Skip processing operation "%s" after observer "%s"',
801 1
                                $this->operationName,
802 1
                                get_class($observer)
803
                            )
804
                        )
805
                    );
806
807
                    // skip the row
808 1
                    break 2;
809
                }
810
811
                // if not, set the subject and process the observer
812 6
                if ($observer instanceof ObserverInterface) {
813 6
                    $this->row = $observer->handle($this);
814
                }
815
            }
816
        }
817
818
        // query whether or not a minute has been passed
819 6
        if ($this->lastLog < time() - 60) {
820
            // log the number processed rows per minute
821 6
            $this->getSystemLogger()->info(
822 6
                sprintf(
823 6
                    'Successfully processed "%d (%d)" rows per minute of file "%s"',
824 6
                    $this->lineNumber - $this->lastLineNumber,
825 6
                    $this->lineNumber,
826 6
                    $this->getFilename()
827
                )
828
            );
829
830
            // reset the last log time and the line number
831 6
            $this->lastLog = time();
832 6
            $this->lastLineNumber = $this->lineNumber;
833
        }
834
835
        // log a debug message with the actual line nr/file information
836 6
        $this->getSystemLogger()->debug(
837 6
            $this->appendExceptionSuffix(
838 6
                sprintf(
839 6
                    'Successfully processed operation "%s"',
840 6
                    $this->operationName
841
                )
842
            )
843
        );
844
845
        // invoke the events that has to be fired when the artfact's row has been successfully processed
846 6
        $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_ROW_PROCESS_SUCCESS, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
847 6
    }
848
849
    /**
850
     * Queries whether or not that the subject needs an OK file to be processed.
851
     *
852
     * @return boolean TRUE if the subject needs an OK file, else FALSE
853
     */
854 1
    public function isOkFileNeeded()
855
    {
856 1
        return $this->getConfiguration()->isOkFileNeeded();
857
    }
858
859
    /**
860
     * Return's the default store.
861
     *
862
     * @return array The default store
863
     */
864
    public function getDefaultStore()
865
    {
866
        return $this->defaultStore;
867
    }
868
869
    /**
870
     * Return's the default store view code.
871
     *
872
     * @return array The default store view code
873
     */
874 5
    public function getDefaultStoreViewCode()
875
    {
876 5
        return $this->defaultStore[MemberNames::CODE];
877
    }
878
879
    /**
880
     * Set's the store view code the create the product/attributes for.
881
     *
882
     * @param string $storeViewCode The store view code
883
     *
884
     * @return void
885
     */
886 4
    public function setStoreViewCode($storeViewCode)
887
    {
888 4
        $this->storeViewCode = $storeViewCode;
889 4
    }
890
891
    /**
892
     * Return's the store view code the create the product/attributes for.
893
     *
894
     * @param string|null $default The default value to return, if the store view code has not been set
895
     *
896
     * @return string The store view code
897
     */
898 8
    public function getStoreViewCode($default = null)
899
    {
900
901
        // return the store view code, if available
902 8
        if ($this->storeViewCode !== null) {
903 4
            return $this->storeViewCode;
904
        }
905
906
        // if NOT and a default code is available
907 4
        if ($default !== null) {
908
            // return the default value
909 3
            return $default;
910
        }
911
912
        // return the default store view code
913 1
        return $this->getDefaultStoreViewCode();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getDefaultStoreViewCode(); (array) is incompatible with the return type declared by the interface TechDivision\Import\Subj...rface::getStoreViewCode of type string.

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...
914
    }
915
916
    /**
917
     * Prepare's the store view code in the subject. If the store_view_code row doesn't contain
918
     * any value, the default code of the default store view will be set.
919
     *
920
     * @return void
921
     */
922 2
    public function prepareStoreViewCode()
923
    {
924
925
        // re-set the store view code
926 2
        $this->setStoreViewCode(null);
927
928
        // initialize the store view code
929 2
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
930 2
            $this->setStoreViewCode($storeViewCode);
931
        }
932 2
    }
933
934
    /**
935
     * Return's the store ID of the store with the passed store view code
936
     *
937
     * @param string $storeViewCode The store view code to return the store ID for
938
     *
939
     * @return integer The ID of the store with the passed ID
940
     * @throws \Exception Is thrown, if the store with the actual code is not available
941
     */
942 4
    public function getStoreId($storeViewCode)
943
    {
944
945
        // query whether or not, the requested store is available
946 4
        if (isset($this->stores[$storeViewCode])) {
947 3
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
948
        }
949
950
        // throw an exception, if not
951 1
        throw new \Exception(
952 1
            sprintf(
953 1
                'Found invalid store view code %s in file %s on line %d',
954 1
                $storeViewCode,
955 1
                $this->getFilename(),
956 1
                $this->getLineNumber()
957
            )
958
        );
959
    }
960
961
    /**
962
     * Return's the store ID of the actual row, or of the default store
963
     * if no store view code is set in the CSV file.
964
     *
965
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
966
     *
967
     * @return integer The ID of the actual store
968
     * @throws \Exception Is thrown, if the store with the actual code is not available
969
     */
970 2
    public function getRowStoreId($default = null)
971
    {
972
973
        // initialize the default store view code, if not passed
974 2
        if ($default === null) {
975 2
            $default = $this->getDefaultStoreViewCode();
976
        }
977
978
        // load the store view code the create the product/attributes for
979 2
        return $this->getStoreId($this->getStoreViewCode($default));
0 ignored issues
show
Bug introduced by
It seems like $default defined by $this->getDefaultStoreViewCode() on line 975 can also be of type array; however, TechDivision\Import\Subj...ect::getStoreViewCode() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $this->getStoreViewCode($default) targeting TechDivision\Import\Subj...ect::getStoreViewCode() can also be of type array; however, TechDivision\Import\Subj...ctSubject::getStoreId() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
980
    }
981
982
    /**
983
     * Return's the root category for the actual view store.
984
     *
985
     * @return array The store's root category
986
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
987
     */
988 2
    public function getRootCategory()
989
    {
990
991
        // load the actual store view code
992 2
        $storeViewCode = $this->getStoreViewCode($this->getDefaultStoreViewCode());
0 ignored issues
show
Documentation introduced by
$this->getDefaultStoreViewCode() is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
993
994
        // query weather or not we've a root category or not
995 2
        if (isset($this->rootCategories[$storeViewCode])) {
996 1
            return $this->rootCategories[$storeViewCode];
997
        }
998
999
        // throw an exception if the root category is NOT available
1000 1
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1001
    }
1002
1003
    /**
1004
     * Return's the Magento configuration value.
1005
     *
1006
     * @param string  $path    The Magento path of the requested configuration value
1007
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1008
     * @param string  $scope   The scope the configuration value has been set
1009
     * @param integer $scopeId The scope ID the configuration value has been set
1010
     *
1011
     * @return mixed The configuration value
1012
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1013
     */
1014 5
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1015
    {
1016
1017
        // initialize the core config data
1018
        $coreConfigData = array(
1019 5
            MemberNames::PATH => $path,
1020 5
            MemberNames::SCOPE => $scope,
1021 5
            MemberNames::SCOPE_ID => $scopeId
1022
        );
1023
1024
        // generate the UID from the passed data
1025 5
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1026
1027
        // iterate over the core config data and try to find the requested configuration value
1028 5
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1029 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1030
        }
1031
1032
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1033 4
        if ($scope === ScopeKeys::SCOPE_STORES) {
1034
            // query whether or not the website with the passed ID is available
1035 2
            foreach ($this->storeWebsites as $storeWebsite) {
1036 2
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1037
                    // replace scope with 'websites' and website ID
1038 2
                    $coreConfigData = array_merge(
1039 2
                        $coreConfigData,
1040
                        array(
1041 2
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1042
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1043
                        )
1044
                    );
1045
1046
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1047 2
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1048
1049
                    // query whether or not, the configuration value on 'websites' level
1050 2 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...
1051 1
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1052
                    }
1053
                }
1054
            }
1055
        }
1056
1057
        // replace scope with 'default' and scope ID '0'
1058 3
        $coreConfigData = array_merge(
1059 3
            $coreConfigData,
1060
            array(
1061 3
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1062
                MemberNames::SCOPE_ID => 0
1063
            )
1064
        );
1065
1066
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1067 3
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1068
1069
        // query whether or not, the configuration value on 'default' level
1070 3 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...
1071 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1072
        }
1073
1074
        // if not, return the passed default value
1075 2
        if ($default !== null) {
1076 1
            return $default;
1077
        }
1078
1079
        // throw an exception if no value can be found
1080
        // in the Magento configuration
1081 1
        throw new \Exception(
1082 1
            sprintf(
1083 1
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1084 1
                $path,
1085 1
                $scope,
1086 1
                $scopeId
1087
            )
1088
        );
1089
    }
1090
1091
    /**
1092
     * Resolve the original column name for the passed one.
1093
     *
1094
     * @param string $columnName The column name that has to be resolved
1095
     *
1096
     * @return string|null The original column name
1097
     */
1098 2
    public function resolveOriginalColumnName($columnName)
1099
    {
1100
1101
        // try to load the original data
1102 2
        $originalData = $this->getOriginalData();
1103
1104
        // query whether or not original data is available
1105 2
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1106
            // query whether or not the original column name is available
1107 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1108 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1109
            }
1110
1111
            // query whether or a wildcard column name is available
1112 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1113 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1114
            }
1115
        }
1116
1117
        // return the original column name
1118 1
        return $columnName;
1119
    }
1120
1121
    /**
1122
     * Return's the original data if available, or an empty array.
1123
     *
1124
     * @return array The original data
1125
     */
1126 2
    public function getOriginalData()
1127
    {
1128
1129
        // initialize the array for the original data
1130 2
        $originalData = array();
1131
1132
        // query whether or not the column contains original data
1133 2
        if ($this->hasOriginalData()) {
1134
            // unerialize the original data from the column
1135 1
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1136
        }
1137
1138
        // return an empty array, if not
1139 2
        return $originalData;
1140
    }
1141
1142
    /**
1143
     * Query's whether or not the actual column contains original data like
1144
     * filename, line number and column names.
1145
     *
1146
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1147
     */
1148 2
    public function hasOriginalData()
1149
    {
1150 2
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1151
    }
1152
1153
    /**
1154
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1155
     * line number and column names and use it for a detailed exception message.
1156
     *
1157
     * @param array      $columnNames The column names that should be resolved and wrapped
1158
     * @param \Exception $parent      The exception we want to wrap
1159
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1160
     *
1161
     * @return \Exception the wrapped exception
1162
     */
1163 1
    public function wrapException(
1164
        array $columnNames = array(),
1165
        \Exception $parent = null,
1166
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1167
    ) {
1168
1169
        // initialize the message
1170 1
        $message = $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...
1171
1172
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1173 1
        if ($this->hasOriginalData()) {
1174
            // load the original data
1175 1
            $originalData = $this->getOriginalData();
1176
1177
            // replace old filename and line number of the original message
1178 1
            $message = $this->appendExceptionSuffix(
1179 1
                $this->stripExceptionSuffix($message),
1180 1
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1181 1
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1182
            );
1183
        } else {
1184
            // append filename and line number to the original message
1185
            $message = $this->appendExceptionSuffix(
1186
                $this->stripExceptionSuffix($message),
1187
                $this->filename,
1188
                $this->lineNumber
1189
            );
1190
        }
1191
1192
        // query whether or not, column names has been passed
1193 1
        if (sizeof($columnNames) > 0) {
1194
            // prepare the original column names
1195 1
            $originalColumnNames = array();
1196 1
            foreach ($columnNames as $columnName) {
1197 1
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1198
            }
1199
1200
            // append the column information
1201 1
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1202
        }
1203
1204
        // create a new exception and wrap the parent one
1205 1
        return new $className($message, null, $parent);
1206
    }
1207
1208
    /**
1209
     * Strip's the exception suffix containing filename and line number from the
1210
     * passed message.
1211
     *
1212
     * @param string $message The message to strip the exception suffix from
1213
     *
1214
     * @return mixed The message without the exception suffix
1215
     */
1216 1
    public function stripExceptionSuffix($message)
1217
    {
1218 1
        return str_replace($this->appendExceptionSuffix(), '', $message);
1219
    }
1220
1221
    /**
1222
     * Append's the exception suffix containing filename and line number to the
1223
     * passed message. If no message has been passed, only the suffix will be
1224
     * returned
1225
     *
1226
     * @param string|null $message    The message to append the exception suffix to
1227
     * @param string|null $filename   The filename used to create the suffix
1228
     * @param string|null $lineNumber The line number used to create the suffx
1229
     *
1230
     * @return string The message with the appended exception suffix
1231
     */
1232 11
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1233
    {
1234
1235
        // query whether or not a filename has been passed
1236 11
        if ($filename === null) {
1237 11
            $filename = $this->getFilename();
1238
        }
1239
1240
        // query whether or not a line number has been passed
1241 11
        if ($lineNumber === null) {
1242 11
            $lineNumber = $this->getLineNumber();
1243
        }
1244
1245
        // if no message has been passed, only return the suffix
1246 11
        if ($message === null) {
1247 1
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1248
        }
1249
1250
        // concatenate the message with the suffix and return it
1251 11
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1252
    }
1253
1254
    /**
1255
     * Raises the value for the counter with the passed key by one.
1256
     *
1257
     * @param mixed $counterName The name of the counter to raise
1258
     *
1259
     * @return integer The counter's new value
1260
     */
1261 1
    public function raiseCounter($counterName)
1262
    {
1263
1264
        // raise the counter with the passed name
1265 1
        return $this->getRegistryProcessor()->raiseCounter(
1266 1
            RegistryKeys::COUNTERS,
1267 1
            $counterName
1268
        );
1269
    }
1270
1271
    /**
1272
     * Merge the passed array into the status of the actual import.
1273
     *
1274
     * @param array $status The status information to be merged
1275
     *
1276
     * @return void
1277
     */
1278 1
    public function mergeAttributesRecursive(array $status)
1279
    {
1280
1281
        // merge the passed status
1282 1
        return $this->getRegistryProcessor()->mergeAttributesRecursive(
1283 1
            RegistryKeys::STATUS,
1284 1
            $status
1285
        );
1286
    }
1287
}
1288