Completed
Pull Request — master (#89)
by Tim
05:27
created

AbstractSubject   D

Complexity

Total Complexity 117

Size/Duplication

Total Lines 1361
Duplicated Lines 3.67 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 36.84%

Importance

Changes 0
Metric Value
wmc 117
lcom 2
cbo 8
dl 50
loc 1361
ccs 119
cts 323
cp 0.3684
rs 4.4102
c 0
b 0
f 0

62 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getDefaultCallbackMappings() 0 4 1
A getRow() 0 4 1
A skipRow() 0 4 1
A getLineNumber() 0 4 1
A getOperationName() 0 4 1
A setHeaders() 0 4 1
A getHeaders() 0 4 1
A hasHeader() 0 4 1
A getHeader() 0 11 2
A addHeader() 0 9 1
A hasValue() 15 15 3
A setValue() 0 4 1
C getValue() 29 29 8
A formatDate() 0 11 2
A explode() 0 13 2
A isDebugMode() 0 4 1
A setConfiguration() 0 4 1
A getConfiguration() 0 4 1
A getSystemLogger() 0 11 2
A setImportAdapter() 0 4 1
A getImportAdapter() 0 4 1
A getSystemLoggers() 0 4 1
A getRegistryProcessor() 0 4 1
A setSerial() 0 4 1
A getSerial() 0 4 1
A setFilename() 0 4 1
A getFilename() 0 4 1
A getSourceDateFormat() 0 4 1
A getMultipleFieldDelimiter() 0 4 1
A getMultipleValueDelimiter() 0 4 1
A getConnection() 0 4 1
B setUp() 0 34 4
A tearDown() 0 17 1
A getNewSourceDir() 0 4 1
A registerObserver() 0 12 2
A registerCallback() 0 12 2
A getCallbacksByType() 0 14 2
A getObservers() 0 4 1
A getCallbacks() 0 4 1
A getCallbackMappings() 0 4 1
B import() 0 66 7
A match() 0 13 1
C importRow() 0 44 7
A mapAttributeCodeByHeaderMapping() 0 14 2
A isOkFileNeeded() 0 4 1
A getDefaultStore() 0 4 1
A setStoreViewCode() 0 4 1
A getStoreViewCode() 0 14 3
A getStoreId() 0 18 2
A getRowStoreId() 0 12 2
A prepareStoreViewCode() 0 11 2
A getRootCategory() 0 17 2
C getCoreConfigData() 6 71 7
B resolveOriginalColumnName() 0 22 4
A getOriginalData() 0 15 2
A hasOriginalData() 0 4 2
B wrapException() 0 45 4
A stripExceptionSuffix() 0 4 1
A appendExceptionSuffix() 0 21 4
A raiseCounter() 0 9 1
A mergeAttributesRecursive() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

Complex classes like AbstractSubject often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractSubject, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * TechDivision\Import\Subjects\AbstractSubject
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Subjects;
22
23
use 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 13
    public function __construct(
205
        RegistryProcessorInterface $registryProcessor,
206
        GeneratorInterface $coreConfigDataUidGenerator,
207
        array $systemLoggers
208
    ) {
209 13
        $this->systemLoggers = $systemLoggers;
210 13
        $this->registryProcessor = $registryProcessor;
211 13
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
212 13
    }
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 1
    public function getLineNumber()
250
    {
251 1
        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 3
    public function setHeaders(array $headers)
272
    {
273 3
        $this->headers = $headers;
274 3
    }
275
276
    /**
277
     * Return's the array containing header row.
278
     *
279
     * @return array The array with the header row
280
     */
281 1
    public function getHeaders()
282
    {
283 1
        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 6
    public function hasHeader($name)
294
    {
295 6
        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 5
    public function getHeader($name)
307
    {
308
309
        // query whether or not, the header is available
310 5
        if (isset($this->headers[$name])) {
311 4
            return $this->headers[$name];
312
        }
313
314
        // throw an exception, if not
315 1
        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 2
    public function addHeader($name)
326
    {
327
328
        // add the header
329 2
        $this->headers[$name] = $position = sizeof($this->headers);
330
331
        // return the new header's position
332 2
        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 2 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 2
        if ($this->hasHeader($name)) {
347
            // load the key for the row
348 1
            $headerValue = $this->getHeader($name);
349
350
            // query whether the rows column has a vaild value
351 1
            return (isset($this->row[$headerValue]) && $this->row[$headerValue] != '');
352
        }
353
354
        // return FALSE if not
355 1
        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 2
    public function setValue($name, $value)
367
    {
368 2
        $this->row[$this->getHeader($name)] = $value;
369 2
    }
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 3 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 3
        $value = null;
387
388
        // query whether or not the header is available
389 3
        if ($this->hasHeader($name)) {
390
            // load the header value
391 2
            $headerValue = $this->getHeader($name);
392
            // query wheter or not, the value with the requested key is available
393 2
            if ((isset($this->row[$headerValue]) && $this->row[$headerValue] != '')) {
394 2
                $value = $this->row[$headerValue];
395
            }
396
        }
397
398
        // query whether or not, a callback has been passed
399 3
        if ($value != null && is_callable($callback)) {
400 1
            $value = call_user_func($callback, $value);
401
        }
402
403
        // query whether or not
404 3
        if ($value == null && $default !== null) {
405 1
            $value = $default;
406
        }
407
408
        // return the value
409 3
        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 $value The value to format
417
     *
418
     * @return string|null The formatted date or NULL if the date is not valid
419
     */
420 2
    public function formatDate($value)
421
    {
422
423
        // create a DateTime instance from the passed value
424 2
        if ($dateTime = \DateTime::createFromFormat($this->getSourceDateFormat(), $value)) {
425 1
            return $dateTime->format('Y-m-d H:i:s');
426
        }
427
428
        // return NULL, if the passed value is NOT a valid date
429 1
        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 13
    public function setConfiguration(SubjectConfigurationInterface $configuration)
473
    {
474 13
        $this->configuration = $configuration;
475 13
    }
476
477
    /**
478
     * Return's the subject configuration.
479
     *
480
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
481
     */
482 5
    public function getConfiguration()
483
    {
484 5
        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 2
    public function getSystemLogger($name = LoggerKeys::SYSTEM)
496
    {
497
498
        // query whether or not, the requested logger is available
499 2
        if (isset($this->systemLoggers[$name])) {
500 2
            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 2
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
515
    {
516 2
        $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 2
    }
518
519
    /**
520
     * Return's the import adapter instance.
521
     *
522
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
523
     */
524 2
    public function getImportAdapter()
525
    {
526 2
        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 2
    public function setSerial($serial)
557
    {
558 2
        $this->serial = $serial;
559 2
    }
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 2
    public function setFilename($filename)
579
    {
580 2
        $this->filename = $filename;
581 2
    }
582
583
    /**
584
     * Return's the name of the file to import.
585
     *
586
     * @return string The filename
587
     */
588 1
    public function getFilename()
589
    {
590 1
        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 2
    public function getSourceDateFormat()
599
    {
600 2
        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 3
    public function import($serial, $filename)
819
    {
820
821
        try {
822
            // stop processing, if the filename doesn't match
823 3
            if (!$this->match($filename)) {
824 1
                return;
825
            }
826
827
            // load the system logger instance
828 2
            $systemLogger = $this->getSystemLogger();
829
830
            // prepare the flag filenames
831 2
            $inProgressFilename = sprintf('%s.inProgress', $filename);
832 2
            $importedFilename = sprintf('%s.imported', $filename);
833 2
            $failedFilename = sprintf('%s.failed', $filename);
834
835
            // query whether or not the file has already been imported
836 2
            if ($this->isFile($failedFilename) ||
837 2
                $this->isFile($importedFilename) ||
838 2
                $this->isFile($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 2
            $this->touch($inProgressFilename);
847
848
            // track the start time
849 2
            $startTime = microtime(true);
850
851
            // initialize the serial/filename
852 2
            $this->setSerial($serial);
853 2
            $this->setFilename($filename);
854
855
            // log a message that the file has to be imported
856 2
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
857
858
            // let the adapter process the file
859 2
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
860
861
            // track the time needed for the import in seconds
862 1
            $endTime = microtime(true) - $startTime;
863
864
            // log a message that the file has successfully been imported
865 1
            $systemLogger->debug(sprintf('Successfully imported file %s in %f s', $filename, $endTime));
866
867
            // rename flag file, because import has been successfull
868 1
            $this->rename($inProgressFilename, $importedFilename);
869
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
870 1
        } catch (\Exception $e) {
871
            // rename the flag file, because import failed and write the stack trace
872 1
            $this->rename($inProgressFilename, $failedFilename);
0 ignored issues
show
Bug introduced by
The variable $inProgressFilename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $failedFilename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
873 1
            $this->write($failedFilename, $e->__toString());
874
875
            // do not wrap the exception if not already done
876 1
            if ($e instanceof WrappedColumnException) {
877
                throw $e;
878
            }
879
880
            // else wrap and throw the exception
881 1
            throw $this->wrapException(array(), $e);
882
        }
883 1
    }
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 3
    protected function match($filename)
894
    {
895
896
        // prepare the pattern to query whether the file has to be processed or not
897 3
        $pattern = sprintf(
898 3
            '/^.*\/%s.*\\.%s$/',
899 3
            $this->getConfiguration()->getPrefix(),
900 3
            $this->getConfiguration()->getSuffix()
901
        );
902
903
        // stop processing, if the filename doesn't match
904 3
        return (boolean) preg_match($pattern, $filename);
905
    }
906
907
    /**
908
     * Imports the passed row into the database. If the import failed, the exception
909
     * will be catched and logged, but the import process will be continued.
910
     *
911
     * @param array $row The row with the data to be imported
912
     *
913
     * @return void
914
     */
915
    public function importRow(array $row)
916
    {
917
918
        // initialize the row
919
        $this->row = $row;
920
921
        // raise the line number and reset the skip row flag
922
        $this->lineNumber++;
923
        $this->skipRow = false;
924
925
        // initialize the headers with the columns from the first line
926
        if (sizeof($this->headers) === 0) {
927
            foreach ($this->row as $value => $key) {
928
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
929
            }
930
            return;
931
        }
932
933
        // process the observers
934
        foreach ($this->getObservers() as $observers) {
935
            // invoke the pre-import/import and post-import observers
936
            foreach ($observers as $observer) {
937
                // query whether or not we have to skip the row
938
                if ($this->skipRow) {
939
                    break;
940
                }
941
942
                // if not, set the subject and process the observer
943
                if ($observer instanceof ObserverInterface) {
944
                    $this->row = $observer->handle($this);
945
                }
946
            }
947
        }
948
949
        // log a debug message with the actual line nr/file information
950
        $this->getSystemLogger()->debug(
951
            sprintf(
952
                'Successfully processed row (operation: %s) in file %s on line %d',
953
                $this->operationName,
954
                $this->filename,
955
                $this->lineNumber
956
            )
957
        );
958
    }
959
960
    /**
961
     * Map the passed attribute code, if a header mapping exists and return the
962
     * mapped mapping.
963
     *
964
     * @param string $attributeCode The attribute code to map
965
     *
966
     * @return string The mapped attribute code, or the original one
967
     */
968
    public function mapAttributeCodeByHeaderMapping($attributeCode)
969
    {
970
971
        // load the header mappings
972
        $headerMappings = $this->getHeaderMappings();
973
974
        // query weather or not we've a mapping, if yes, map the attribute code
975
        if (isset($headerMappings[$attributeCode])) {
976
            $attributeCode = $headerMappings[$attributeCode];
977
        }
978
979
        // return the (mapped) attribute code
980
        return $attributeCode;
981
    }
982
983
    /**
984
     * Queries whether or not that the subject needs an OK file to be processed.
985
     *
986
     * @return boolean TRUE if the subject needs an OK file, else FALSE
987
     */
988
    public function isOkFileNeeded()
989
    {
990
        return $this->getConfiguration()->isOkFileNeeded();
991
    }
992
993
    /**
994
     * Return's the default store.
995
     *
996
     * @return array The default store
997
     */
998
    public function getDefaultStore()
999
    {
1000
        return $this->defaultStore;
1001
    }
1002
1003
    /**
1004
     * Set's the store view code the create the product/attributes for.
1005
     *
1006
     * @param string $storeViewCode The store view code
1007
     *
1008
     * @return void
1009
     */
1010
    public function setStoreViewCode($storeViewCode)
1011
    {
1012
        $this->storeViewCode = $storeViewCode;
1013
    }
1014
1015
    /**
1016
     * Return's the store view code the create the product/attributes for.
1017
     *
1018
     * @param string|null $default The default value to return, if the store view code has not been set
1019
     *
1020
     * @return string The store view code
1021
     */
1022
    public function getStoreViewCode($default = null)
1023
    {
1024
1025
        // return the store view code, if available
1026
        if ($this->storeViewCode != null) {
1027
            return $this->storeViewCode;
1028
        }
1029
1030
        // if NOT and a default code is available
1031
        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...
1032
            // return the default value
1033
            return $default;
1034
        }
1035
    }
1036
1037
    /**
1038
     * Return's the store ID of the store with the passed store view code
1039
     *
1040
     * @param string $storeViewCode The store view code to return the store ID for
1041
     *
1042
     * @return integer The ID of the store with the passed ID
1043
     * @throws \Exception Is thrown, if the store with the actual code is not available
1044
     */
1045
    public function getStoreId($storeViewCode)
1046
    {
1047
1048
        // query whether or not, the requested store is available
1049
        if (isset($this->stores[$storeViewCode])) {
1050
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
1051
        }
1052
1053
        // throw an exception, if not
1054
        throw new \Exception(
1055
            sprintf(
1056
                'Found invalid store view code %s in file %s on line %d',
1057
                $storeViewCode,
1058
                $this->getFilename(),
1059
                $this->getLineNumber()
1060
            )
1061
        );
1062
    }
1063
1064
    /**
1065
     * Return's the store ID of the actual row, or of the default store
1066
     * if no store view code is set in the CSV file.
1067
     *
1068
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
1069
     *
1070
     * @return integer The ID of the actual store
1071
     * @throws \Exception Is thrown, if the store with the actual code is not available
1072
     */
1073
    public function getRowStoreId($default = null)
1074
    {
1075
1076
        // initialize the default store view code, if not passed
1077
        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...
1078
            $defaultStore = $this->getDefaultStore();
1079
            $default = $defaultStore[MemberNames::CODE];
1080
        }
1081
1082
        // load the store view code the create the product/attributes for
1083
        return $this->getStoreId($this->getStoreViewCode($default));
1084
    }
1085
1086
    /**
1087
     * Prepare's the store view code in the subject.
1088
     *
1089
     * @return void
1090
     */
1091
    public function prepareStoreViewCode()
1092
    {
1093
1094
        // re-set the store view code
1095
        $this->setStoreViewCode(null);
1096
1097
        // initialize the store view code
1098
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
1099
            $this->setStoreViewCode($storeViewCode);
1100
        }
1101
    }
1102
1103
    /**
1104
     * Return's the root category for the actual view store.
1105
     *
1106
     * @return array The store's root category
1107
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
1108
     */
1109
    public function getRootCategory()
1110
    {
1111
1112
        // load the default store
1113
        $defaultStore = $this->getDefaultStore();
1114
1115
        // load the actual store view code
1116
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
1117
1118
        // query weather or not we've a root category or not
1119
        if (isset($this->rootCategories[$storeViewCode])) {
1120
            return $this->rootCategories[$storeViewCode];
1121
        }
1122
1123
        // throw an exception if the root category is NOT available
1124
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1125
    }
1126
1127
    /**
1128
     * Return's the Magento configuration value.
1129
     *
1130
     * @param string  $path    The Magento path of the requested configuration value
1131
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1132
     * @param string  $scope   The scope the configuration value has been set
1133
     * @param integer $scopeId The scope ID the configuration value has been set
1134
     *
1135
     * @return mixed The configuration value
1136
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1137
     */
1138
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1139
    {
1140
1141
        // initialize the core config data
1142
        $coreConfigData = array(
1143
            MemberNames::PATH => $path,
1144
            MemberNames::SCOPE => $scope,
1145
            MemberNames::SCOPE_ID => $scopeId
1146
        );
1147
1148
        // generate the UID from the passed data
1149
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1150
1151
        // iterate over the core config data and try to find the requested configuration value
1152
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1153
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1154
        }
1155
1156
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1157
        if ($scope === ScopeKeys::SCOPE_STORES && isset($this->stores[$scopeId])) {
1158
            // replace scope with 'websites' and website ID
1159
            $coreConfigData = array_merge(
1160
                $coreConfigData,
1161
                array(
1162
                    MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1163
                    MemberNames::SCOPE_ID => $this->stores[$scopeId][MemberNames::WEBSITE_ID]
1164
                )
1165
            );
1166
1167
            // generate the UID from the passed data, merged with the 'websites' scope and ID
1168
            $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1169
1170
            // query whether or not, the configuration value on 'websites' level
1171 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...
1172
                return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1173
            }
1174
        }
1175
1176
        // replace scope with 'default' and scope ID '0'
1177
        $coreConfigData = array_merge(
1178
            $coreConfigData,
1179
            array(
1180
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1181
                MemberNames::SCOPE_ID => 0
1182
            )
1183
        );
1184
1185
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1186
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1187
1188
        // query whether or not, the configuration value on 'default' level
1189 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...
1190
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1191
        }
1192
1193
        // if not, return the passed default value
1194
        if ($default !== null) {
1195
            return $default;
1196
        }
1197
1198
        // throw an exception if no value can be found
1199
        // in the Magento configuration
1200
        throw new \Exception(
1201
            sprintf(
1202
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1203
                $path,
1204
                $scope,
1205
                $scopeId
1206
            )
1207
        );
1208
    }
1209
1210
    /**
1211
     * Resolve the original column name for the passed one.
1212
     *
1213
     * @param string $columnName The column name that has to be resolved
1214
     *
1215
     * @return string|null The original column name
1216
     */
1217
    public function resolveOriginalColumnName($columnName)
1218
    {
1219
1220
        // try to load the original data
1221
        $originalData = $this->getOriginalData();
1222
1223
        // query whether or not original data is available
1224
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1225
            // query whether or not the original column name is available
1226
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1227
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1228
            }
1229
1230
            // query whether or a wildcard column name is available
1231
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1232
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1233
            }
1234
        }
1235
1236
        // return the original column name
1237
        return $columnName;
1238
    }
1239
1240
    /**
1241
     * Return's the original data if available, or an empty array.
1242
     *
1243
     * @return array The original data
1244
     */
1245
    public function getOriginalData()
1246
    {
1247
1248
        // initialize the array for the original data
1249
        $originalData = array();
1250
1251
        // query whether or not the column contains original data
1252
        if ($this->hasOriginalData()) {
1253
            // unerialize the original data from the column
1254
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1255
        }
1256
1257
        // return an empty array, if not
1258
        return $originalData;
1259
    }
1260
1261
    /**
1262
     * Query's whether or not the actual column contains original data like
1263
     * filename, line number and column names.
1264
     *
1265
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1266
     */
1267 1
    public function hasOriginalData()
1268
    {
1269 1
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1270
    }
1271
1272
    /**
1273
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1274
     * line number and column names and use it for a detailed exception message.
1275
     *
1276
     * @param array      $columnNames The column names that should be resolved and wrapped
1277
     * @param \Exception $parent      The exception we want to wrap
1278
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1279
     *
1280
     * @return \Exception the wrapped exception
1281
     */
1282 1
    public function wrapException(
1283
        array $columnNames = array(),
1284
        \Exception $parent = null,
1285
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1286
    ) {
1287
1288
        // initialize the message
1289 1
        $message = $parent->getMessage();
0 ignored issues
show
Bug introduced by
It seems like $parent is not always an object, but can also be of type null. Maybe add an additional type check?

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

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
1290
1291
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1292 1
        if ($this->hasOriginalData()) {
1293
            // load the original data
1294
            $originalData = $this->getOriginalData();
1295
1296
            // replace old filename and line number of the original message
1297
            $message = $this->appendExceptionSuffix(
1298
                $this->stripExceptionSuffix($message),
1299
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1300
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1301
            );
1302
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
1303
        } else {
1304
            // append filename and line number to the original message
1305 1
            $message = $this->appendExceptionSuffix(
1306 1
                $this->stripExceptionSuffix($message),
1307 1
                $this->filename,
1308 1
                $this->lineNumber
1309
            );
1310
        }
1311
1312
        // query whether or not, column names has been passed
1313 1
        if (sizeof($columnNames) > 0) {
1314
            // prepare the original column names
1315
            $originalColumnNames = array();
1316
            foreach ($columnNames as $columnName) {
1317
                $originalColumnNames = $this->resolveOriginalColumnName($columnName);
1318
            }
1319
1320
            // append the column information
1321
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1322
        }
1323
1324
        // create a new exception and wrap the parent one
1325 1
        return new $className($message, null, $parent);
1326
    }
1327
1328
    /**
1329
     * Strip's the exception suffix containing filename and line number from the
1330
     * passed message.
1331
     *
1332
     * @param string $message The message to strip the exception suffix from
1333
     *
1334
     * @return mixed The message without the exception suffix
1335
     */
1336 1
    public function stripExceptionSuffix($message)
1337
    {
1338 1
        return str_replace($this->appendExceptionSuffix(), '', $message);
1339
    }
1340
1341
    /**
1342
     * Append's the exception suffix containing filename and line number to the
1343
     * passed message. If no message has been passed, only the suffix will be
1344
     * returned
1345
     *
1346
     * @param string|null $message    The message to append the exception suffix to
1347
     * @param string|null $filename   The filename used to create the suffix
1348
     * @param string|null $lineNumber The line number used to create the suffx
1349
     *
1350
     * @return string The message with the appended exception suffix
1351
     */
1352 1
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1353
    {
1354
1355
        // query whether or not a filename has been passed
1356 1
        if ($filename === null) {
1357 1
            $filename = $this->getFilename();
1358
        }
1359
1360
        // query whether or not a line number has been passed
1361 1
        if ($lineNumber === null) {
1362 1
            $lineNumber = $this->getLineNumber();
1363
        }
1364
1365
        // if no message has been passed, only return the suffix
1366 1
        if ($message === null) {
1367 1
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1368
        }
1369
1370
        // concatenate the message with the suffix and return it
1371 1
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1372
    }
1373
1374
    /**
1375
     * Raises the value for the counter with the passed key by one.
1376
     *
1377
     * @param mixed $counterName The name of the counter to raise
1378
     *
1379
     * @return integer The counter's new value
1380
     */
1381
    public function raiseCounter($counterName)
1382
    {
1383
1384
        // raise the counter with the passed name
1385
        return $this->getRegistryProcessor()->raiseCounter(
1386
            $this->getSerial(),
1387
            $counterName
1388
        );
1389
    }
1390
1391
    /**
1392
     * Merge the passed array into the status of the actual import.
1393
     *
1394
     * @param array $status The status information to be merged
1395
     *
1396
     * @return void
1397
     */
1398
    public function mergeAttributesRecursive(array $status)
1399
    {
1400
1401
        // merge the passed status
1402
        $this->getRegistryProcessor()->mergeAttributesRecursive(
1403
            $this->getSerial(),
1404
            $status
1405
        );
1406
    }
1407
}
1408