Completed
Push — master ( a3f369...f825b4 )
by Tim
14s
created

AbstractSubject::import()   B

Complexity

Conditions 7
Paths 35

Size

Total Lines 66
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 66
ccs 0
cts 35
cp 0
rs 7.0832
c 0
b 0
f 0
cc 7
eloc 28
nc 35
nop 2
crap 56

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 TechDivision\Import\Utils\ScopeKeys;
25
use TechDivision\Import\Utils\LoggerKeys;
26
use TechDivision\Import\Utils\ColumnKeys;
27
use TechDivision\Import\Utils\MemberNames;
28
use TechDivision\Import\Utils\RegistryKeys;
29
use TechDivision\Import\Utils\Generators\GeneratorInterface;
30
use TechDivision\Import\Services\RegistryProcessor;
31
use TechDivision\Import\Callbacks\CallbackInterface;
32
use TechDivision\Import\Observers\ObserverInterface;
33
use TechDivision\Import\Adapter\ImportAdapterInterface;
34
use TechDivision\Import\Exceptions\WrappedColumnException;
35
use TechDivision\Import\Services\RegistryProcessorInterface;
36
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
37
38
/**
39
 * An abstract subject implementation.
40
 *
41
 * @author    Tim Wagner <[email protected]>
42
 * @copyright 2016 TechDivision GmbH <[email protected]>
43
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
44
 * @link      https://github.com/techdivision/import
45
 * @link      http://www.techdivision.com
46
 */
47
abstract class AbstractSubject implements SubjectInterface
48
{
49
50
    /**
51
     * The trait that provides basic filesystem handling functionality.
52
     *
53
     * @var TechDivision\Import\Subjects\FilesystemTrait
54
     */
55
    use FilesystemTrait;
56
57
    /**
58
     * The import adapter instance.
59
     *
60
     * @var \TechDivision\Import\Adapter\AdapterInterface
61
     */
62
    protected $importAdapter;
63
64
    /**
65
     * The system configuration.
66
     *
67
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
68
     */
69
    protected $configuration;
70
71
    /**
72
     * The array with the system logger instances.
73
     *
74
     * @var array
75
     */
76
    protected $systemLoggers = array();
77
78
    /**
79
     * The RegistryProcessor instance to handle running threads.
80
     *
81
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
82
     */
83
    protected $registryProcessor;
84
85
    /**
86
     * The actions unique serial.
87
     *
88
     * @var string
89
     */
90
    protected $serial;
91
92
    /**
93
     * The name of the file to be imported.
94
     *
95
     * @var string
96
     */
97
    protected $filename;
98
99
    /**
100
     * Array with the subject's observers.
101
     *
102
     * @var array
103
     */
104
    protected $observers = array();
105
106
    /**
107
     * Array with the subject's callbacks.
108
     *
109
     * @var array
110
     */
111
    protected $callbacks = array();
112
113
    /**
114
     * The subject's callback mappings.
115
     *
116
     * @var array
117
     */
118
    protected $callbackMappings = array();
119
120
    /**
121
     * Contain's the column names from the header line.
122
     *
123
     * @var array
124
     */
125
    protected $headers = array();
126
127
    /**
128
     * The actual line number.
129
     *
130
     * @var integer
131
     */
132
    protected $lineNumber = 0;
133
134
    /**
135
     * The actual operation name.
136
     *
137
     * @var string
138
     */
139
    protected $operationName ;
140
141
    /**
142
     * The flag that stop's overserver execution on the actual row.
143
     *
144
     * @var boolean
145
     */
146
    protected $skipRow = false;
147
148
    /**
149
     * The available root categories.
150
     *
151
     * @var array
152
     */
153
    protected $rootCategories = array();
154
155
    /**
156
     * The Magento configuration.
157
     *
158
     * @var array
159
     */
160
    protected $coreConfigData = array();
161
162
    /**
163
     * The available stores.
164
     *
165
     * @var array
166
     */
167
    protected $stores = array();
168
169
    /**
170
     * The default store.
171
     *
172
     * @var array
173
     */
174
    protected $defaultStore;
175
176
    /**
177
     * The store view code the create the product/attributes for.
178
     *
179
     * @var string
180
     */
181
    protected $storeViewCode;
182
183
    /**
184
     * The UID generator for the core config data.
185
     *
186
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
187
     */
188
    protected $coreConfigDataUidGenerator;
189
190
    /**
191
     * The actual row.
192
     *
193
     * @var array
194
     */
195
    protected $row = array();
196
197
    /**
198
     * Initialize the subject instance.
199
     *
200
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
201
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
202
     * @param array                                                    $systemLoggers              The array with the system loggers instances
203
     */
204
    public function __construct(
205
        RegistryProcessorInterface $registryProcessor,
206
        GeneratorInterface $coreConfigDataUidGenerator,
207
        array $systemLoggers
208
    ) {
209
        $this->systemLoggers = $systemLoggers;
210
        $this->registryProcessor = $registryProcessor;
211
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
212
    }
213
214
    /**
215
     * Return's the default callback mappings.
216
     *
217
     * @return array The default callback mappings
218
     */
219
    public function getDefaultCallbackMappings()
220
    {
221
        return array();
222
    }
223
224
    /**
225
     * Return's the actual row.
226
     *
227
     * @return array The actual row
228
     */
229
    public function getRow()
230
    {
231
        return $this->row;
232
    }
233
234
    /**
235
     * Stop's observer execution on the actual row.
236
     *
237
     * @return void
238
     */
239
    public function skipRow()
240
    {
241
        $this->skipRow = true;
242
    }
243
244
    /**
245
     * Return's the actual line number.
246
     *
247
     * @return integer The line number
248
     */
249
    public function getLineNumber()
250
    {
251
        return $this->lineNumber;
252
    }
253
254
    /**
255
     * Return's the actual operation name.
256
     *
257
     * @return string
258
     */
259
    public function getOperationName()
260
    {
261
        return $this->operationName;
262
    }
263
264
    /**
265
     * Set's the array containing header row.
266
     *
267
     * @param array $headers The array with the header row
268
     *
269
     * @return void
270
     */
271
    public function setHeaders(array $headers)
272
    {
273
        $this->headers = $headers;
274
    }
275
276
    /**
277
     * Return's the array containing header row.
278
     *
279
     * @return array The array with the header row
280
     */
281
    public function getHeaders()
282
    {
283
        return $this->headers;
284
    }
285
286
    /**
287
     * Queries whether or not the header with the passed name is available.
288
     *
289
     * @param string $name The header name to query
290
     *
291
     * @return boolean TRUE if the header is available, else FALSE
292
     */
293
    public function hasHeader($name)
294
    {
295
        return isset($this->headers[$name]);
296
    }
297
298
    /**
299
     * Return's the header value for the passed name.
300
     *
301
     * @param string $name The name of the header to return the value for
302
     *
303
     * @return mixed The header value
304
     * \InvalidArgumentException Is thrown, if the header with the passed name is NOT available
305
     */
306
    public function getHeader($name)
307
    {
308
309
        // query whether or not, the header is available
310
        if (isset($this->headers[$name])) {
311
            return $this->headers[$name];
312
        }
313
314
        // throw an exception, if not
315
        throw new \InvalidArgumentException(sprintf('Header %s is not available', $name));
316
    }
317
318
    /**
319
     * Add's the header with the passed name and position, if not NULL.
320
     *
321
     * @param string $name The header name to add
322
     *
323
     * @return integer The new headers position
324
     */
325
    public function addHeader($name)
326
    {
327
328
        // add the header
329
        $this->headers[$name] = $position = sizeof($this->headers);
330
331
        // return the new header's position
332
        return $position;
333
    }
334
335
    /**
336
     * Query whether or not a value for the column with the passed name exists.
337
     *
338
     * @param string $name The column name to query for a valid value
339
     *
340
     * @return boolean TRUE if the value is set, else FALSE
341
     */
342 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...
343
    {
344
345
        // query whether or not the header is available
346
        if ($this->hasHeader($name)) {
347
            // load the key for the row
348
            $headerValue = $this->getHeader($name);
349
350
            // query whether the rows column has a vaild value
351
            return (isset($this->row[$headerValue]) && $this->row[$headerValue] != '');
352
        }
353
354
        // return FALSE if not
355
        return false;
356
    }
357
358
    /**
359
     * Set the value in the passed column name.
360
     *
361
     * @param string $name  The column name to set the value for
362
     * @param mixed  $value The value to set
363
     *
364
     * @return void
365
     */
366
    public function setValue($name, $value)
367
    {
368
        $this->row[$this->getHeader($name)] = $value;
369
    }
370
371
    /**
372
     * Resolve's the value with the passed colum name from the actual row. If a callback will
373
     * be passed, the callback will be invoked with the found value as parameter. If
374
     * the value is NULL or empty, the default value will be returned.
375
     *
376
     * @param string        $name     The name of the column to return the value for
377
     * @param mixed|null    $default  The default value, that has to be returned, if the row's value is empty
378
     * @param callable|null $callback The callback that has to be invoked on the value, e. g. to format it
379
     *
380
     * @return mixed|null The, almost formatted, value
381
     */
382 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...
383
    {
384
385
        // initialize the value
386
        $value = null;
387
388
        // query whether or not the header is available
389
        if ($this->hasHeader($name)) {
390
            // load the header value
391
            $headerValue = $this->getHeader($name);
392
            // query wheter or not, the value with the requested key is available
393
            if ((isset($this->row[$headerValue]) && $this->row[$headerValue] != '')) {
394
                $value = $this->row[$headerValue];
395
            }
396
        }
397
398
        // query whether or not, a callback has been passed
399
        if ($value != null && is_callable($callback)) {
400
            $value = call_user_func($callback, $value);
401
        }
402
403
        // query whether or not
404
        if ($value == null && $default !== null) {
405
            $value = $default;
406
        }
407
408
        // return the value
409
        return $value;
410
    }
411
412
    /**
413
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
414
     * If the passed value is NOT a valid date, NULL will be returned.
415
     *
416
     * @param string|null $value The value to format
417
     *
418
     * @return string The formatted date
419
     */
420
    public function formatDate($value)
421
    {
422
423
        // create a DateTime instance from the passed value
424
        if ($dateTime = \DateTime::createFromFormat($this->getSourceDateFormat(), $value)) {
425
            return $dateTime->format('Y-m-d H:i:s');
426
        }
427
428
        // return NULL, if the passed value is NOT a valid date
429
        return null;
430
    }
431
432
    /**
433
     * Extracts the elements of the passed value by exploding them
434
     * with the also passed delimiter.
435
     *
436
     * @param string      $value     The value to extract
437
     * @param string|null $delimiter The delimiter used to extrace the elements
438
     *
439
     * @return array The exploded values
440
     */
441
    public function explode($value, $delimiter = null)
442
    {
443
        // load the global configuration
444
        $configuration = $this->getConfiguration();
445
446
        // initializet delimiter, enclosure and escape char
447
        $delimiter = $delimiter ? $delimiter : $configuration->getDelimiter();
448
        $enclosure = $configuration->getEnclosure();
449
        $escape = $configuration->getEscape();
450
451
        // parse and return the found data as array
452
        return str_getcsv($value, $delimiter, $enclosure, $escape);
453
    }
454
455
    /**
456
     * Queries whether or not debug mode is enabled or not, default is TRUE.
457
     *
458
     * @return boolean TRUE if debug mode is enabled, else FALSE
459
     */
460
    public function isDebugMode()
461
    {
462
        return $this->getConfiguration()->isDebugMode();
463
    }
464
465
    /**
466
     * Set's the subject configuration.
467
     *
468
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration
469
     *
470
     * @return void
471
     */
472
    public function setConfiguration(SubjectConfigurationInterface $configuration)
473
    {
474
        $this->configuration = $configuration;
475
    }
476
477
    /**
478
     * Return's the subject configuration.
479
     *
480
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
481
     */
482
    public function getConfiguration()
483
    {
484
        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...
485
    }
486
487
    /**
488
     * Return's the logger with the passed name, by default the system logger.
489
     *
490
     * @param string $name The name of the requested system logger
491
     *
492
     * @return \Psr\Log\LoggerInterface The logger instance
493
     * @throws \Exception Is thrown, if the requested logger is NOT available
494
     */
495
    public function getSystemLogger($name = LoggerKeys::SYSTEM)
496
    {
497
498
        // query whether or not, the requested logger is available
499
        if (isset($this->systemLoggers[$name])) {
500
            return $this->systemLoggers[$name];
501
        }
502
503
        // throw an exception if the requested logger is NOT available
504
        throw new \Exception(sprintf('The requested logger \'%s\' is not available', $name));
505
    }
506
507
    /**
508
     * Set's the import adapter instance.
509
     *
510
     * @param \TechDivision\Import\Adapter\ImportAdapterInterface $importAdapter The import adapter instance
511
     *
512
     * @return void
513
     */
514
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
515
    {
516
        $this->importAdapter = $importAdapter;
0 ignored issues
show
Documentation Bug introduced by
It seems like $importAdapter of type object<TechDivision\Impo...ImportAdapterInterface> is incompatible with the declared type object<TechDivision\Impo...apter\AdapterInterface> of property $importAdapter.

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...
517
    }
518
519
    /**
520
     * Return's the import adapter instance.
521
     *
522
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
523
     */
524
    public function getImportAdapter()
525
    {
526
        return $this->importAdapter;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->importAdapter; (TechDivision\Import\Adapter\AdapterInterface) is incompatible with the return type declared by the interface TechDivision\Import\Subj...rface::getImportAdapter of type TechDivision\Import\Adapter\ImportAdapterInterface.

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...
527
    }
528
529
    /**
530
     * Return's the array with the system logger instances.
531
     *
532
     * @return array The logger instance
533
     */
534
    public function getSystemLoggers()
535
    {
536
        return $this->systemLoggers;
537
    }
538
539
    /**
540
     * Return's the RegistryProcessor instance to handle the running threads.
541
     *
542
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
543
     */
544
    public function getRegistryProcessor()
545
    {
546
        return $this->registryProcessor;
547
    }
548
549
    /**
550
     * Set's the unique serial for this import process.
551
     *
552
     * @param string $serial The unique serial
553
     *
554
     * @return void
555
     */
556
    public function setSerial($serial)
557
    {
558
        $this->serial = $serial;
559
    }
560
561
    /**
562
     * Return's the unique serial for this import process.
563
     *
564
     * @return string The unique serial
565
     */
566
    public function getSerial()
567
    {
568
        return $this->serial;
569
    }
570
571
    /**
572
     * Set's the name of the file to import
573
     *
574
     * @param string $filename The filename
575
     *
576
     * @return void
577
     */
578
    public function setFilename($filename)
579
    {
580
        $this->filename = $filename;
581
    }
582
583
    /**
584
     * Return's the name of the file to import.
585
     *
586
     * @return string The filename
587
     */
588
    public function getFilename()
589
    {
590
        return $this->filename;
591
    }
592
593
    /**
594
     * Return's the source date format to use.
595
     *
596
     * @return string The source date format
597
     */
598
    public function getSourceDateFormat()
599
    {
600
        return $this->getConfiguration()->getSourceDateFormat();
601
    }
602
603
    /**
604
     * Return's the multiple field delimiter character to use, default value is comma (,).
605
     *
606
     * @return string The multiple field delimiter character
607
     */
608
    public function getMultipleFieldDelimiter()
609
    {
610
        return $this->getConfiguration()->getMultipleFieldDelimiter();
611
    }
612
613
    /**
614
     * Return's the multiple value delimiter character to use, default value is comma (|).
615
     *
616
     * @return string The multiple value delimiter character
617
     */
618
    public function getMultipleValueDelimiter()
619
    {
620
        return $this->getConfiguration()->getMultipleValueDelimiter();
621
    }
622
623
    /**
624
     * Return's the initialized PDO connection.
625
     *
626
     * @return \PDO The initialized PDO connection
627
     */
628
    public function getConnection()
629
    {
630
        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...
631
    }
632
633
    /**
634
     * Intializes the previously loaded global data for exactly one bunch.
635
     *
636
     * @param string $serial The serial of the actual import
637
     *
638
     * @return void
639
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
640
     */
641
    public function setUp($serial)
642
    {
643
644
        // load the status of the actual import
645
        $status = $this->getRegistryProcessor()->getAttribute($serial);
646
647
        // load the global data we've prepared initially
648
        $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
649
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
650
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
651
        $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
652
653
        // initialize the operation name
654
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();
655
656
        // merge the callback mappings with the mappings from the child instance
657
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
658
659
        // merge the callback mappings the the one from the configuration file
660
        foreach ($this->getConfiguration()->getCallbacks() as $callbackMappings) {
661
            foreach ($callbackMappings as $attributeCode => $mappings) {
662
                // write a log message, that default callback configuration will
663
                // be overwritten with the one from the configuration file
664
                if (isset($this->callbackMappings[$attributeCode])) {
665
                    $this->getSystemLogger()->notice(
666
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
667
                    );
668
                }
669
670
                // override the attributes callbacks
671
                $this->callbackMappings[$attributeCode] = $mappings;
672
            }
673
        }
674
    }
675
676
    /**
677
     * Clean up the global data after importing the variants.
678
     *
679
     * @param string $serial The serial of the actual import
680
     *
681
     * @return void
682
     */
683
    public function tearDown($serial)
684
    {
685
686
        // load the registry processor
687
        $registryProcessor = $this->getRegistryProcessor();
688
689
        // update the source directory for the next subject
690
        $registryProcessor->mergeAttributesRecursive(
691
            $serial,
692
            array(RegistryKeys::SOURCE_DIRECTORY => $this->getNewSourceDir($serial))
693
        );
694
695
        // log a debug message with the new source directory
696
        $this->getSystemLogger()->debug(
697
            sprintf('Subject %s successfully updated source directory to %s', __CLASS__, $this->getNewSourceDir($serial))
698
        );
699
    }
700
701
    /**
702
     * Return's the next source directory, which will be the target directory
703
     * of this subject, in most cases.
704
     *
705
     * @param string $serial The serial of the actual import
706
     *
707
     * @return string The new source directory
708
     */
709
    protected function getNewSourceDir($serial)
710
    {
711
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $serial);
712
    }
713
714
    /**
715
     * Register the passed observer with the specific type.
716
     *
717
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
718
     * @param string                                           $type     The type to register the observer with
719
     *
720
     * @return void
721
     */
722
    public function registerObserver(ObserverInterface $observer, $type)
723
    {
724
725
        // query whether or not the array with the callbacks for the
726
        // passed type has already been initialized, or not
727
        if (!isset($this->observers[$type])) {
728
            $this->observers[$type] = array();
729
        }
730
731
        // append the callback with the instance of the passed type
732
        $this->observers[$type][] = $observer;
733
    }
734
735
    /**
736
     * Register the passed callback with the specific type.
737
     *
738
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
739
     * @param string                                           $type     The type to register the callback with
740
     *
741
     * @return void
742
     */
743
    public function registerCallback(CallbackInterface $callback, $type)
744
    {
745
746
        // query whether or not the array with the callbacks for the
747
        // passed type has already been initialized, or not
748
        if (!isset($this->callbacks[$type])) {
749
            $this->callbacks[$type] = array();
750
        }
751
752
        // append the callback with the instance of the passed type
753
        $this->callbacks[$type][] = $callback;
754
    }
755
756
    /**
757
     * Return's the array with callbacks for the passed type.
758
     *
759
     * @param string $type The type of the callbacks to return
760
     *
761
     * @return array The callbacks
762
     */
763
    public function getCallbacksByType($type)
764
    {
765
766
        // initialize the array for the callbacks
767
        $callbacks = array();
768
769
        // query whether or not callbacks for the type are available
770
        if (isset($this->callbacks[$type])) {
771
            $callbacks = $this->callbacks[$type];
772
        }
773
774
        // return the array with the type's callbacks
775
        return $callbacks;
776
    }
777
778
    /**
779
     * Return's the array with the available observers.
780
     *
781
     * @return array The observers
782
     */
783
    public function getObservers()
784
    {
785
        return $this->observers;
786
    }
787
788
    /**
789
     * Return's the array with the available callbacks.
790
     *
791
     * @return array The callbacks
792
     */
793
    public function getCallbacks()
794
    {
795
        return $this->callbacks;
796
    }
797
798
    /**
799
     * Return's the callback mappings for this subject.
800
     *
801
     * @return array The array with the subject's callback mappings
802
     */
803
    public function getCallbackMappings()
804
    {
805
        return $this->callbackMappings;
806
    }
807
808
    /**
809
     * Imports the content of the file with the passed filename.
810
     *
811
     *
812
     * @param string $serial   The serial of the actual import
813
     * @param string $filename The filename to process
814
     *
815
     * @return void
816
     * @throws \Exception Is thrown, if the import can't be processed
817
     */
818
    public function import($serial, $filename)
819
    {
820
821
        try {
822
            // stop processing, if the filename doesn't match
823
            if (!$this->match($filename)) {
824
                return;
825
            }
826
827
            // load the system logger instance
828
            $systemLogger = $this->getSystemLogger();
829
830
            // prepare the flag filenames
831
            $inProgressFilename = sprintf('%s.inProgress', $filename);
832
            $importedFilename = sprintf('%s.imported', $filename);
833
            $failedFilename = sprintf('%s.failed', $filename);
834
835
            // query whether or not the file has already been imported
836
            if (is_file($failedFilename) ||
837
                is_file($importedFilename) ||
838
                is_file($inProgressFilename)
839
            ) {
840
                // log a debug message and exit
841
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
842
                return;
843
            }
844
845
            // flag file as in progress
846
            touch($inProgressFilename);
847
848
            // track the start time
849
            $startTime = microtime(true);
850
851
            // initialize the serial/filename
852
            $this->setSerial($serial);
853
            $this->setFilename($filename);
854
855
            // log a message that the file has to be imported
856
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
857
858
            // let the adapter process the file
859
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
860
861
            // track the time needed for the import in seconds
862
            $endTime = microtime(true) - $startTime;
863
864
            // log a message that the file has successfully been imported
865
            $systemLogger->debug(sprintf('Successfully imported file %s in %f s', $filename, $endTime));
866
867
            // rename flag file, because import has been successfull
868
            rename($inProgressFilename, $importedFilename);
869
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
870
        } catch (\Exception $e) {
871
            // rename the flag file, because import failed and write the stack trace
872
            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...
873
            file_put_contents($failedFilename, $e->__toString());
874
875
            // do not wrap the exception if not already done
876
            if ($e instanceof WrappedColumnException) {
877
                throw $e;
878
            }
879
880
            // else wrap and throw the exception
881
            throw $this->wrapException(array(), $e);
882
        }
883
    }
884
885
    /**
886
     * This method queries whether or not the passed filename matches
887
     * the pattern, based on the subjects configured prefix.
888
     *
889
     * @param string $filename The filename to match
890
     *
891
     * @return boolean TRUE if the filename matches, else FALSE
892
     */
893
    protected function match($filename)
894
    {
895
896
        // prepare the pattern to query whether the file has to be processed or not
897
        $pattern = sprintf('/^.*\/%s.*\\.csv$/', $this->getConfiguration()->getPrefix());
898
899
        // stop processing, if the filename doesn't match
900
        return (boolean) preg_match($pattern, $filename);
901
    }
902
903
    /**
904
     * Imports the passed row into the database. If the import failed, the exception
905
     * will be catched and logged, but the import process will be continued.
906
     *
907
     * @param array $row The row with the data to be imported
908
     *
909
     * @return void
910
     */
911
    public function importRow(array $row)
912
    {
913
914
        // initialize the row
915
        $this->row = $row;
916
917
        // raise the line number and reset the skip row flag
918
        $this->lineNumber++;
919
        $this->skipRow = false;
920
921
        // initialize the headers with the columns from the first line
922
        if (sizeof($this->headers) === 0) {
923
            foreach ($this->row as $value => $key) {
924
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
925
            }
926
            return;
927
        }
928
929
        // process the observers
930
        foreach ($this->getObservers() as $observers) {
931
            // invoke the pre-import/import and post-import observers
932
            foreach ($observers as $observer) {
933
                // query whether or not we have to skip the row
934
                if ($this->skipRow) {
935
                    break;
936
                }
937
938
                // if not, set the subject and process the observer
939
                if ($observer instanceof ObserverInterface) {
940
                    $this->row = $observer->handle($this);
941
                }
942
            }
943
        }
944
945
        // log a debug message with the actual line nr/file information
946
        $this->getSystemLogger()->debug(
947
            sprintf(
948
                'Successfully processed row (operation: %s) in file %s on line %d',
949
                $this->operationName,
950
                $this->filename,
951
                $this->lineNumber
952
            )
953
        );
954
    }
955
956
    /**
957
     * Map the passed attribute code, if a header mapping exists and return the
958
     * mapped mapping.
959
     *
960
     * @param string $attributeCode The attribute code to map
961
     *
962
     * @return string The mapped attribute code, or the original one
963
     */
964
    public function mapAttributeCodeByHeaderMapping($attributeCode)
965
    {
966
967
        // load the header mappings
968
        $headerMappings = $this->getHeaderMappings();
969
970
        // query weather or not we've a mapping, if yes, map the attribute code
971
        if (isset($headerMappings[$attributeCode])) {
972
            $attributeCode = $headerMappings[$attributeCode];
973
        }
974
975
        // return the (mapped) attribute code
976
        return $attributeCode;
977
    }
978
979
    /**
980
     * Queries whether or not that the subject needs an OK file to be processed.
981
     *
982
     * @return boolean TRUE if the subject needs an OK file, else FALSE
983
     */
984
    public function isOkFileNeeded()
985
    {
986
        return $this->getConfiguration()->isOkFileNeeded();
987
    }
988
989
    /**
990
     * Return's the default store.
991
     *
992
     * @return array The default store
993
     */
994
    public function getDefaultStore()
995
    {
996
        return $this->defaultStore;
997
    }
998
999
    /**
1000
     * Set's the store view code the create the product/attributes for.
1001
     *
1002
     * @param string $storeViewCode The store view code
1003
     *
1004
     * @return void
1005
     */
1006
    public function setStoreViewCode($storeViewCode)
1007
    {
1008
        $this->storeViewCode = $storeViewCode;
1009
    }
1010
1011
    /**
1012
     * Return's the store view code the create the product/attributes for.
1013
     *
1014
     * @param string|null $default The default value to return, if the store view code has not been set
1015
     *
1016
     * @return string The store view code
1017
     */
1018
    public function getStoreViewCode($default = null)
1019
    {
1020
1021
        // return the store view code, if available
1022
        if ($this->storeViewCode != null) {
1023
            return $this->storeViewCode;
1024
        }
1025
1026
        // if NOT and a default code is available
1027
        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...
1028
            // return the default value
1029
            return $default;
1030
        }
1031
    }
1032
1033
    /**
1034
     * Return's the store ID of the store with the passed store view code
1035
     *
1036
     * @param string $storeViewCode The store view code to return the store ID for
1037
     *
1038
     * @return integer The ID of the store with the passed ID
1039
     * @throws \Exception Is thrown, if the store with the actual code is not available
1040
     */
1041
    public function getStoreId($storeViewCode)
1042
    {
1043
1044
        // query whether or not, the requested store is available
1045
        if (isset($this->stores[$storeViewCode])) {
1046
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
1047
        }
1048
1049
        // throw an exception, if not
1050
        throw new \Exception(
1051
            sprintf(
1052
                'Found invalid store view code %s in file %s on line %d',
1053
                $storeViewCode,
1054
                $this->getFilename(),
1055
                $this->getLineNumber()
1056
            )
1057
        );
1058
    }
1059
1060
    /**
1061
     * Return's the store ID of the actual row, or of the default store
1062
     * if no store view code is set in the CSV file.
1063
     *
1064
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
1065
     *
1066
     * @return integer The ID of the actual store
1067
     * @throws \Exception Is thrown, if the store with the actual code is not available
1068
     */
1069
    public function getRowStoreId($default = null)
1070
    {
1071
1072
        // initialize the default store view code, if not passed
1073
        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...
1074
            $defaultStore = $this->getDefaultStore();
1075
            $default = $defaultStore[MemberNames::CODE];
1076
        }
1077
1078
        // load the store view code the create the product/attributes for
1079
        return $this->getStoreId($this->getStoreViewCode($default));
1080
    }
1081
1082
    /**
1083
     * Prepare's the store view code in the subject.
1084
     *
1085
     * @return void
1086
     */
1087
    public function prepareStoreViewCode()
1088
    {
1089
1090
        // re-set the store view code
1091
        $this->setStoreViewCode(null);
1092
1093
        // initialize the store view code
1094
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
1095
            $this->setStoreViewCode($storeViewCode);
1096
        }
1097
    }
1098
1099
    /**
1100
     * Return's the root category for the actual view store.
1101
     *
1102
     * @return array The store's root category
1103
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
1104
     */
1105
    public function getRootCategory()
1106
    {
1107
1108
        // load the default store
1109
        $defaultStore = $this->getDefaultStore();
1110
1111
        // load the actual store view code
1112
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
1113
1114
        // query weather or not we've a root category or not
1115
        if (isset($this->rootCategories[$storeViewCode])) {
1116
            return $this->rootCategories[$storeViewCode];
1117
        }
1118
1119
        // throw an exception if the root category is NOT available
1120
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1121
    }
1122
1123
    /**
1124
     * Return's the Magento configuration value.
1125
     *
1126
     * @param string  $path    The Magento path of the requested configuration value
1127
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1128
     * @param string  $scope   The scope the configuration value has been set
1129
     * @param integer $scopeId The scope ID the configuration value has been set
1130
     *
1131
     * @return mixed The configuration value
1132
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1133
     */
1134
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1135
    {
1136
1137
        // initialize the core config data
1138
        $coreConfigData = array(
1139
            MemberNames::PATH => $path,
1140
            MemberNames::SCOPE => $scope,
1141
            MemberNames::SCOPE_ID => $scopeId
1142
        );
1143
1144
        // generate the UID from the passed data
1145
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1146
1147
        // iterate over the core config data and try to find the requested configuration value
1148
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1149
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1150
        }
1151
1152
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1153
        if ($scope === ScopeKeys::SCOPE_STORES && isset($this->stores[$scopeId])) {
1154
            // replace scope with 'websites' and website ID
1155
            $coreConfigData = array_merge(
1156
                $coreConfigData,
1157
                array(
1158
                    MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1159
                    MemberNames::SCOPE_ID => $this->stores[$scopeId][MemberNames::WEBSITE_ID]
1160
                )
1161
            );
1162
1163
            // generate the UID from the passed data, merged with the 'websites' scope and ID
1164
            $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1165
1166
            // query whether or not, the configuration value on 'websites' level
1167 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...
1168
                return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1169
            }
1170
        }
1171
1172
        // replace scope with 'default' and scope ID '0'
1173
        $coreConfigData = array_merge(
1174
            $coreConfigData,
1175
            array(
1176
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1177
                MemberNames::SCOPE_ID => 0
1178
            )
1179
        );
1180
1181
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1182
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1183
1184
        // query whether or not, the configuration value on 'default' level
1185 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...
1186
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1187
        }
1188
1189
        // if not, return the passed default value
1190
        if ($default !== null) {
1191
            return $default;
1192
        }
1193
1194
        // throw an exception if no value can be found
1195
        // in the Magento configuration
1196
        throw new \Exception(
1197
            sprintf(
1198
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1199
                $path,
1200
                $scope,
1201
                $scopeId
1202
            )
1203
        );
1204
    }
1205
1206
    /**
1207
     * Resolve the original column name for the passed one.
1208
     *
1209
     * @param string $columnName The column name that has to be resolved
1210
     *
1211
     * @return string|null The original column name
1212
     */
1213
    public function resolveOriginalColumnName($columnName)
1214
    {
1215
1216
        // try to load the original data
1217
        $originalData = $this->getOriginalData();
1218
1219
        // query whether or not original data is available
1220
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1221
            // query whether or not the original column name is available
1222
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1223
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1224
            }
1225
1226
            // query whether or a wildcard column name is available
1227
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1228
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1229
            }
1230
        }
1231
1232
        // return the original column name
1233
        return $columnName;
1234
    }
1235
1236
    /**
1237
     * Return's the original data if available, or an empty array.
1238
     *
1239
     * @return array The original data
1240
     */
1241
    public function getOriginalData()
1242
    {
1243
1244
        // initialize the array for the original data
1245
        $originalData = array();
1246
1247
        // query whether or not the column contains original data
1248
        if ($this->hasOriginalData()) {
1249
            // unerialize the original data from the column
1250
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1251
        }
1252
1253
        // return an empty array, if not
1254
        return $originalData;
1255
    }
1256
1257
    /**
1258
     * Query's whether or not the actual column contains original data like
1259
     * filename, line number and column names.
1260
     *
1261
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1262
     */
1263
    public function hasOriginalData()
1264
    {
1265
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1266
    }
1267
1268
    /**
1269
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1270
     * line number and column names and use it for a detailed exception message.
1271
     *
1272
     * @param array      $columnNames The column names that should be resolved and wrapped
1273
     * @param \Exception $parent      The exception we want to wrap
1274
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1275
     *
1276
     * @return \Exception the wrapped exception
1277
     */
1278
    public function wrapException(
1279
        array $columnNames = array(),
1280
        \Exception $parent = null,
1281
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1282
    ) {
1283
1284
        // initialize the message
1285
        $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...
1286
1287
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1288
        if ($this->hasOriginalData()) {
1289
            // load the original data
1290
            $originalData = $this->getOriginalData();
1291
1292
            // replace old filename and line number of the original message
1293
            $message = $this->appendExceptionSuffix(
1294
                $this->stripExceptionSuffix($message),
1295
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1296
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1297
            );
1298
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
1299
        } else {
1300
            // append filename and line number to the original message
1301
            $message = $this->appendExceptionSuffix(
1302
                $this->stripExceptionSuffix($message),
1303
                $this->filename,
1304
                $this->lineNumber
1305
            );
1306
        }
1307
1308
        // query whether or not, column names has been passed
1309
        if (sizeof($columnNames) > 0) {
1310
            // prepare the original column names
1311
            $originalColumnNames = array();
1312
            foreach ($columnNames as $columnName) {
1313
                $originalColumnNames = $this->resolveOriginalColumnName($columnName);
1314
            }
1315
1316
            // append the column information
1317
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1318
        }
1319
1320
        // create a new exception and wrap the parent one
1321
        return new $className($message, null, $parent);
1322
    }
1323
1324
    /**
1325
     * Strip's the exception suffix containing filename and line number from the
1326
     * passed message.
1327
     *
1328
     * @param string $message The message to strip the exception suffix from
1329
     *
1330
     * @return mixed The message without the exception suffix
1331
     */
1332
    public function stripExceptionSuffix($message)
1333
    {
1334
        return str_replace($this->appendExceptionSuffix(), '', $message);
1335
    }
1336
1337
    /**
1338
     * Append's the exception suffix containing filename and line number to the
1339
     * passed message. If no message has been passed, only the suffix will be
1340
     * returned
1341
     *
1342
     * @param string|null $message    The message to append the exception suffix to
1343
     * @param string|null $filename   The filename used to create the suffix
1344
     * @param string|null $lineNumber The line number used to create the suffx
1345
     *
1346
     * @return string The message with the appended exception suffix
1347
     */
1348
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1349
    {
1350
1351
        // query whether or not a filename has been passed
1352
        if ($filename === null) {
1353
            $filename = $this->getFilename();
1354
        }
1355
1356
        // query whether or not a line number has been passed
1357
        if ($lineNumber === null) {
1358
            $lineNumber = $this->getLineNumber();
1359
        }
1360
1361
        // if no message has been passed, only return the suffix
1362
        if ($message === null) {
1363
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1364
        }
1365
1366
        // concatenate the message with the suffix and return it
1367
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1368
    }
1369
1370
    /**
1371
     * Raises the value for the counter with the passed key by one.
1372
     *
1373
     * @param mixed $counterName The name of the counter to raise
1374
     *
1375
     * @return integer The counter's new value
1376
     */
1377
    public function raiseCounter($counterName)
1378
    {
1379
1380
        // raise the counter with the passed name
1381
        return $this->getRegistryProcessor()->raiseCounter(
1382
            $this->getSerial(),
1383
            $counterName
1384
        );
1385
    }
1386
1387
    /**
1388
     * Merge the passed array into the status of the actual import.
1389
     *
1390
     * @param array $status The status information to be merged
1391
     *
1392
     * @return void
1393
     */
1394
    public function mergeAttributesRecursive(array $status)
1395
    {
1396
1397
        // merge the passed status
1398
        $this->getRegistryProcessor()->mergeAttributesRecursive(
1399
            $this->getSerial(),
1400
            $status
1401
        );
1402
    }
1403
}
1404