Completed
Push — 8.x ( 824af6 )
by Tim
09:11
created

AbstractSubject::hasOriginalData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Subjects\AbstractSubject
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Subjects;
22
23
use 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 70
    public function __construct(
236
        RegistryProcessorInterface $registryProcessor,
237
        GeneratorInterface $coreConfigDataUidGenerator,
238
        Collection $systemLoggers,
239
        EmitterInterface $emitter
240
    ) {
241 70
        $this->emitter = $emitter;
242 70
        $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 70
        $this->registryProcessor = $registryProcessor;
244 70
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
245 70
    }
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 70
    public function setConfiguration(SubjectConfigurationInterface $configuration)
378
    {
379 70
        $this->configuration = $configuration;
380 70
    }
381
382
    /**
383
     * Return's the subject configuration.
384
     *
385
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
386
     */
387 70
    public function getConfiguration()
388
    {
389 70
        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 70
    public function getRegistryProcessor()
420
    {
421 70
        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 4
    public function getSerial()
442
    {
443 4
        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 70
    public function setUp($serial)
484
    {
485
486
        // load the status of the actual import
487 70
        $status = $this->getRegistryProcessor()->getAttribute($serial);
488
489
        // load the global data we've prepared initially
490 70
        $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
491 70
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
492 70
        $this->storeWebsites  = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
493 70
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
494 70
        $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
495
496
        // initialize the operation name
497 70
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();
498
499
        // merge the callback mappings with the mappings from the child instance
500 70
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
501
502
        // merge the header mappings with the values found in the configuration
503 70
        $this->headerMappings = array_merge($this->headerMappings, $this->getConfiguration()->getHeaderMappings());
504
505
        // merge the callback mappings the the one from the configuration file
506 70
        foreach ($this->getConfiguration()->getCallbacks() as $callbackMappings) {
507 70
            foreach ($callbackMappings as $attributeCode => $mappings) {
508
                // write a log message, that default callback configuration will
509
                // be overwritten with the one from the configuration file
510 70
                if (isset($this->callbackMappings[$attributeCode])) {
511 70
                    $this->getSystemLogger()->notice(
512 70
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
513
                    );
514
                }
515
516
                // override the attributes callbacks
517 70
                $this->callbackMappings[$attributeCode] = $mappings;
518
            }
519
        }
520 70
    }
521
522
    /**
523
     * Clean up the global data after importing the variants.
524
     *
525
     * @param string $serial The serial of the actual import
526
     *
527
     * @return void
528
     */
529 1
    public function tearDown($serial)
530
    {
531
532
        // load the registry processor
533 1
        $registryProcessor = $this->getRegistryProcessor();
534
535
        // update the source directory for the next subject
536 1
        $registryProcessor->mergeAttributesRecursive(
537 1
            $serial,
538
            array(
539 1
                RegistryKeys::SOURCE_DIRECTORY => $newSourceDir = $this->getNewSourceDir($serial),
540 1
                RegistryKeys::FILES => array($this->getFilename() => array(RegistryKeys::STATUS => 1))
541
            )
542
        );
543
544
        // log a debug message with the new source directory
545 1
        $this->getSystemLogger()->debug(
546 1
            sprintf('Subject %s successfully updated source directory to %s', get_class($this), $newSourceDir)
547
        );
548 1
    }
549
550
    /**
551
     * Return's the target directory for the artefact export.
552
     *
553
     * @return string The target directory for the artefact export
554
     */
555 1
    public function getTargetDir()
556
    {
557 1
        return $this->getNewSourceDir($this->getSerial());
558
    }
559
560
    /**
561
     * Return's the next source directory, which will be the target directory
562
     * of this subject, in most cases.
563
     *
564
     * @param string $serial The serial of the actual import
565
     *
566
     * @return string The new source directory
567
     */
568 5
    public function getNewSourceDir($serial)
569
    {
570 5
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $serial);
571
    }
572
573
    /**
574
     * Register the passed observer with the specific type.
575
     *
576
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
577
     * @param string                                           $type     The type to register the observer with
578
     *
579
     * @return void
580
     */
581 6
    public function registerObserver(ObserverInterface $observer, $type)
582
    {
583
584
        // query whether or not the array with the callbacks for the
585
        // passed type has already been initialized, or not
586 6
        if (!isset($this->observers[$type])) {
587 6
            $this->observers[$type] = array();
588
        }
589
590
        // append the callback with the instance of the passed type
591 6
        $this->observers[$type][] = $observer;
592 6
    }
593
594
    /**
595
     * Register the passed callback with the specific type.
596
     *
597
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
598
     * @param string                                           $type     The type to register the callback with
599
     *
600
     * @return void
601
     */
602 2
    public function registerCallback(CallbackInterface $callback, $type)
603
    {
604
605
        // query whether or not the array with the callbacks for the
606
        // passed type has already been initialized, or not
607 2
        if (!isset($this->callbacks[$type])) {
608 2
            $this->callbacks[$type] = array();
609
        }
610
611
        // append the callback with the instance of the passed type
612 2
        $this->callbacks[$type][] = $callback;
613 2
    }
614
615
    /**
616
     * Return's the array with callbacks for the passed type.
617
     *
618
     * @param string $type The type of the callbacks to return
619
     *
620
     * @return array The callbacks
621
     */
622 1
    public function getCallbacksByType($type)
623
    {
624
625
        // initialize the array for the callbacks
626 1
        $callbacks = array();
627
628
        // query whether or not callbacks for the type are available
629 1
        if (isset($this->callbacks[$type])) {
630 1
            $callbacks = $this->callbacks[$type];
631
        }
632
633
        // return the array with the type's callbacks
634 1
        return $callbacks;
635
    }
636
637
    /**
638
     * Return's the array with the available observers.
639
     *
640
     * @return array The observers
641
     */
642 6
    public function getObservers()
643
    {
644 6
        return $this->observers;
645
    }
646
647
    /**
648
     * Return's the array with the available callbacks.
649
     *
650
     * @return array The callbacks
651
     */
652 1
    public function getCallbacks()
653
    {
654 1
        return $this->callbacks;
655
    }
656
657
    /**
658
     * Return's the callback mappings for this subject.
659
     *
660
     * @return array The array with the subject's callback mappings
661
     */
662 2
    public function getCallbackMappings()
663
    {
664 2
        return $this->callbackMappings;
665
    }
666
667
    /**
668
     * Imports the content of the file with the passed filename.
669
     *
670
     *
671
     * @param string $serial   The serial of the actual import
672
     * @param string $filename The filename to process
673
     *
674
     * @return void
675
     * @throws \Exception Is thrown, if the import can't be processed
676
     */
677 2
    public function import($serial, $filename)
678
    {
679
680
        try {
681
            // initialize the serial/filename
682 2
            $this->setSerial($serial);
683 2
            $this->setFilename($filename);
684
685
            // invoke the events that has to be fired before the artfact will be processed
686 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...
687
688
            // load the system logger instance
689 2
            $systemLogger = $this->getSystemLogger();
690
691
            // prepare the flag filenames
692 2
            $inProgressFilename = sprintf('%s.inProgress', $filename);
693 2
            $importedFilename = sprintf('%s.imported', $filename);
694 2
            $failedFilename = sprintf('%s.failed', $filename);
695
696
            // query whether or not the file has already been imported
697 2
            if ($this->isFile($failedFilename) ||
698 1
                $this->isFile($importedFilename) ||
699 2
                $this->isFile($inProgressFilename)
700
            ) {
701
                // log a debug message and exit
702 1
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
703 1
                return;
704
            }
705
706
            // flag file as in progress
707 1
            $this->touch($inProgressFilename);
708
709
            // track the start time
710 1
            $startTime = microtime(true);
711
712
            // initialize the last time we've logged the counter with the processed rows per minute
713 1
            $this->lastLog = time();
714
715
            // log a message that the file has to be imported
716 1
            $systemLogger->info(sprintf('Now start processing file %s', $filename));
717
718
            // let the adapter process the file
719 1
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
720
721
            // track the time needed for the import in seconds
722 1
            $endTime = microtime(true) - $startTime;
723
724
            // log a message that the file has successfully been imported
725 1
            $systemLogger->info(sprintf('Successfully processed file %s with %d lines in %f s', $filename, $this->lineNumber, $endTime));
726
727
            // rename flag file, because import has been successfull
728 1
            if ($this->getConfiguration()->isCreatingImportedFile()) {
729 1
                $this->rename($inProgressFilename, $importedFilename);
730
            } else {
731
                $this->getFilesystemAdapter()->delete($inProgressFilename);
732
            }
733
734
            // invoke the events that has to be fired when the artfact has been successfully processed
735 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...
736
        } catch (\Exception $e) {
737
            // rename the flag file, because import failed and write the stack trace
738
            $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...
739
            $this->write($failedFilename, $e->__toString());
740
741
            // invoke the events that has to be fired when the artfact can't be processed
742
            $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...
743
744
            // do not wrap the exception if not already done
745
            if ($e instanceof WrappedColumnException) {
746
                throw $e;
747
            }
748
749
            // else wrap and throw the exception
750
            throw $this->wrapException(array(), $e);
751
        }
752 1
    }
753
754
    /**
755
     * Imports the passed row into the database. If the import failed, the exception
756
     * will be catched and logged, but the import process will be continued.
757
     *
758
     * @param array $row The row with the data to be imported
759
     *
760
     * @return void
761
     */
762 7
    public function importRow(array $row)
763
    {
764
765
        // initialize the row
766 7
        $this->row = $row;
767
768
        // raise the line number and reset the skip row flag
769 7
        $this->lineNumber++;
770 7
        $this->skipRow = false;
771
772
        // invoke the events that has to be fired before the artfact's row will be processed
773 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...
774
775
        // initialize the headers with the columns from the first line
776 7
        if (sizeof($this->headers) === 0) {
777 1
            foreach ($this->row as $value => $key) {
778 1
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
779
            }
780 1
            return;
781
        }
782
783
        // process the observers
784 6
        foreach ($this->getObservers() as $observers) {
785
            // invoke the pre-import/import and post-import observers
786
            /** @var \TechDivision\Import\Observers\ObserverInterface $observer */
787 6
            foreach ($observers as $observer) {
788
                // query whether or not we have to skip the row
789 6
                if ($this->skipRow) {
790
                    // log a debug message with the actual line nr/file information
791 1
                    $this->getSystemLogger()->warning(
792 1
                        $this->appendExceptionSuffix(
793 1
                            sprintf(
794 1
                                'Skip processing operation "%s" after observer "%s"',
795 1
                                $this->operationName,
796 1
                                get_class($observer)
797
                            )
798
                        )
799
                    );
800
801
                    // skip the row
802 1
                    break 2;
803
                }
804
805
                // if not, set the subject and process the observer
806 6
                if ($observer instanceof ObserverInterface) {
807 6
                    $this->row = $observer->handle($this);
808
                }
809
            }
810
        }
811
812
        // query whether or not a minute has been passed
813 6
        if ($this->lastLog < time() - 60) {
814
            // log the number processed rows per minute
815 6
            $this->getSystemLogger()->info(
816 6
                sprintf(
817 6
                    'Successfully processed "%d (%d)" rows per minute of file "%s"',
818 6
                    $this->lineNumber - $this->lastLineNumber,
819 6
                    $this->lineNumber,
820 6
                    $this->getFilename()
821
                )
822
            );
823
824
            // reset the last log time and the line number
825 6
            $this->lastLog = time();
826 6
            $this->lastLineNumber = $this->lineNumber;
827
        }
828
829
        // log a debug message with the actual line nr/file information
830 6
        $this->getSystemLogger()->debug(
831 6
            $this->appendExceptionSuffix(
832 6
                sprintf(
833 6
                    'Successfully processed operation "%s"',
834 6
                    $this->operationName
835
                )
836
            )
837
        );
838
839
        // invoke the events that has to be fired when the artfact's row has been successfully processed
840 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...
841 6
    }
842
843
    /**
844
     * Queries whether or not that the subject needs an OK file to be processed.
845
     *
846
     * @return boolean TRUE if the subject needs an OK file, else FALSE
847
     */
848 1
    public function isOkFileNeeded()
849
    {
850 1
        return $this->getConfiguration()->isOkFileNeeded();
851
    }
852
853
    /**
854
     * Return's the default store.
855
     *
856
     * @return array The default store
857
     */
858
    public function getDefaultStore()
859
    {
860
        return $this->defaultStore;
861
    }
862
863
    /**
864
     * Return's the default store view code.
865
     *
866
     * @return array The default store view code
867
     */
868 5
    public function getDefaultStoreViewCode()
869
    {
870 5
        return $this->defaultStore[MemberNames::CODE];
871
    }
872
873
    /**
874
     * Set's the store view code the create the product/attributes for.
875
     *
876
     * @param string $storeViewCode The store view code
877
     *
878
     * @return void
879
     */
880 4
    public function setStoreViewCode($storeViewCode)
881
    {
882 4
        $this->storeViewCode = $storeViewCode;
883 4
    }
884
885
    /**
886
     * Return's the store view code the create the product/attributes for.
887
     *
888
     * @param string|null $default The default value to return, if the store view code has not been set
889
     *
890
     * @return string The store view code
891
     */
892 8
    public function getStoreViewCode($default = null)
893
    {
894
895
        // return the store view code, if available
896 8
        if ($this->storeViewCode !== null) {
897 4
            return $this->storeViewCode;
898
        }
899
900
        // if NOT and a default code is available
901 4
        if ($default !== null) {
902
            // return the default value
903 3
            return $default;
904
        }
905
906
        // return the default store view code
907 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...
908
    }
909
910
    /**
911
     * Prepare's the store view code in the subject. If the store_view_code row doesn't contain
912
     * any value, the default code of the default store view will be set.
913
     *
914
     * @return void
915
     */
916 2
    public function prepareStoreViewCode()
917
    {
918
919
        // re-set the store view code
920 2
        $this->setStoreViewCode(null);
921
922
        // initialize the store view code
923 2
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
924 2
            $this->setStoreViewCode($storeViewCode);
925
        }
926 2
    }
927
928
    /**
929
     * Return's the store ID of the store with the passed store view code
930
     *
931
     * @param string $storeViewCode The store view code to return the store ID for
932
     *
933
     * @return integer The ID of the store with the passed ID
934
     * @throws \Exception Is thrown, if the store with the actual code is not available
935
     */
936 4
    public function getStoreId($storeViewCode)
937
    {
938
939
        // query whether or not, the requested store is available
940 4
        if (isset($this->stores[$storeViewCode])) {
941 3
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
942
        }
943
944
        // throw an exception, if not
945 1
        throw new \Exception(
946 1
            sprintf(
947 1
                'Found invalid store view code %s in file %s on line %d',
948 1
                $storeViewCode,
949 1
                $this->getFilename(),
950 1
                $this->getLineNumber()
951
            )
952
        );
953
    }
954
955
    /**
956
     * Return's the store ID of the actual row, or of the default store
957
     * if no store view code is set in the CSV file.
958
     *
959
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
960
     *
961
     * @return integer The ID of the actual store
962
     * @throws \Exception Is thrown, if the store with the actual code is not available
963
     */
964 2
    public function getRowStoreId($default = null)
965
    {
966
967
        // initialize the default store view code, if not passed
968 2
        if ($default === null) {
969 2
            $default = $this->getDefaultStoreViewCode();
970
        }
971
972
        // load the store view code the create the product/attributes for
973 2
        return $this->getStoreId($this->getStoreViewCode($default));
0 ignored issues
show
Bug introduced by
It seems like $default defined by $this->getDefaultStoreViewCode() on line 969 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...
974
    }
975
976
    /**
977
     * Return's the root category for the actual view store.
978
     *
979
     * @return array The store's root category
980
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
981
     */
982 2
    public function getRootCategory()
983
    {
984
985
        // load the actual store view code
986 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...
987
988
        // query weather or not we've a root category or not
989 2
        if (isset($this->rootCategories[$storeViewCode])) {
990 1
            return $this->rootCategories[$storeViewCode];
991
        }
992
993
        // throw an exception if the root category is NOT available
994 1
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
995
    }
996
997
    /**
998
     * Return's the Magento configuration value.
999
     *
1000
     * @param string  $path    The Magento path of the requested configuration value
1001
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1002
     * @param string  $scope   The scope the configuration value has been set
1003
     * @param integer $scopeId The scope ID the configuration value has been set
1004
     *
1005
     * @return mixed The configuration value
1006
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1007
     */
1008 5
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1009
    {
1010
1011
        // initialize the core config data
1012
        $coreConfigData = array(
1013 5
            MemberNames::PATH => $path,
1014 5
            MemberNames::SCOPE => $scope,
1015 5
            MemberNames::SCOPE_ID => $scopeId
1016
        );
1017
1018
        // generate the UID from the passed data
1019 5
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1020
1021
        // iterate over the core config data and try to find the requested configuration value
1022 5
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1023 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1024
        }
1025
1026
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1027 4
        if ($scope === ScopeKeys::SCOPE_STORES) {
1028
            // query whether or not the website with the passed ID is available
1029 2
            foreach ($this->storeWebsites as $storeWebsite) {
1030 2
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1031
                    // replace scope with 'websites' and website ID
1032 2
                    $coreConfigData = array_merge(
1033 2
                        $coreConfigData,
1034
                        array(
1035 2
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1036
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1037
                        )
1038
                    );
1039
1040
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1041 2
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1042
1043
                    // query whether or not, the configuration value on 'websites' level
1044 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...
1045 2
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1046
                    }
1047
                }
1048
            }
1049
        }
1050
1051
        // replace scope with 'default' and scope ID '0'
1052 3
        $coreConfigData = array_merge(
1053 3
            $coreConfigData,
1054
            array(
1055 3
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1056
                MemberNames::SCOPE_ID => 0
1057
            )
1058
        );
1059
1060
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1061 3
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1062
1063
        // query whether or not, the configuration value on 'default' level
1064 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...
1065 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1066
        }
1067
1068
        // if not, return the passed default value
1069 2
        if ($default !== null) {
1070 1
            return $default;
1071
        }
1072
1073
        // throw an exception if no value can be found
1074
        // in the Magento configuration
1075 1
        throw new \Exception(
1076 1
            sprintf(
1077 1
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1078 1
                $path,
1079 1
                $scope,
1080 1
                $scopeId
1081
            )
1082
        );
1083
    }
1084
1085
    /**
1086
     * Resolve the original column name for the passed one.
1087
     *
1088
     * @param string $columnName The column name that has to be resolved
1089
     *
1090
     * @return string|null The original column name
1091
     */
1092 2
    public function resolveOriginalColumnName($columnName)
1093
    {
1094
1095
        // try to load the original data
1096 2
        $originalData = $this->getOriginalData();
1097
1098
        // query whether or not original data is available
1099 2
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1100
            // query whether or not the original column name is available
1101 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1102 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1103
            }
1104
1105
            // query whether or a wildcard column name is available
1106 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1107 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1108
            }
1109
        }
1110
1111
        // return the original column name
1112 1
        return $columnName;
1113
    }
1114
1115
    /**
1116
     * Return's the original data if available, or an empty array.
1117
     *
1118
     * @return array The original data
1119
     */
1120 2
    public function getOriginalData()
1121
    {
1122
1123
        // initialize the array for the original data
1124 2
        $originalData = array();
1125
1126
        // query whether or not the column contains original data
1127 2
        if ($this->hasOriginalData()) {
1128
            // unerialize the original data from the column
1129 1
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1130
        }
1131
1132
        // return an empty array, if not
1133 2
        return $originalData;
1134
    }
1135
1136
    /**
1137
     * Query's whether or not the actual column contains original data like
1138
     * filename, line number and column names.
1139
     *
1140
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1141
     */
1142 2
    public function hasOriginalData()
1143
    {
1144 2
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1145
    }
1146
1147
    /**
1148
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1149
     * line number and column names and use it for a detailed exception message.
1150
     *
1151
     * @param array      $columnNames The column names that should be resolved and wrapped
1152
     * @param \Exception $parent      The exception we want to wrap
1153
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1154
     *
1155
     * @return \Exception the wrapped exception
1156
     */
1157 1
    public function wrapException(
1158
        array $columnNames = array(),
1159
        \Exception $parent = null,
1160
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1161
    ) {
1162
1163
        // initialize the message
1164 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...
1165
1166
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1167 1
        if ($this->hasOriginalData()) {
1168
            // load the original data
1169 1
            $originalData = $this->getOriginalData();
1170
1171
            // replace old filename and line number of the original message
1172 1
            $message = $this->appendExceptionSuffix(
1173 1
                $this->stripExceptionSuffix($message),
1174 1
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1175 1
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1176
            );
1177
        } else {
1178
            // append filename and line number to the original message
1179
            $message = $this->appendExceptionSuffix(
1180
                $this->stripExceptionSuffix($message),
1181
                $this->filename,
1182
                $this->lineNumber
1183
            );
1184
        }
1185
1186
        // query whether or not, column names has been passed
1187 1
        if (sizeof($columnNames) > 0) {
1188
            // prepare the original column names
1189 1
            $originalColumnNames = array();
1190 1
            foreach ($columnNames as $columnName) {
1191 1
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1192
            }
1193
1194
            // append the column information
1195 1
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1196
        }
1197
1198
        // create a new exception and wrap the parent one
1199 1
        return new $className($message, null, $parent);
1200
    }
1201
1202
    /**
1203
     * Strip's the exception suffix containing filename and line number from the
1204
     * passed message.
1205
     *
1206
     * @param string $message The message to strip the exception suffix from
1207
     *
1208
     * @return mixed The message without the exception suffix
1209
     */
1210 1
    public function stripExceptionSuffix($message)
1211
    {
1212 1
        return str_replace($this->appendExceptionSuffix(), '', $message);
1213
    }
1214
1215
    /**
1216
     * Append's the exception suffix containing filename and line number to the
1217
     * passed message. If no message has been passed, only the suffix will be
1218
     * returned
1219
     *
1220
     * @param string|null $message    The message to append the exception suffix to
1221
     * @param string|null $filename   The filename used to create the suffix
1222
     * @param string|null $lineNumber The line number used to create the suffx
1223
     *
1224
     * @return string The message with the appended exception suffix
1225
     */
1226 11
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1227
    {
1228
1229
        // query whether or not a filename has been passed
1230 11
        if ($filename === null) {
1231 11
            $filename = $this->getFilename();
1232
        }
1233
1234
        // query whether or not a line number has been passed
1235 11
        if ($lineNumber === null) {
1236 11
            $lineNumber = $this->getLineNumber();
1237
        }
1238
1239
        // if no message has been passed, only return the suffix
1240 11
        if ($message === null) {
1241 1
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1242
        }
1243
1244
        // concatenate the message with the suffix and return it
1245 11
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1246
    }
1247
1248
    /**
1249
     * Raises the value for the counter with the passed key by one.
1250
     *
1251
     * @param mixed $counterName The name of the counter to raise
1252
     *
1253
     * @return integer The counter's new value
1254
     */
1255 1
    public function raiseCounter($counterName)
1256
    {
1257
1258
        // raise the counter with the passed name
1259 1
        return $this->getRegistryProcessor()->raiseCounter(
1260 1
            $this->getSerial(),
1261 1
            $counterName
1262
        );
1263
    }
1264
1265
    /**
1266
     * Merge the passed array into the status of the actual import.
1267
     *
1268
     * @param array $status The status information to be merged
1269
     *
1270
     * @return void
1271
     */
1272 1
    public function mergeAttributesRecursive(array $status)
1273
    {
1274
1275
        // merge the passed status
1276 1
        $this->getRegistryProcessor()->mergeAttributesRecursive(
1277 1
            $this->getSerial(),
1278 1
            $status
1279
        );
1280 1
    }
1281
}
1282