Completed
Pull Request — master (#65)
by Tim
03:47
created

AbstractSubject::explode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
ccs 0
cts 7
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 2
crap 6
1
<?php
2
3
/**
4
 * TechDivision\Import\Subjects\AbstractSubject
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Subjects;
22
23
use Psr\Log\LoggerInterface;
24
use Goodby\CSV\Import\Standard\Lexer;
25
use Goodby\CSV\Import\Standard\LexerConfig;
26
use Goodby\CSV\Import\Standard\Interpreter;
27
use TechDivision\Import\Utils\ScopeKeys;
28
use TechDivision\Import\Utils\LoggerKeys;
29
use TechDivision\Import\Utils\ColumnKeys;
30
use TechDivision\Import\Utils\MemberNames;
31
use TechDivision\Import\Utils\RegistryKeys;
32
use TechDivision\Import\Utils\Generators\GeneratorInterface;
33
use TechDivision\Import\Services\RegistryProcessor;
34
use TechDivision\Import\Callbacks\CallbackInterface;
35
use TechDivision\Import\Observers\ObserverInterface;
36
use TechDivision\Import\Exceptions\WrappedColumnException;
37
use TechDivision\Import\Services\RegistryProcessorInterface;
38
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
39
40
/**
41
 * An abstract subject implementation.
42
 *
43
 * @author    Tim Wagner <[email protected]>
44
 * @copyright 2016 TechDivision GmbH <[email protected]>
45
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
46
 * @link      https://github.com/techdivision/import
47
 * @link      http://www.techdivision.com
48
 */
49
abstract class AbstractSubject implements SubjectInterface
50
{
51
52
    /**
53
     * The trait that provides basic filesystem handling functionality.
54
     *
55
     * @var TechDivision\Import\Subjects\FilesystemTrait
56
     */
57
    use FilesystemTrait;
58
59
    /**
60
     * The system configuration.
61
     *
62
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
63
     */
64
    protected $configuration;
65
66
    /**
67
     * The array with the system logger instances.
68
     *
69
     * @var array
70
     */
71
    protected $systemLoggers = array();
72
73
    /**
74
     * The RegistryProcessor instance to handle running threads.
75
     *
76
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
77
     */
78
    protected $registryProcessor;
79
80
    /**
81
     * The actions unique serial.
82
     *
83
     * @var string
84
     */
85
    protected $serial;
86
87
    /**
88
     * The name of the file to be imported.
89
     *
90
     * @var string
91
     */
92
    protected $filename;
93
94
    /**
95
     * Array with the subject's observers.
96
     *
97
     * @var array
98
     */
99
    protected $observers = array();
100
101
    /**
102
     * Array with the subject's callbacks.
103
     *
104
     * @var array
105
     */
106
    protected $callbacks = array();
107
108
    /**
109
     * The subject's callback mappings.
110
     *
111
     * @var array
112
     */
113
    protected $callbackMappings = array();
114
115
    /**
116
     * Contain's the column names from the header line.
117
     *
118
     * @var array
119
     */
120
    protected $headers = array();
121
122
    /**
123
     * The actual line number.
124
     *
125
     * @var integer
126
     */
127
    protected $lineNumber = 0;
128
129
    /**
130
     * The actual operation name.
131
     *
132
     * @var string
133
     */
134
    protected $operationName ;
135
136
    /**
137
     * The flag that stop's overserver execution on the actual row.
138
     *
139
     * @var boolean
140
     */
141
    protected $skipRow = false;
142
143
    /**
144
     * The available root categories.
145
     *
146
     * @var array
147
     */
148
    protected $rootCategories = array();
149
150
    /**
151
     * The Magento configuration.
152
     *
153
     * @var array
154
     */
155
    protected $coreConfigData = array();
156
157
    /**
158
     * The available stores.
159
     *
160
     * @var array
161
     */
162
    protected $stores = array();
163
164
    /**
165
     * The default store.
166
     *
167
     * @var array
168
     */
169
    protected $defaultStore;
170
171
    /**
172
     * The store view code the create the product/attributes for.
173
     *
174
     * @var string
175
     */
176
    protected $storeViewCode;
177
178
    /**
179
     * The UID generator for the core config data.
180
     *
181
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
182
     */
183
    protected $coreConfigDataUidGenerator;
184
185
    /**
186
     * The actual row.
187
     *
188
     * @var array
189
     */
190
    protected $row = array();
191
192
    /**
193
     * Initialize the subject instance.
194
     *
195
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration              The subject configuration instance
196
     * @param \TechDivision\Import\Services\RegistryProcessorInterface         $registryProcessor          The registry processor instance
197
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface         $coreConfigDataUidGenerator The UID generator for the core config data
198
     * @param array                                                            $systemLoggers              The array with the system loggers instances
199
     */
200
    public function __construct(
201
        SubjectConfigurationInterface $configuration,
202
        RegistryProcessorInterface $registryProcessor,
203
        GeneratorInterface $coreConfigDataUidGenerator,
204
        array $systemLoggers
205
    ) {
206
        $this->configuration = $configuration;
207
        $this->registryProcessor = $registryProcessor;
208
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
209
        $this->systemLoggers = $systemLoggers;
210
    }
211
212
    /**
213
     * Return's the default callback mappings.
214
     *
215
     * @return array The default callback mappings
216
     */
217
    public function getDefaultCallbackMappings()
218
    {
219
        return array();
220
    }
221
222
    /**
223
     * Return's the actual row.
224
     *
225
     * @return array The actual row
226
     */
227
    public function getRow()
228
    {
229
        return $this->row;
230
    }
231
232
    /**
233
     * Stop's observer execution on the actual row.
234
     *
235
     * @return void
236
     */
237
    public function skipRow()
238
    {
239
        $this->skipRow = true;
240
    }
241
242
    /**
243
     * Return's the actual line number.
244
     *
245
     * @return integer The line number
246
     */
247
    public function getLineNumber()
248
    {
249
        return $this->lineNumber;
250
    }
251
252
    /**
253
     * Return's the actual operation name.
254
     *
255
     * @return string
256
     */
257
    public function getOperationName()
258
    {
259
        return $this->operationName;
260
    }
261
262
    /**
263
     * Set's the array containing header row.
264
     *
265
     * @param array $headers The array with the header row
266
     *
267
     * @return void
268
     */
269
    public function setHeaders(array $headers)
270
    {
271
        $this->headers = $headers;
272
    }
273
274
    /**
275
     * Return's the array containing header row.
276
     *
277
     * @return array The array with the header row
278
     */
279
    public function getHeaders()
280
    {
281
        return $this->headers;
282
    }
283
284
    /**
285
     * Queries whether or not the header with the passed name is available.
286
     *
287
     * @param string $name The header name to query
288
     *
289
     * @return boolean TRUE if the header is available, else FALSE
290
     */
291
    public function hasHeader($name)
292
    {
293
        return isset($this->headers[$name]);
294
    }
295
296
    /**
297
     * Return's the header value for the passed name.
298
     *
299
     * @param string $name The name of the header to return the value for
300
     *
301
     * @return mixed The header value
302
     * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
303
     */
304
    public function getHeader($name)
305
    {
306
307
        // query whether or not, the header is available
308
        if (isset($this->headers[$name])) {
309
            return $this->headers[$name];
310
        }
311
312
        // throw an exception, if not
313
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
314
    }
315
316
    /**
317
     * Add's the header with the passed name and position, if not NULL.
318
     *
319
     * @param string $name The header name to add
320
     *
321
     * @return integer The new headers position
322
     */
323
    public function addHeader($name)
324
    {
325
326
        // add the header
327
        $this->headers[$name] = $position = sizeof($this->headers);
328
329
        // return the new header's position
330
        return $position;
331
    }
332
333
    /**
334
     * Query whether or not a value for the column with the passed name exists.
335
     *
336
     * @param string $name The column name to query for a valid value
337
     *
338
     * @return boolean TRUE if the value is set, else FALSE
339
     */
340 View Code Duplication
    public function hasValue($name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
341
    {
342
343
        // query whether or not the header is available
344
        if ($this->hasHeader($name)) {
345
            // load the key for the row
346
            $headerValue = $this->getHeader($name);
347
348
            // query whether the rows column has a vaild value
349
            return (isset($this->row[$headerValue]) && $this->row[$headerValue] != '');
350
        }
351
352
        // return FALSE if not
353
        return false;
354
    }
355
356
    /**
357
     * Set the value in the passed column name.
358
     *
359
     * @param string $name  The column name to set the value for
360
     * @param mixed  $value The value to set
361
     *
362
     * @return void
363
     */
364
    public function setValue($name, $value)
365
    {
366
        $this->row[$this->getHeader($name)] = $value;
367
    }
368
369
    /**
370
     * Resolve's the value with the passed colum name from the actual row. If a callback will
371
     * be passed, the callback will be invoked with the found value as parameter. If
372
     * the value is NULL or empty, the default value will be returned.
373
     *
374
     * @param string        $name     The name of the column to return the value for
375
     * @param mixed|null    $default  The default value, that has to be returned, if the row's value is empty
376
     * @param callable|null $callback The callback that has to be invoked on the value, e. g. to format it
377
     *
378
     * @return mixed|null The, almost formatted, value
379
     */
380 View Code Duplication
    public function getValue($name, $default = null, callable $callback = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
381
    {
382
383
        // initialize the value
384
        $value = null;
385
386
        // query whether or not the header is available
387
        if ($this->hasHeader($name)) {
388
            // load the header value
389
            $headerValue = $this->getHeader($name);
390
            // query wheter or not, the value with the requested key is available
391
            if ((isset($this->row[$headerValue]) && $this->row[$headerValue] != '')) {
392
                $value = $this->row[$headerValue];
393
            }
394
        }
395
396
        // query whether or not, a callback has been passed
397
        if ($value != null && is_callable($callback)) {
398
            $value = call_user_func($callback, $value);
399
        }
400
401
        // query whether or not
402
        if ($value == null && $default !== null) {
403
            $value = $default;
404
        }
405
406
        // return the value
407
        return $value;
408
    }
409
410
    /**
411
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
412
     * If the passed value is NOT a valid date, NULL will be returned.
413
     *
414
     * @param string|null $value The value to format
415
     *
416
     * @return string The formatted date
417
     */
418
    public function formatDate($value)
419
    {
420
421
        // create a DateTime instance from the passed value
422
        if ($dateTime = \DateTime::createFromFormat($this->getSourceDateFormat(), $value)) {
423
            return $dateTime->format('Y-m-d H:i:s');
424
        }
425
426
        // return NULL, if the passed value is NOT a valid date
427
        return null;
428
    }
429
430
    /**
431
     * Extracts the elements of the passed value by exploding them
432
     * with the also passed delimiter.
433
     *
434
     * @param string      $value     The value to extract
435
     * @param string|null $delimiter The delimiter used to extrace the elements
436
     *
437
     * @return array The exploded values
438
     */
439
    public function explode($value, $delimiter = null)
440
    {
441
442
        // load the default multiple field delimiter
443
        if ($delimiter === null) {
444
            $delimiter = $this->getMultipleFieldDelimiter();
445
        }
446
447
        // explode and return the array with the values, by using the delimiter
448
        return explode($delimiter, $value);
449
    }
450
451
    /**
452
     * Queries whether or not debug mode is enabled or not, default is TRUE.
453
     *
454
     * @return boolean TRUE if debug mode is enabled, else FALSE
455
     */
456
    public function isDebugMode()
457
    {
458
        return $this->getConfiguration()->isDebugMode();
459
    }
460
461
    /**
462
     * Return's the system configuration.
463
     *
464
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The system configuration
465
     */
466
    public function getConfiguration()
467
    {
468
        return $this->configuration;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->configuration; (TechDivision\Import\Conf...tConfigurationInterface) is incompatible with the return type declared by the interface TechDivision\Import\Subj...rface::getConfiguration of type TechDivision\Import\Configuration\SubjectInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
469
    }
470
471
    /**
472
     * Return's the logger with the passed name, by default the system logger.
473
     *
474
     * @param string $name The name of the requested system logger
475
     *
476
     * @return \Psr\Log\LoggerInterface The logger instance
477
     * @throws \Exception Is thrown, if the requested logger is NOT available
478
     */
479
    public function getSystemLogger($name = LoggerKeys::SYSTEM)
480
    {
481
482
        // query whether or not, the requested logger is available
483
        if (isset($this->systemLoggers[$name])) {
484
            return $this->systemLoggers[$name];
485
        }
486
487
        // throw an exception if the requested logger is NOT available
488
        throw new \Exception(sprintf('The requested logger \'%s\' is not available', $name));
489
    }
490
491
    /**
492
     * Return's the array with the system logger instances.
493
     *
494
     * @return array The logger instance
495
     */
496
    public function getSystemLoggers()
497
    {
498
        return $this->systemLoggers;
499
    }
500
501
    /**
502
     * Return's the RegistryProcessor instance to handle the running threads.
503
     *
504
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
505
     */
506
    public function getRegistryProcessor()
507
    {
508
        return $this->registryProcessor;
509
    }
510
511
    /**
512
     * Set's the unique serial for this import process.
513
     *
514
     * @param string $serial The unique serial
515
     *
516
     * @return void
517
     */
518
    public function setSerial($serial)
519
    {
520
        $this->serial = $serial;
521
    }
522
523
    /**
524
     * Return's the unique serial for this import process.
525
     *
526
     * @return string The unique serial
527
     */
528
    public function getSerial()
529
    {
530
        return $this->serial;
531
    }
532
533
    /**
534
     * Set's the name of the file to import
535
     *
536
     * @param string $filename The filename
537
     *
538
     * @return void
539
     */
540
    public function setFilename($filename)
541
    {
542
        $this->filename = $filename;
543
    }
544
545
    /**
546
     * Return's the name of the file to import.
547
     *
548
     * @return string The filename
549
     */
550
    public function getFilename()
551
    {
552
        return $this->filename;
553
    }
554
555
    /**
556
     * Return's the source date format to use.
557
     *
558
     * @return string The source date format
559
     */
560
    public function getSourceDateFormat()
561
    {
562
        return $this->getConfiguration()->getSourceDateFormat();
563
    }
564
565
    /**
566
     * Return's the multiple field delimiter character to use, default value is comma (,).
567
     *
568
     * @return string The multiple field delimiter character
569
     */
570
    public function getMultipleFieldDelimiter()
571
    {
572
        return $this->getConfiguration()->getMultipleFieldDelimiter();
0 ignored issues
show
Bug introduced by
The method getMultipleFieldDelimiter() does not seem to exist on object<TechDivision\Impo...ConfigurationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
573
    }
574
575
    /**
576
     * Return's the multiple value delimiter character to use, default value is comma (|).
577
     *
578
     * @return string The multiple value delimiter character
579
     */
580
    public function getMultipleValueDelimiter()
581
    {
582
        return $this->getConfiguration()->getMultipleValueDelimiter();
0 ignored issues
show
Bug introduced by
The method getMultipleValueDelimiter() 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...
583
    }
584
585
    /**
586
     * Return's the initialized PDO connection.
587
     *
588
     * @return \PDO The initialized PDO connection
589
     */
590
    public function getConnection()
591
    {
592
        return $this->getProductProcessor()->getConnection();
0 ignored issues
show
Bug introduced by
The method getProductProcessor() does not seem to exist on object<TechDivision\Impo...bjects\AbstractSubject>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
593
    }
594
595
    /**
596
     * Intializes the previously loaded global data for exactly one bunch.
597
     *
598
     * @param string $serial The serial of the actual import
599
     *
600
     * @return void
601
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
602
     */
603
    public function setUp($serial)
604
    {
605
606
        // load the status of the actual import
607
        $status = $this->getRegistryProcessor()->getAttribute($serial);
608
609
        // load the global data we've prepared initially
610
        $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
611
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
612
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
613
        $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
614
615
        // initialize the operation name
616
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();
617
618
        // merge the callback mappings with the mappings from the child instance
619
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
620
621
        // merge the callback mappings the the one from the configuration file
622
        foreach ($this->getConfiguration()->getCallbacks() as $callbackMappings) {
623
            foreach ($callbackMappings as $attributeCode => $mappings) {
624
                // write a log message, that default callback configuration will
625
                // be overwritten with the one from the configuration file
626
                if (isset($this->callbackMappings[$attributeCode])) {
627
                    $this->getSystemLogger()->notice(
628
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
629
                    );
630
                }
631
632
                // override the attributes callbacks
633
                $this->callbackMappings[$attributeCode] = $mappings;
634
            }
635
        }
636
    }
637
638
    /**
639
     * Clean up the global data after importing the variants.
640
     *
641
     * @param string $serial The serial of the actual import
642
     *
643
     * @return void
644
     */
645
    public function tearDown($serial)
646
    {
647
648
        // load the registry processor
649
        $registryProcessor = $this->getRegistryProcessor();
650
651
        // update the source directory for the next subject
652
        $registryProcessor->mergeAttributesRecursive(
653
            $serial,
654
            array(RegistryKeys::SOURCE_DIRECTORY => $this->getNewSourceDir($serial))
655
        );
656
657
        // log a debug message with the new source directory
658
        $this->getSystemLogger()->debug(
659
            sprintf('Subject %s successfully updated source directory to %s', __CLASS__, $this->getNewSourceDir($serial))
660
        );
661
    }
662
663
    /**
664
     * Return's the next source directory, which will be the target directory
665
     * of this subject, in most cases.
666
     *
667
     * @param string $serial The serial of the actual import
668
     *
669
     * @return string The new source directory
670
     */
671
    protected function getNewSourceDir($serial)
672
    {
673
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $serial);
674
    }
675
676
    /**
677
     * Register the passed observer with the specific type.
678
     *
679
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
680
     * @param string                                           $type     The type to register the observer with
681
     *
682
     * @return void
683
     */
684
    public function registerObserver(ObserverInterface $observer, $type)
685
    {
686
687
        // query whether or not the array with the callbacks for the
688
        // passed type has already been initialized, or not
689
        if (!isset($this->observers[$type])) {
690
            $this->observers[$type] = array();
691
        }
692
693
        // append the callback with the instance of the passed type
694
        $this->observers[$type][] = $observer;
695
    }
696
697
    /**
698
     * Register the passed callback with the specific type.
699
     *
700
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
701
     * @param string                                           $type     The type to register the callback with
702
     *
703
     * @return void
704
     */
705
    public function registerCallback(CallbackInterface $callback, $type)
706
    {
707
708
        // query whether or not the array with the callbacks for the
709
        // passed type has already been initialized, or not
710
        if (!isset($this->callbacks[$type])) {
711
            $this->callbacks[$type] = array();
712
        }
713
714
        // append the callback with the instance of the passed type
715
        $this->callbacks[$type][] = $callback;
716
    }
717
718
    /**
719
     * Return's the array with callbacks for the passed type.
720
     *
721
     * @param string $type The type of the callbacks to return
722
     *
723
     * @return array The callbacks
724
     */
725
    public function getCallbacksByType($type)
726
    {
727
728
        // initialize the array for the callbacks
729
        $callbacks = array();
730
731
        // query whether or not callbacks for the type are available
732
        if (isset($this->callbacks[$type])) {
733
            $callbacks = $this->callbacks[$type];
734
        }
735
736
        // return the array with the type's callbacks
737
        return $callbacks;
738
    }
739
740
    /**
741
     * Return's the array with the available observers.
742
     *
743
     * @return array The observers
744
     */
745
    public function getObservers()
746
    {
747
        return $this->observers;
748
    }
749
750
    /**
751
     * Return's the array with the available callbacks.
752
     *
753
     * @return array The callbacks
754
     */
755
    public function getCallbacks()
756
    {
757
        return $this->callbacks;
758
    }
759
760
    /**
761
     * Return's the callback mappings for this subject.
762
     *
763
     * @return array The array with the subject's callback mappings
764
     */
765
    public function getCallbackMappings()
766
    {
767
        return $this->callbackMappings;
768
    }
769
770
    /**
771
     * Imports the content of the file with the passed filename.
772
     *
773
     *
774
     * @param string $serial   The serial of the actual import
775
     * @param string $filename The filename to process
776
     *
777
     * @return void
778
     * @throws \Exception Is thrown, if the import can't be processed
779
     */
780
    public function import($serial, $filename)
781
    {
782
783
        try {
784
            // stop processing, if the filename doesn't match
785
            if (!$this->match($filename)) {
786
                return;
787
            }
788
789
            // load the system logger instance
790
            $systemLogger = $this->getSystemLogger();
791
792
            // prepare the flag filenames
793
            $inProgressFilename = sprintf('%s.inProgress', $filename);
794
            $importedFilename = sprintf('%s.imported', $filename);
795
            $failedFilename = sprintf('%s.failed', $filename);
796
797
            // query whether or not the file has already been imported
798
            if (is_file($failedFilename) ||
799
                is_file($importedFilename) ||
800
                is_file($inProgressFilename)
801
            ) {
802
                // log a debug message and exit
803
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
804
                return;
805
            }
806
807
            // flag file as in progress
808
            touch($inProgressFilename);
809
810
            // track the start time
811
            $startTime = microtime(true);
812
813
            // initialize the serial/filename
814
            $this->setSerial($serial);
815
            $this->setFilename($filename);
816
817
            // load the system logger
818
            $systemLogger = $this->getSystemLogger();
819
820
            // initialize the lexer instance itself
821
            $lexer = new Lexer($this->getLexerConfig());
822
823
            // initialize the interpreter
824
            $interpreter = new Interpreter();
825
            $interpreter->addObserver(array($this, 'importRow'));
826
827
            // query whether or not we want to use the strict mode
828
            if (!$this->getConfiguration()->isStrictMode()) {
829
                $interpreter->unstrict();
830
            }
831
832
            // log a message that the file has to be imported
833
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
834
835
            // parse the CSV file to be imported
836
            $lexer->parse($filename, $interpreter);
837
838
            // track the time needed for the import in seconds
839
            $endTime = microtime(true) - $startTime;
840
841
            // log a message that the file has successfully been imported
842
            $systemLogger->debug(sprintf('Successfully imported file %s in %f s', $filename, $endTime));
843
844
            // rename flag file, because import has been successfull
845
            rename($inProgressFilename, $importedFilename);
846
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
847
        } catch (\Exception $e) {
848
            // rename the flag file, because import failed and write the stack trace
849
            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...
850
            file_put_contents($failedFilename, $e->__toString());
851
852
            // do not wrap the exception if not already done
853
            if ($e instanceof WrappedColumnException) {
854
                throw $e;
855
            }
856
857
            // else wrap and throw the exception
858
            throw $this->wrapException(array(), $e);
859
        }
860
    }
861
862
    /**
863
     * This method queries whether or not the passed filename matches
864
     * the pattern, based on the subjects configured prefix.
865
     *
866
     * @param string $filename The filename to match
867
     *
868
     * @return boolean TRUE if the filename matches, else FALSE
869
     */
870
    protected function match($filename)
871
    {
872
873
        // prepare the pattern to query whether the file has to be processed or not
874
        $pattern = sprintf('/^.*\/%s.*\\.csv$/', $this->getConfiguration()->getPrefix());
875
876
        // stop processing, if the filename doesn't match
877
        return (boolean) preg_match($pattern, $filename);
878
    }
879
880
    /**
881
     * Initialize and return the lexer configuration.
882
     *
883
     * @return \Goodby\CSV\Import\Standard\LexerConfig The lexer configuration
884
     */
885
    protected function getLexerConfig()
886
    {
887
888
        // initialize the lexer configuration
889
        $config = new LexerConfig();
890
891
        // query whether or not a delimiter character has been configured
892
        if ($delimiter = $this->getConfiguration()->getDelimiter()) {
893
            $config->setDelimiter($delimiter);
894
        }
895
896
        // query whether or not a custom escape character has been configured
897
        if ($escape = $this->getConfiguration()->getEscape()) {
898
            $config->setEscape($escape);
899
        }
900
901
        // query whether or not a custom enclosure character has been configured
902
        if ($enclosure = $this->getConfiguration()->getEnclosure()) {
903
            $config->setEnclosure($enclosure);
904
        }
905
906
        // query whether or not a custom source charset has been configured
907
        if ($fromCharset = $this->getConfiguration()->getFromCharset()) {
908
            $config->setFromCharset($fromCharset);
909
        }
910
911
        // query whether or not a custom target charset has been configured
912
        if ($toCharset = $this->getConfiguration()->getToCharset()) {
913
            $config->setToCharset($toCharset);
914
        }
915
916
        // return the lexer configuratio
917
        return $config;
918
    }
919
920
    /**
921
     * Imports the passed row into the database. If the import failed, the exception
922
     * will be catched and logged, but the import process will be continued.
923
     *
924
     * @param array $row The row with the data to be imported
925
     *
926
     * @return void
927
     */
928
    public function importRow(array $row)
929
    {
930
931
        // initialize the row
932
        $this->row = $row;
933
934
        // raise the line number and reset the skip row flag
935
        $this->lineNumber++;
936
        $this->skipRow = false;
937
938
        // initialize the headers with the columns from the first line
939
        if (sizeof($this->headers) === 0) {
940
            foreach ($this->row as $value => $key) {
941
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
942
            }
943
            return;
944
        }
945
946
        // process the observers
947
        foreach ($this->getObservers() as $observers) {
948
            // invoke the pre-import/import and post-import observers
949
            foreach ($observers as $observer) {
950
                // query whether or not we have to skip the row
951
                if ($this->skipRow) {
952
                    break;
953
                }
954
955
                // if not, process the next observer
956
                if ($observer instanceof ObserverInterface) {
957
                    $this->row = $observer->handle($this->row);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Observers\ObserverInterface as the method handle() does only exist in the following implementations of said interface: TechDivision\Import\Obse...stractAttributeObserver, TechDivision\Import\Obse...tractFileUploadObserver, TechDivision\Import\Obse...tionalAttributeObserver, TechDivision\Import\Observers\AttributeSetObserver.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
958
                }
959
            }
960
        }
961
962
        // log a debug message with the actual line nr/file information
963
        $this->getSystemLogger()->debug(
964
            sprintf(
965
                'Successfully processed row (operation: %s) in file %s on line %d',
966
                $this->operationName,
967
                $this->filename,
968
                $this->lineNumber
969
            )
970
        );
971
    }
972
973
    /**
974
     * Map the passed attribute code, if a header mapping exists and return the
975
     * mapped mapping.
976
     *
977
     * @param string $attributeCode The attribute code to map
978
     *
979
     * @return string The mapped attribute code, or the original one
980
     */
981
    public function mapAttributeCodeByHeaderMapping($attributeCode)
982
    {
983
984
        // load the header mappings
985
        $headerMappings = $this->getHeaderMappings();
986
987
        // query weather or not we've a mapping, if yes, map the attribute code
988
        if (isset($headerMappings[$attributeCode])) {
989
            $attributeCode = $headerMappings[$attributeCode];
990
        }
991
992
        // return the (mapped) attribute code
993
        return $attributeCode;
994
    }
995
996
    /**
997
     * Queries whether or not that the subject needs an OK file to be processed.
998
     *
999
     * @return boolean TRUE if the subject needs an OK file, else FALSE
1000
     */
1001
    public function isOkFileNeeded()
1002
    {
1003
        return $this->getConfiguration()->isOkFileNeeded();
1004
    }
1005
1006
    /**
1007
     * Return's the default store.
1008
     *
1009
     * @return array The default store
1010
     */
1011
    public function getDefaultStore()
1012
    {
1013
        return $this->defaultStore;
1014
    }
1015
1016
    /**
1017
     * Set's the store view code the create the product/attributes for.
1018
     *
1019
     * @param string $storeViewCode The store view code
1020
     *
1021
     * @return void
1022
     */
1023
    public function setStoreViewCode($storeViewCode)
1024
    {
1025
        $this->storeViewCode = $storeViewCode;
1026
    }
1027
1028
    /**
1029
     * Return's the store view code the create the product/attributes for.
1030
     *
1031
     * @param string|null $default The default value to return, if the store view code has not been set
1032
     *
1033
     * @return string The store view code
1034
     */
1035
    public function getStoreViewCode($default = null)
1036
    {
1037
1038
        // return the store view code, if available
1039
        if ($this->storeViewCode != null) {
1040
            return $this->storeViewCode;
1041
        }
1042
1043
        // if NOT and a default code is available
1044
        if ($default != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $default of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1045
            // return the default value
1046
            return $default;
1047
        }
1048
    }
1049
1050
    /**
1051
     * Prepare's the store view code in the subject.
1052
     *
1053
     * @return void
1054
     */
1055
    public function prepareStoreViewCode()
1056
    {
1057
1058
        // re-set the store view code
1059
        $this->setStoreViewCode(null);
1060
1061
        // initialize the store view code
1062
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
1063
            $this->setStoreViewCode($storeViewCode);
1064
        }
1065
    }
1066
1067
    /**
1068
     * Return's the root category for the actual view store.
1069
     *
1070
     * @return array The store's root category
1071
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
1072
     */
1073
    public function getRootCategory()
1074
    {
1075
1076
        // load the default store
1077
        $defaultStore = $this->getDefaultStore();
1078
1079
        // load the actual store view code
1080
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
1081
1082
        // query weather or not we've a root category or not
1083
        if (isset($this->rootCategories[$storeViewCode])) {
1084
            return $this->rootCategories[$storeViewCode];
1085
        }
1086
1087
        // throw an exception if the root category is NOT available
1088
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1089
    }
1090
1091
    /**
1092
     * Return's the Magento configuration value.
1093
     *
1094
     * @param string  $path    The Magento path of the requested configuration value
1095
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1096
     * @param string  $scope   The scope the configuration value has been set
1097
     * @param integer $scopeId The scope ID the configuration value has been set
1098
     *
1099
     * @return mixed The configuration value
1100
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1101
     */
1102
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1103
    {
1104
1105
        // initialize the core config data
1106
        $coreConfigData = array(
1107
            MemberNames::PATH => $path,
1108
            MemberNames::SCOPE => $scope,
1109
            MemberNames::SCOPE_ID => $scopeId
1110
        );
1111
1112
        // generate the UID from the passed data
1113
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1114
1115
        // iterate over the core config data and try to find the requested configuration value
1116
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1117
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1118
        }
1119
1120
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1121
        if ($scope === ScopeKeys::SCOPE_STORES && isset($this->stores[$scopeId])) {
1122
            // replace scope with 'websites' and website ID
1123
            $coreConfigData = array_merge(
1124
                $coreConfigData,
1125
                array(
1126
                    MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1127
                    MemberNames::SCOPE_ID => $this->stores[$scopeId][MemberNames::WEBSITE_ID]
1128
                )
1129
            );
1130
1131
            // generate the UID from the passed data, merged with the 'websites' scope and ID
1132
            $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1133
1134
            // query whether or not, the configuration value on 'websites' level
1135 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...
1136
                return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1137
            }
1138
        }
1139
1140
        // replace scope with 'default' and scope ID '0'
1141
        $coreConfigData = array_merge(
1142
            $coreConfigData,
1143
            array(
1144
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1145
                MemberNames::SCOPE_ID => 0
1146
            )
1147
        );
1148
1149
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1150
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1151
1152
        // query whether or not, the configuration value on 'default' level
1153 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...
1154
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1155
        }
1156
1157
        // if not, return the passed default value
1158
        if ($default !== null) {
1159
            return $default;
1160
        }
1161
1162
        // throw an exception if no value can be found
1163
        // in the Magento configuration
1164
        throw new \Exception(
1165
            sprintf(
1166
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1167
                $path,
1168
                $scope,
1169
                $scopeId
1170
            )
1171
        );
1172
    }
1173
1174
    /**
1175
     * Resolve the original column name for the passed one.
1176
     *
1177
     * @param string $columnName The column name that has to be resolved
1178
     *
1179
     * @return string|null The original column name
1180
     */
1181
    public function resolveOriginalColumnName($columnName)
1182
    {
1183
1184
        // try to load the original data
1185
        $originalData = $this->getOriginalData();
1186
1187
        // query whether or not original data is available
1188
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1189
            // query whether or not the original column name is available
1190
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1191
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1192
            }
1193
1194
            // query whether or a wildcard column name is available
1195
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1196
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1197
            }
1198
        }
1199
1200
        // return the original column name
1201
        return $columnName;
1202
    }
1203
1204
    /**
1205
     * Return's the original data if available, or an empty array.
1206
     *
1207
     * @return array The original data
1208
     */
1209
    public function getOriginalData()
1210
    {
1211
1212
        // initialize the array for the original data
1213
        $originalData = array();
1214
1215
        // query whether or not the column contains original data
1216
        if ($this->hasOriginalData()) {
1217
            // unerialize the original data from the column
1218
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1219
        }
1220
1221
        // return an empty array, if not
1222
        return $originalData;
1223
    }
1224
1225
    /**
1226
     * Query's whether or not the actual column contains original data like
1227
     * filename, line number and column names.
1228
     *
1229
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1230
     */
1231
    public function hasOriginalData()
1232
    {
1233
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1234
    }
1235
1236
    /**
1237
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1238
     * line number and column names and use it for a detailed exception message.
1239
     *
1240
     * @param array      $columnNames The column names that should be resolved and wrapped
1241
     * @param \Exception $parent      The exception we want to wrap
1242
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1243
     *
1244
     * @return \Exception the wrapped exception
1245
     */
1246
    public function wrapException(
1247
        array $columnNames = array(),
1248
        \Exception $parent = null,
1249
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1250
    ) {
1251
1252
        // initialize the message
1253
        $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...
1254
1255
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1256
        if ($this->hasOriginalData()) {
1257
            // load the original data
1258
            $originalData = $this->getOriginalData();
1259
1260
            // replace old filename and line number of the original message
1261
            $message = $this->appendExceptionSuffix(
1262
                $this->stripExceptionSuffix($message),
1263
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1264
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1265
            );
1266
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
1267
        } else {
1268
            // append filename and line number to the original message
1269
            $message = $this->appendExceptionSuffix(
1270
                $this->stripExceptionSuffix($message),
1271
                $this->filename,
1272
                $this->lineNumber
1273
            );
1274
        }
1275
1276
        // query whether or not, column names has been passed
1277
        if (sizeof($columnNames) > 0) {
1278
            // prepare the original column names
1279
            $originalColumnNames = array();
1280
            foreach ($columnNames as $columnName) {
1281
                $originalColumnNames = $this->resolveOriginalColumnName($columnName);
1282
            }
1283
1284
            // append the column information
1285
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1286
        }
1287
1288
        // create a new exception and wrap the parent one
1289
        return new $className($message, null, $parent);
1290
    }
1291
1292
    /**
1293
     * Strip's the exception suffix containing filename and line number from the
1294
     * passed message.
1295
     *
1296
     * @param string $message The message to strip the exception suffix from
1297
     *
1298
     * @return mixed The message without the exception suffix
1299
     */
1300
    public function stripExceptionSuffix($message)
1301
    {
1302
        return str_replace($this->appendExceptionSuffix(), '', $message);
1303
    }
1304
1305
    /**
1306
     * Append's the exception suffix containing filename and line number to the
1307
     * passed message. If no message has been passed, only the suffix will be
1308
     * returned
1309
     *
1310
     * @param string|null $message    The message to append the exception suffix to
1311
     * @param string|null $filename   The filename used to create the suffix
1312
     * @param string|null $lineNumber The line number used to create the suffx
1313
     *
1314
     * @return string The message with the appended exception suffix
1315
     */
1316
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1317
    {
1318
1319
        // query whether or not a filename has been passed
1320
        if ($filename === null) {
1321
            $filename = $this->getFilename();
1322
        }
1323
1324
        // query whether or not a line number has been passed
1325
        if ($lineNumber === null) {
1326
            $lineNumber = $this->getLineNumber();
1327
        }
1328
1329
        // if no message has been passed, only return the suffix
1330
        if ($message === null) {
1331
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1332
        }
1333
1334
        // concatenate the message with the suffix and return it
1335
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1336
    }
1337
1338
    /**
1339
     * Raises the value for the counter with the passed key by one.
1340
     *
1341
     * @param mixed $counterName The name of the counter to raise
1342
     *
1343
     * @return integer The counter's new value
1344
     */
1345
    public function raiseCounter($counterName)
1346
    {
1347
1348
        // raise the counter with the passed name
1349
        return $this->getRegistryProcessor()->raiseCounter(
1350
            $this->getSerial(),
1351
            $counterName
1352
        );
1353
    }
1354
1355
    /**
1356
     * Merge the passed array into the status of the actual import.
1357
     *
1358
     * @param array $status The status information to be merged
1359
     *
1360
     * @return void
1361
     */
1362
    public function mergeAttributesRecursive(array $status)
1363
    {
1364
1365
        // merge the passed status
1366
        $this->getRegistryProcessor()->mergeAttributesRecursive(
1367
            $this->getSerial(),
1368
            $status
1369
        );
1370
    }
1371
}
1372