Completed
Push — 15.x ( 1a3897...0b1d63 )
by Tim
02:57
created

AbstractSubject::tearDown()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 6
cts 7
cp 0.8571
rs 9.7333
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2.0116
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 Ramsey\Uuid\Uuid;
24
use League\Event\EmitterInterface;
25
use Doctrine\Common\Collections\Collection;
26
use TechDivision\Import\RowTrait;
27
use TechDivision\Import\HeaderTrait;
28
use TechDivision\Import\SystemLoggerTrait;
29
use TechDivision\Import\Utils\ScopeKeys;
30
use TechDivision\Import\Utils\ColumnKeys;
31
use TechDivision\Import\Utils\EventNames;
32
use TechDivision\Import\Utils\MemberNames;
33
use TechDivision\Import\Utils\RegistryKeys;
34
use TechDivision\Import\Utils\EntityTypeCodes;
35
use TechDivision\Import\Utils\Generators\GeneratorInterface;
36
use TechDivision\Import\Callbacks\CallbackInterface;
37
use TechDivision\Import\Observers\ObserverInterface;
38
use TechDivision\Import\Adapter\ImportAdapterInterface;
39
use TechDivision\Import\Exceptions\WrappedColumnException;
40
use TechDivision\Import\Services\RegistryProcessorInterface;
41
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
42
43
/**
44
 * An abstract subject implementation.
45
 *
46
 * @author    Tim Wagner <[email protected]>
47
 * @copyright 2016 TechDivision GmbH <[email protected]>
48
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
49
 * @link      https://github.com/techdivision/import
50
 * @link      http://www.techdivision.com
51
 */
52
abstract class AbstractSubject implements SubjectInterface, FilesystemSubjectInterface, DateConverterSubjectInterface
53
{
54
55
    /**
56
     * The trait that provides basic filesystem handling functionality.
57
     *
58
     * @var \TechDivision\Import\Subjects\FilesystemTrait
59
     */
60
    use FilesystemTrait;
61
62
    /**
63
     * The trait that provides basic filesystem handling functionality.
64
     *
65
     * @var \TechDivision\Import\SystemLoggerTrait
66
     */
67
    use SystemLoggerTrait;
68
69
    /**
70
     * The trait that provides date converting functionality.
71
     *
72
     * @var \TechDivision\Import\DateConverterTrait
73
     */
74
    use DateConverterTrait;
75
76
    /**
77
     * The trait that provides header handling functionality.
78
     *
79
     * @var \TechDivision\Import\HeaderTrait
80
     */
81
    use HeaderTrait;
82
83
    /**
84
     * The trait that provides row handling functionality.
85
     *
86
     * @var \TechDivision\Import\RowTrait
87
     */
88
    use RowTrait;
89
90
    /**
91
     * The unique identifier for the actual invocation.
92
     *
93
     * @var string
94
     */
95
    protected $uniqueId;
96
97
    /**
98
     * The name of the file to be imported.
99
     *
100
     * @var string
101
     */
102
    protected $filename;
103
104
    /**
105
     * The actual line number.
106
     *
107
     * @var integer
108
     */
109
    protected $lineNumber = 0;
110
111
    /**
112
     * The import adapter instance.
113
     *
114
     * @var \TechDivision\Import\Adapter\ImportAdapterInterface
115
     */
116
    protected $importAdapter;
117
118
    /**
119
     * The subject configuration.
120
     *
121
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
122
     */
123
    protected $configuration;
124
125
    /**
126
     * The plugin configuration.
127
     *
128
     * @var \TechDivision\Import\Configuration\PluginConfigurationInterface
129
     */
130
    protected $pluginConfiguration;
131
132
    /**
133
     * The RegistryProcessor instance to handle running threads.
134
     *
135
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
136
     */
137
    protected $registryProcessor;
138
139
    /**
140
     * The actions unique serial.
141
     *
142
     * @var string
143
     */
144
    protected $serial;
145
146
    /**
147
     * Array with the subject's observers.
148
     *
149
     * @var array
150
     */
151
    protected $observers = array();
152
153
    /**
154
     * Array with the subject's callbacks.
155
     *
156
     * @var array
157
     */
158
    protected $callbacks = array();
159
160
    /**
161
     * The subject's callback mappings.
162
     *
163
     * @var array
164
     */
165
    protected $callbackMappings = array();
166
167
    /**
168
     * The available root categories.
169
     *
170
     * @var array
171
     */
172
    protected $rootCategories = array();
173
174
    /**
175
     * The Magento configuration.
176
     *
177
     * @var array
178
     */
179
    protected $coreConfigData = array();
180
181
    /**
182
     * The available stores.
183
     *
184
     * @var array
185
     */
186
    protected $stores = array();
187
188
    /**
189
     * The available websites.
190
     *
191
     * @var array
192
     */
193
    protected $storeWebsites = array();
194
195
    /**
196
     * The default store.
197
     *
198
     * @var array
199
     */
200
    protected $defaultStore;
201
202
    /**
203
     * The store view code the create the product/attributes for.
204
     *
205
     * @var string
206
     */
207
    protected $storeViewCode;
208
209
    /**
210
     * The UID generator for the core config data.
211
     *
212
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
213
     */
214
    protected $coreConfigDataUidGenerator;
215
216
    /**
217
     * UNIX timestamp with the date the last row counter has been logged.
218
     *
219
     * @var integer
220
     */
221
    protected $lastLog = 0;
222
223
    /**
224
     * The number of the last line that has been logged with the row counter
225
     * @var integer
226
     */
227
    protected $lastLineNumber = 0;
228
229
    /**
230
     * The event emitter instance.
231
     *
232
     * @var \League\Event\EmitterInterface
233
     */
234
    protected $emitter;
235
236
    /**
237
     * The status of the file (0 = not processed, 1 = successfully processed, 2 = processed with failure)
238
     *
239
     * @var array
240
     */
241
    protected $status = array();
242
243
    /**
244
     * Mapping for the virtual entity type code to the real Magento 2 EAV entity type code.
245
     *
246
     * @var array
247
     */
248
    protected $entityTypeCodeMappings = array(
249
        EntityTypeCodes::EAV_ATTRIBUTE                 => EntityTypeCodes::CATALOG_PRODUCT,
250
        EntityTypeCodes::EAV_ATTRIBUTE_SET             => EntityTypeCodes::CATALOG_PRODUCT,
251
        EntityTypeCodes::CATALOG_PRODUCT_PRICE         => EntityTypeCodes::CATALOG_PRODUCT,
252
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY     => EntityTypeCodes::CATALOG_PRODUCT,
253
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY_MSI => EntityTypeCodes::CATALOG_PRODUCT,
254
        EntityTypeCodes::CATALOG_PRODUCT_TIER_PRICE    => EntityTypeCodes::CATALOG_PRODUCT
255
    );
256
257
    /**
258
     * Initialize the subject instance.
259
     *
260
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
261
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
262
     * @param \Doctrine\Common\Collections\Collection                  $systemLoggers              The array with the system loggers instances
263
     * @param \League\Event\EmitterInterface                           $emitter                    The event emitter instance
264
     */
265 82
    public function __construct(
266
        RegistryProcessorInterface $registryProcessor,
267
        GeneratorInterface $coreConfigDataUidGenerator,
268
        Collection $systemLoggers,
269
        EmitterInterface $emitter
270
    ) {
271 82
        $this->emitter = $emitter;
272 82
        $this->systemLoggers = $systemLoggers;
0 ignored issues
show
Documentation Bug introduced by
It seems like $systemLoggers of type object<Doctrine\Common\Collections\Collection> is incompatible with the declared type array of property $systemLoggers.

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

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

Loading history...
273 82
        $this->registryProcessor = $registryProcessor;
274 82
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
275 82
    }
276
277
    /**
278
     * Return's the event emitter instance.
279
     *
280
     * @return \League\Event\EmitterInterface The event emitter instance
281
     */
282 9
    public function getEmitter()
283
    {
284 9
        return $this->emitter;
285
    }
286
287
    /**
288
     * Set's the name of the file to import
289
     *
290
     * @param string $filename The filename
291
     *
292
     * @return void
293
     */
294 13
    public function setFilename($filename)
295
    {
296 13
        $this->filename = $filename;
297 13
    }
298
299
    /**
300
     * Return's the name of the file to import.
301
     *
302
     * @return string The filename
303
     */
304 13
    public function getFilename()
305
    {
306 13
        return $this->filename;
307
    }
308
309
    /**
310
     * Set's the actual line number.
311
     *
312
     * @param integer $lineNumber The line number
313
     *
314
     * @return void
315
     */
316 1
    public function setLineNumber($lineNumber)
317
    {
318 1
        $this->lineNumber = $lineNumber;
319 1
    }
320
321
    /**
322
     * Return's the actual line number.
323
     *
324
     * @return integer The line number
325
     */
326 11
    public function getLineNumber()
327
    {
328 11
        return $this->lineNumber;
329
    }
330
331
    /**
332
     * Return's the default callback mappings.
333
     *
334
     * @return array The default callback mappings
335
     */
336 1
    public function getDefaultCallbackMappings()
337
    {
338 1
        return array();
339
    }
340
341
    /**
342
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
343
     * If the passed value is NOT a valid date, NULL will be returned.
344
     *
345
     * @param string $value The value to format
346
     *
347
     * @return string|null The formatted date or NULL if the date is not valid
348
     */
349
    public function formatDate($value)
350
    {
351
        return $this->getDateConverter()->convert($value);
352
    }
353
354
    /**
355
     * Extracts the elements of the passed value by exploding them
356
     * with the also passed delimiter.
357
     *
358
     * @param string      $value     The value to extract
359
     * @param string|null $delimiter The delimiter used to extrace the elements
360
     *
361
     * @return array The exploded values
362
     */
363
    public function explode($value, $delimiter = null)
364
    {
365
        return $this->getImportAdapter()->explode($value, $delimiter);
366
    }
367
368
    /**
369
     * Queries whether or not debug mode is enabled or not, default is TRUE.
370
     *
371
     * @return boolean TRUE if debug mode is enabled, else FALSE
372
     */
373 1
    public function isDebugMode()
374
    {
375 1
        return $this->getConfiguration()->isDebugMode();
376
    }
377
378
    /**
379
     * Return's the subject's execution context configuration.
380
     *
381
     * @return \TechDivision\Import\ExecutionContextInterface The execution context configuration to use
382
     */
383
    public function getExecutionContext()
384
    {
385
        return $this->getConfiguration()->getPluginConfiguration()->getExecutionContext();
0 ignored issues
show
Bug introduced by
The method getExecutionContext() does not seem to exist on object<TechDivision\Impo...ConfigurationInterface>.

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

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

Loading history...
386
    }
387
388
    /**
389
     * Set's the subject configuration.
390
     *
391
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration
392
     *
393
     * @return void
394
     */
395 82
    public function setConfiguration(SubjectConfigurationInterface $configuration)
396
    {
397 82
        $this->configuration = $configuration;
398 82
    }
399
400
    /**
401
     * Return's the subject configuration.
402
     *
403
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
404
     */
405 82
    public function getConfiguration()
406
    {
407 82
        return $this->configuration;
408
    }
409
410
    /**
411
     * Set's the import adapter instance.
412
     *
413
     * @param \TechDivision\Import\Adapter\ImportAdapterInterface $importAdapter The import adapter instance
414
     *
415
     * @return void
416
     */
417 1
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
418
    {
419 1
        $this->importAdapter = $importAdapter;
420 1
    }
421
422
    /**
423
     * Return's the import adapter instance.
424
     *
425
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
426
     */
427 1
    public function getImportAdapter()
428
    {
429 1
        return $this->importAdapter;
430
    }
431
432
    /**
433
     * Return's the RegistryProcessor instance to handle the running threads.
434
     *
435
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
436
     */
437 82
    public function getRegistryProcessor()
438
    {
439 82
        return $this->registryProcessor;
440
    }
441
442
    /**
443
     * Set's the unique serial for this import process.
444
     *
445
     * @param string $serial The unique serial
446
     *
447
     * @return void
448
     */
449 9
    public function setSerial($serial)
450
    {
451 9
        $this->serial = $serial;
452 9
    }
453
454
    /**
455
     * Return's the unique serial for this import process.
456
     *
457
     * @return string The unique serial
458
     */
459 2
    public function getSerial()
460
    {
461 2
        return $this->serial;
462
    }
463
464
    /**
465
     * Merge's the passed status into the actual one.
466
     *
467
     * @param array $status The status to MergeBuilder
468
     *
469
     * @return void
470
     */
471 2
    public function mergeStatus(array $status)
472
    {
473 2
        $this->status = array_merge_recursive($this->status, $status);
474 2
    }
475
476
    /**
477
     * Retur's the actual status.
478
     *
479
     * @return array The actual status
480
     */
481 1
    public function getStatus()
482
    {
483 1
        return $this->status;
484
    }
485
486
    /**
487
     * Return's the unique identifier for the actual invocation.
488
     *
489
     * @return string The unique identifier
490
     */
491 2
    public function getUniqueId()
492
    {
493 2
        return $this->uniqueId;
494
    }
495
496
    /**
497
     * Return's the source date format to use.
498
     *
499
     * @return string The source date format
500
     */
501
    public function getSourceDateFormat()
502
    {
503
        return $this->getConfiguration()->getSourceDateFormat();
0 ignored issues
show
Bug introduced by
The method getSourceDateFormat() does not seem to exist on object<TechDivision\Impo...ConfigurationInterface>.

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

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

Loading history...
504
    }
505
506
    /**
507
     * Return's the multiple field delimiter character to use, default value is comma (,).
508
     *
509
     * @return string The multiple field delimiter character
510
     */
511 1
    public function getMultipleFieldDelimiter()
512
    {
513 1
        return $this->getConfiguration()->getMultipleFieldDelimiter();
514
    }
515
516
    /**
517
     * Return's the multiple value delimiter character to use, default value is comma (|).
518
     *
519
     * @return string The multiple value delimiter character
520
     */
521 1
    public function getMultipleValueDelimiter()
522
    {
523 1
        return $this->getConfiguration()->getMultipleValueDelimiter();
524
    }
525
526
    /**
527
     * Intializes the previously loaded global data for exactly one bunch.
528
     *
529
     * @param string $serial The serial of the actual import
530
     *
531
     * @return void
532
     */
533 82
    public function setUp($serial)
534
    {
535
536
        // initialize the unique ID for the actual invocation
537 82
        $this->uniqueId = Uuid::uuid4()->toString();
538
539
        // load the status of the actual import
540 82
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
541
542
        // load the global data, if prepared initially
543 82
        if (isset($status[RegistryKeys::GLOBAL_DATA])) {
544 82
            $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
545 82
            $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
546 82
            $this->storeWebsites  = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
547 82
            $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
548 82
            $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
549
        }
550
551
        // merge the callback mappings with the mappings from the child instance
552 82
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
553
554
        // merge the header mappings with the values found in the configuration
555 82
        $this->headerMappings = array_merge($this->headerMappings, $this->getConfiguration()->getHeaderMappings());
556
557
        // load the available callbacks from the configuration
558 82
        $availableCallbacks = $this->getConfiguration()->getCallbacks();
559
560
        // merge the callback mappings the the one from the configuration file
561 82
        foreach ($availableCallbacks as $callbackMappings) {
562 82
            foreach ($callbackMappings as $attributeCode => $mappings) {
563
                // write a log message, that default callback configuration will
564
                // be overwritten with the one from the configuration file
565 82
                if (isset($this->callbackMappings[$attributeCode])) {
566 82
                    $this->getSystemLogger()->notice(
567 82
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
568
                    );
569
                }
570
571
                // override the attributes callbacks
572 82
                $this->callbackMappings[$attributeCode] = $mappings;
573
            }
574
        }
575 82
    }
576
577
    /**
578
     * Clean up the global data after importing the variants.
579
     *
580
     * @param string $serial The serial of the actual import
581
     *
582
     * @return void
583
     */
584 1
    public function tearDown($serial)
585
    {
586
587
        // load the registry processor
588 1
        $registryProcessor = $this->getRegistryProcessor();
589
590
        // update the source directory for the next subject
591 1
        foreach ($this->getStatus() as $key => $status) {
592
            $registryProcessor->mergeAttributesRecursive($key, $status);
593
        }
594
595
        // log a debug message with the new source directory
596 1
        $this->getSystemLogger()->debug(
597 1
            sprintf('Subject %s successfully updated status data for import %s', get_class($this), $serial)
598
        );
599 1
    }
600
601
    /**
602
     * Return's the target directory for the artefact export.
603
     *
604
     * @return string The target directory for the artefact export
605
     */
606 1 View Code Duplication
    public function getTargetDir()
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...
607
    {
608
609
        // load the status from the registry processor
610 1
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
611
612
        // query whether or not a target directory (mandatory) has been configured
613 1
        if (isset($status[RegistryKeys::TARGET_DIRECTORY])) {
614
            return $status[RegistryKeys::TARGET_DIRECTORY];
615
        }
616
617
        // throw an exception if the root category is NOT available
618 1
        throw new \Exception(sprintf('Can\'t find a target directory in status data for import %s', $this->getSerial()));
619
    }
620
621
    /**
622
     * Register the passed observer with the specific type.
623
     *
624
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
625
     * @param string                                           $type     The type to register the observer with
626
     *
627
     * @return void
628
     */
629 6
    public function registerObserver(ObserverInterface $observer, $type)
630
    {
631
632
        // query whether or not the array with the callbacks for the
633
        // passed type has already been initialized, or not
634 6
        if (!isset($this->observers[$type])) {
635 6
            $this->observers[$type] = array();
636
        }
637
638
        // append the callback with the instance of the passed type
639 6
        $this->observers[$type][] = $observer;
640 6
    }
641
642
    /**
643
     * Register the passed callback with the specific type.
644
     *
645
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
646
     * @param string                                           $type     The type to register the callback with
647
     *
648
     * @return void
649
     */
650 2
    public function registerCallback(CallbackInterface $callback, $type)
651
    {
652
653
        // query whether or not the array with the callbacks for the
654
        // passed type has already been initialized, or not
655 2
        if (!isset($this->callbacks[$type])) {
656 2
            $this->callbacks[$type] = array();
657
        }
658
659
        // append the callback with the instance of the passed type
660 2
        $this->callbacks[$type][] = $callback;
661 2
    }
662
663
    /**
664
     * Return's the array with callbacks for the passed type.
665
     *
666
     * @param string $type The type of the callbacks to return
667
     *
668
     * @return array The callbacks
669
     */
670 1
    public function getCallbacksByType($type)
671
    {
672
673
        // initialize the array for the callbacks
674 1
        $callbacks = array();
675
676
        // query whether or not callbacks for the type are available
677 1
        if (isset($this->callbacks[$type])) {
678 1
            $callbacks = $this->callbacks[$type];
679
        }
680
681
        // return the array with the type's callbacks
682 1
        return $callbacks;
683
    }
684
685
    /**
686
     * Return's the array with the available observers.
687
     *
688
     * @return array The observers
689
     */
690 6
    public function getObservers()
691
    {
692 6
        return $this->observers;
693
    }
694
695
    /**
696
     * Return's the array with the available callbacks.
697
     *
698
     * @return array The callbacks
699
     */
700 1
    public function getCallbacks()
701
    {
702 1
        return $this->callbacks;
703
    }
704
705
    /**
706
     * Return's the callback mappings for this subject.
707
     *
708
     * @return array The array with the subject's callback mappings
709
     */
710 2
    public function getCallbackMappings()
711
    {
712 2
        return $this->callbackMappings;
713
    }
714
715
    /**
716
     * Imports the content of the file with the passed filename.
717
     *
718
     *
719
     * @param string $serial   The serial of the actual import
720
     * @param string $filename The filename to process
721
     *
722
     * @return void
723
     * @throws \Exception Is thrown, if the import can't be processed
724
     */
725 2
    public function import($serial, $filename)
726
    {
727
728
        try {
729
            // initialize the serial/filename
730 2
            $this->setSerial($serial);
731 2
            $this->setFilename($filename);
732
733
            // invoke the events that has to be fired before the artfact will be processed
734 2
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_START, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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

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

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

Loading history...
735
736
            // load the system logger instance
737 2
            $systemLogger = $this->getSystemLogger();
738
739
            // prepare the flag filenames
740 2
            $inProgressFilename = sprintf('%s.inProgress', $filename);
741 2
            $importedFilename = sprintf('%s.imported', $filename);
742 2
            $failedFilename = sprintf('%s.failed', $filename);
743
744
            // query whether or not the file has already been imported
745 2
            if ($this->isFile($failedFilename) ||
746 1
                $this->isFile($importedFilename) ||
747 2
                $this->isFile($inProgressFilename)
748
            ) {
749
                // log a debug message and exit
750 1
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
751 1
                return;
752
            }
753
754
            // flag file as in progress
755 1
            $this->touch($inProgressFilename);
756
757
            // track the start time
758 1
            $startTime = microtime(true);
759
760
            // initialize the last time we've logged the counter with the processed rows per minute
761 1
            $this->lastLog = time();
762
763
            // log a message that the file has to be imported
764 1
            $systemLogger->info(sprintf('Now start processing file %s', $filename));
765
766
            // let the adapter process the file
767 1
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
768
769
            // track the time needed for the import in seconds
770 1
            $endTime = microtime(true) - $startTime;
771
772
            // log a message that the file has successfully been imported
773 1
            $systemLogger->info(sprintf('Successfully processed file %s with %d lines in %f s', $filename, $this->lineNumber, $endTime));
774
775
            // rename flag file, because import has been successfull
776 1
            if ($this->getConfiguration()->isCreatingImportedFile()) {
777 1
                $this->rename($inProgressFilename, $importedFilename);
778
            } else {
779
                $this->getFilesystemAdapter()->delete($inProgressFilename);
780
            }
781
782
            // update the status
783 1
            $this->mergeStatus(
784
                array(
785
                    RegistryKeys::STATUS => array(
786
                        RegistryKeys::FILES => array(
787
                            $filename => array(
788 1
                                $this->getUniqueId() => array(
789 1
                                    RegistryKeys::STATUS => 1,
790 1
                                    RegistryKeys::PROCESSED_ROWS => $this->getLineNumber()
791
                                )
792
                            )
793
                        )
794
                    )
795
                )
796
            );
797
798
            // invoke the events that has to be fired when the artfact has been successfully processed
799 1
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_SUCCESS, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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

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

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

Loading history...
800
        } catch (\Exception $e) {
801
            // rename the flag file, because import failed and write the stack trace
802
            $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...
803
            $this->write($failedFilename, $e->__toString());
804
805
            // update the status with the error message
806
            $this->mergeStatus(
807
                array(
808
                    RegistryKeys::STATUS => array(
809
                        RegistryKeys::FILES => array(
810
                            $filename => array(
811
                                $this->getUniqueId() => array(
812
                                    RegistryKeys::STATUS         => 2,
813
                                    RegistryKeys::ERROR_MESSAGE  => $e->getMessage(),
814
                                    RegistryKeys::PROCESSED_ROWS => $this->getLineNumber()
815
                                )
816
                            )
817
                        )
818
                    )
819
                )
820
            );
821
822
            // invoke the events that has to be fired when the artfact can't be processed
823
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_FAILURE, $this, $e);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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

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

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

Loading history...
824
825
            // do not wrap the exception if not already done
826
            if ($e instanceof WrappedColumnException) {
827
                throw $e;
828
            }
829
830
            // else wrap and throw the exception
831
            throw $this->wrapException(array(), $e);
832
        }
833 1
    }
834
835
    /**
836
     * Imports the passed row into the database. If the import failed, the exception
837
     * will be catched and logged, but the import process will be continued.
838
     *
839
     * @param array $row The row with the data to be imported
840
     *
841
     * @return void
842
     */
843 7
    public function importRow(array $row)
844
    {
845
846
        // initialize the row
847 7
        $this->row = $row;
848
849
        // raise the line number and reset the skip row flag
850 7
        $this->lineNumber++;
851 7
        $this->skipRow = false;
852
853
        // invoke the events that has to be fired before the artfact's row will be processed
854 7
        $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_ROW_PROCESS_START, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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

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

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

Loading history...
855
856
        // initialize the headers with the columns from the first line
857 7
        if (sizeof($this->headers) === 0) {
858 1
            foreach ($this->row as $value => $key) {
859 1
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
860
            }
861 1
            return;
862
        }
863
864
        // load the available observers
865 6
        $availableObservers = $this->getObservers();
866
867
        // process the observers
868 6
        foreach ($availableObservers as $observers) {
869
            // invoke the pre-import/import and post-import observers
870
            /** @var \TechDivision\Import\Observers\ObserverInterface $observer */
871 6
            foreach ($observers as $observer) {
872
                // query whether or not we have to skip the row
873 6
                if ($this->skipRow) {
874
                    // log a debug message with the actual line nr/file information
875 1
                    $this->getSystemLogger()->warning(
876 1
                        $this->appendExceptionSuffix(
877 1
                            sprintf(
878 1
                                'Skip processing operation "%s" after observer "%s"',
879 1
                                implode(' > ', $this->getConfiguration()->getConfiguration()->getOperationNames()),
880
                                get_class($observer)
881
                            )
882
                        )
883
                    );
884
885
                    // skip the row
886
                    break 2;
887
                }
888
889
                // if not, set the subject and process the observer
890 6
                if ($observer instanceof ObserverInterface) {
891 6
                    $this->row = $observer->handle($this);
892
                }
893
            }
894
        }
895
896
        // query whether or not a minute has been passed
897 5
        if ($this->lastLog < time() - 60) {
898
            // log the number processed rows per minute
899 5
            $this->getSystemLogger()->info(
900 5
                sprintf(
901 5
                    'Successfully processed "%d (%d)" rows per minute of file "%s"',
902 5
                    $this->lineNumber - $this->lastLineNumber,
903 5
                    $this->lineNumber,
904 5
                    $this->getFilename()
905
                )
906
            );
907
908
            // reset the last log time and the line number
909 5
            $this->lastLog = time();
910 5
            $this->lastLineNumber = $this->lineNumber;
911
        }
912
913
        // log a debug message with the actual line nr/file information
914 5
        $this->getSystemLogger()->debug(
915 5
            $this->appendExceptionSuffix(
916 5
                sprintf(
917 5
                    'Successfully processed operation "%s"',
918 5
                    implode(' > ', $this->getConfiguration()->getConfiguration()->getOperationNames())
919
                )
920
            )
921
        );
922
923
        // invoke the events that has to be fired when the artfact's row has been successfully processed
924
        $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_ROW_PROCESS_SUCCESS, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

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

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

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

Loading history...
925
    }
926
927
    /**
928
     * Queries whether or not that the subject needs an OK file to be processed.
929
     *
930
     * @return boolean TRUE if the subject needs an OK file, else FALSE
931
     */
932 1
    public function isOkFileNeeded()
933
    {
934 1
        return $this->getConfiguration()->isOkFileNeeded();
935
    }
936
937
    /**
938
     * Return's the default store.
939
     *
940
     * @return array The default store
941
     */
942
    public function getDefaultStore()
943
    {
944
        return $this->defaultStore;
945
    }
946
947
    /**
948
     * Return's the default store view code.
949
     *
950
     * @return array The default store view code
951
     */
952 4
    public function getDefaultStoreViewCode()
953
    {
954 4
        return $this->defaultStore[MemberNames::CODE];
955
    }
956
957
    /**
958
     * Set's the store view code the create the product/attributes for.
959
     *
960
     * @param string $storeViewCode The store view code
961
     *
962
     * @return void
963
     */
964 3
    public function setStoreViewCode($storeViewCode)
965
    {
966 3
        $this->storeViewCode = $storeViewCode;
967 3
    }
968
969
    /**
970
     * Return's the store view code the create the product/attributes for.
971
     *
972
     * @param string|null $default The default value to return, if the store view code has not been set
973
     *
974
     * @return string The store view code
975
     */
976 6
    public function getStoreViewCode($default = null)
977
    {
978
979
        // return the store view code, if available
980 6
        if ($this->storeViewCode !== null) {
981 2
            return $this->storeViewCode;
982
        }
983
984
        // if NOT and a default code is available
985 4
        if ($default !== null) {
986
            // return the default value
987 3
            return $default;
988
        }
989
990
        // return the default store view code
991 1
        return $this->getDefaultStoreViewCode();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getDefaultStoreViewCode(); (array) is incompatible with the return type declared by the interface TechDivision\Import\Subj...rface::getStoreViewCode of type string.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
992
    }
993
994
    /**
995
     * Prepare's the store view code in the subject. If the store_view_code row doesn't contain
996
     * any value, the default code of the default store view will be set.
997
     *
998
     * @return void
999
     */
1000 1
    public function prepareStoreViewCode()
1001
    {
1002
1003
        // re-set the store view code
1004 1
        $this->setStoreViewCode(null);
1005
1006
        // initialize the store view code
1007 1
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
1008 1
            $this->setStoreViewCode($storeViewCode);
1009
        }
1010 1
    }
1011
1012
    /**
1013
     * Return's the store ID of the store with the passed store view code
1014
     *
1015
     * @param string $storeViewCode The store view code to return the store ID for
1016
     *
1017
     * @return integer The ID of the store with the passed ID
1018
     * @throws \Exception Is thrown, if the store with the actual code is not available
1019
     */
1020 2
    public function getStoreId($storeViewCode)
1021
    {
1022
1023
        // query whether or not, the requested store is available
1024 2
        if (isset($this->stores[$storeViewCode])) {
1025 2
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
1026
        }
1027
1028
        // throw an exception, if not
1029
        throw new \Exception(
1030
            sprintf(
1031
                'Found invalid store view code %s in file %s on line %d',
1032
                $storeViewCode,
1033
                $this->getFilename(),
1034
                $this->getLineNumber()
1035
            )
1036
        );
1037
    }
1038
1039
    /**
1040
     * Return's the store ID of the actual row, or of the default store
1041
     * if no store view code is set in the CSV file.
1042
     *
1043
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
1044
     *
1045
     * @return integer The ID of the actual store
1046
     * @throws \Exception Is thrown, if the store with the actual code is not available
1047
     */
1048 1
    public function getRowStoreId($default = null)
1049
    {
1050
1051
        // initialize the default store view code, if not passed
1052 1
        if ($default === null) {
1053 1
            $default = $this->getDefaultStoreViewCode();
1054
        }
1055
1056
        // load the store view code the create the product/attributes for
1057 1
        return $this->getStoreId($this->getStoreViewCode($default));
0 ignored issues
show
Bug introduced by
It seems like $default defined by $this->getDefaultStoreViewCode() on line 1053 can also be of type array; however, TechDivision\Import\Subj...ect::getStoreViewCode() does only seem to accept string|null, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

An additional type check may prevent trouble.

Loading history...
1058
    }
1059
1060
    /**
1061
     * Return's the root category for the actual view store.
1062
     *
1063
     * @return array The store's root category
1064
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
1065
     */
1066 2
    public function getRootCategory()
1067
    {
1068
1069
        // load the actual store view code
1070 2
        $storeViewCode = $this->getStoreViewCode($this->getDefaultStoreViewCode());
0 ignored issues
show
Documentation introduced by
$this->getDefaultStoreViewCode() is of type array, but the function expects a string|null.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1071
1072
        // query weather or not we've a root category or not
1073 2
        if (isset($this->rootCategories[$storeViewCode])) {
1074 1
            return $this->rootCategories[$storeViewCode];
1075
        }
1076
1077
        // throw an exception if the root category is NOT available
1078 1
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1079
    }
1080
1081
    /**
1082
     * Return's the Magento configuration value.
1083
     *
1084
     * @param string  $path    The Magento path of the requested configuration value
1085
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1086
     * @param string  $scope   The scope the configuration value has been set
1087
     * @param integer $scopeId The scope ID the configuration value has been set
1088
     *
1089
     * @return mixed The configuration value
1090
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1091
     */
1092 5
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1093
    {
1094
1095
        // initialize the core config data
1096
        $coreConfigData = array(
1097 5
            MemberNames::PATH => $path,
1098 5
            MemberNames::SCOPE => $scope,
1099 5
            MemberNames::SCOPE_ID => $scopeId
1100
        );
1101
1102
        // generate the UID from the passed data
1103 5
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1104
1105
        // iterate over the core config data and try to find the requested configuration value
1106 5
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1107 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1108
        }
1109
1110
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1111 4
        if ($scope === ScopeKeys::SCOPE_STORES) {
1112
            // query whether or not the website with the passed ID is available
1113 2
            foreach ($this->storeWebsites as $storeWebsite) {
1114 2
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1115
                    // replace scope with 'websites' and website ID
1116 2
                    $coreConfigData = array_merge(
1117 2
                        $coreConfigData,
1118
                        array(
1119 2
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1120
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1121
                        )
1122
                    );
1123
1124
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1125 2
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1126
1127
                    // query whether or not, the configuration value on 'websites' level
1128 2 View Code Duplication
                    if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1129 2
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1130
                    }
1131
                }
1132
            }
1133
        }
1134
1135
        // replace scope with 'default' and scope ID '0'
1136 3
        $coreConfigData = array_merge(
1137 3
            $coreConfigData,
1138
            array(
1139 3
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1140
                MemberNames::SCOPE_ID => 0
1141
            )
1142
        );
1143
1144
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1145 3
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1146
1147
        // query whether or not, the configuration value on 'default' level
1148 3 View Code Duplication
        if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1149 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1150
        }
1151
1152
        // if not, return the passed default value
1153 2
        if ($default !== null) {
1154 1
            return $default;
1155
        }
1156
1157
        // throw an exception if no value can be found
1158
        // in the Magento configuration
1159 1
        throw new \Exception(
1160 1
            sprintf(
1161 1
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1162 1
                $path,
1163 1
                $scope,
1164 1
                $scopeId
1165
            )
1166
        );
1167
    }
1168
1169
    /**
1170
     * Resolve the original column name for the passed one.
1171
     *
1172
     * @param string $columnName The column name that has to be resolved
1173
     *
1174
     * @return string|null The original column name
1175
     */
1176 1
    public function resolveOriginalColumnName($columnName)
1177
    {
1178
1179
        // try to load the original data
1180 1
        $originalData = $this->getOriginalData();
1181
1182
        // query whether or not original data is available
1183 1
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1184
            // query whether or not the original column name is available
1185
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1186
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1187
            }
1188
1189
            // query whether or a wildcard column name is available
1190
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1191
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1192
            }
1193
        }
1194
1195
        // return the original column name
1196 1
        return $columnName;
1197
    }
1198
1199
    /**
1200
     * Return's the original data if available, or an empty array.
1201
     *
1202
     * @return array The original data
1203
     */
1204 1
    public function getOriginalData()
1205
    {
1206
1207
        // initialize the array for the original data
1208 1
        $originalData = array();
1209
1210
        // query whether or not the column contains original data
1211 1
        if ($this->hasOriginalData()) {
1212
            // unerialize the original data from the column
1213
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1214
        }
1215
1216
        // return an empty array, if not
1217 1
        return $originalData;
1218
    }
1219
1220
    /**
1221
     * Query's whether or not the actual column contains original data like
1222
     * filename, line number and column names.
1223
     *
1224
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1225
     */
1226 1
    public function hasOriginalData()
1227
    {
1228 1
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1229
    }
1230
1231
    /**
1232
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1233
     * line number and column names and use it for a detailed exception message.
1234
     *
1235
     * @param array      $columnNames The column names that should be resolved and wrapped
1236
     * @param \Exception $parent      The exception we want to wrap
1237
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1238
     *
1239
     * @return \Exception the wrapped exception
1240
     */
1241
    public function wrapException(
1242
        array $columnNames = array(),
1243
        \Exception $parent = null,
1244
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1245
    ) {
1246
1247
        // initialize the message
1248
        $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...
1249
1250
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1251
        if ($this->hasOriginalData()) {
1252
            // load the original data
1253
            $originalData = $this->getOriginalData();
1254
1255
            // replace old filename and line number of the original message
1256
            $message = $this->appendExceptionSuffix(
1257
                $this->stripExceptionSuffix($message),
1258
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1259
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1260
            );
1261
        } else {
1262
            // append filename and line number to the original message
1263
            $message = $this->appendExceptionSuffix(
1264
                $this->stripExceptionSuffix($message),
1265
                $this->filename,
1266
                $this->lineNumber
1267
            );
1268
        }
1269
1270
        // query whether or not, column names has been passed
1271
        if (sizeof($columnNames) > 0) {
1272
            // prepare the original column names
1273
            $originalColumnNames = array();
1274
            foreach ($columnNames as $columnName) {
1275
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1276
            }
1277
1278
            // append the column information
1279
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1280
        }
1281
1282
        // create a new exception and wrap the parent one
1283
        return new $className($message, null, $parent);
1284
    }
1285
1286
    /**
1287
     * Strip's the exception suffix containing filename and line number from the
1288
     * passed message.
1289
     *
1290
     * @param string $message The message to strip the exception suffix from
1291
     *
1292
     * @return mixed The message without the exception suffix
1293
     */
1294
    public function stripExceptionSuffix($message)
1295
    {
1296
        return str_replace($this->appendExceptionSuffix(), '', $message);
1297
    }
1298
1299
    /**
1300
     * Append's the exception suffix containing filename and line number to the
1301
     * passed message. If no message has been passed, only the suffix will be
1302
     * returned
1303
     *
1304
     * @param string|null $message    The message to append the exception suffix to
1305
     * @param string|null $filename   The filename used to create the suffix
1306
     * @param string|null $lineNumber The line number used to create the suffx
1307
     *
1308
     * @return string The message with the appended exception suffix
1309
     */
1310 8
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1311
    {
1312
1313
        // query whether or not a filename has been passed
1314 8
        if ($filename === null) {
1315 8
            $filename = $this->getFilename();
1316
        }
1317
1318
        // query whether or not a line number has been passed
1319 8
        if ($lineNumber === null) {
1320 8
            $lineNumber = $this->getLineNumber();
1321
        }
1322
1323
        // if no message has been passed, only return the suffix
1324 8
        if ($message === null) {
1325
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1326
        }
1327
1328
        // concatenate the message with the suffix and return it
1329 8
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1330
    }
1331
1332
    /**
1333
     * Raises the value for the counter with the passed key by one.
1334
     *
1335
     * @param mixed $counterName The name of the counter to raise
1336
     *
1337
     * @return integer The counter's new value
1338
     */
1339 1
    public function raiseCounter($counterName)
1340
    {
1341
1342
        // raise the counter with the passed name
1343 1
        return $this->getRegistryProcessor()->raiseCounter(
1344 1
            RegistryKeys::COUNTERS,
1345 1
            $counterName
1346
        );
1347
    }
1348
1349
    /**
1350
     * Merge the passed array into the status of the actual import.
1351
     *
1352
     * @param array $status The status information to be merged
1353
     *
1354
     * @return void
1355
     */
1356 1
    public function mergeAttributesRecursive(array $status)
1357
    {
1358
1359
        // merge the passed status
1360 1
        return $this->getRegistryProcessor()->mergeAttributesRecursive(
1361 1
            RegistryKeys::STATUS,
1362 1
            $status
1363
        );
1364
    }
1365
1366
    /**
1367
     * Return's the entity type code to be used.
1368
     *
1369
     * @return string The entity type code to be used
1370
     */
1371
    public function getEntityTypeCode()
1372
    {
1373
1374
        // load the configuration specific entity type code from the plugin configuration
1375
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
1376
1377
        // try to map the entity type code
1378
        if (isset($this->entityTypeCodeMappings[$entityTypeCode])) {
1379
            $entityTypeCode = $this->entityTypeCodeMappings[$entityTypeCode];
1380
        }
1381
1382
        // return the (mapped) entity type code
1383
        return $entityTypeCode;
1384
    }
1385
}
1386