Completed
Push — master ( b5c840...3d5461 )
by Marcus
04:46
created

AbstractSubject::setUp()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 46
ccs 20
cts 20
cp 1
rs 8.867
c 0
b 0
f 0
cc 5
nc 8
nop 1
crap 5
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
     * The default values for the columns from the configuration.
245
     *
246
     * @var array
247
     */
248
    protected $defaultColumnValues = array();
249
250
    /**
251
     * The values of the actual column, pre-initialized with the default values.
252
     *
253
     * @var array
254
     */
255
    protected $columnValues = array();
256
257
    /**
258
     * Mapping for the virtual entity type code to the real Magento 2 EAV entity type code.
259
     *
260
     * @var array
261
     */
262
    protected $entityTypeCodeMappings = array(
263
        EntityTypeCodes::EAV_ATTRIBUTE                 => EntityTypeCodes::CATALOG_PRODUCT,
264
        EntityTypeCodes::EAV_ATTRIBUTE_SET             => EntityTypeCodes::CATALOG_PRODUCT,
265
        EntityTypeCodes::CATALOG_PRODUCT_URL           => EntityTypeCodes::CATALOG_PRODUCT,
266
        EntityTypeCodes::CATALOG_PRODUCT_PRICE         => EntityTypeCodes::CATALOG_PRODUCT,
267
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY     => EntityTypeCodes::CATALOG_PRODUCT,
268
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY_MSI => EntityTypeCodes::CATALOG_PRODUCT,
269
        EntityTypeCodes::CATALOG_PRODUCT_TIER_PRICE    => EntityTypeCodes::CATALOG_PRODUCT
270
    );
271
272
    /**
273
     * Initialize the subject instance.
274
     *
275
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
276
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
277
     * @param \Doctrine\Common\Collections\Collection                  $systemLoggers              The array with the system loggers instances
278
     * @param \League\Event\EmitterInterface                           $emitter                    The event emitter instance
279
     */
280 81
    public function __construct(
281
        RegistryProcessorInterface $registryProcessor,
282
        GeneratorInterface $coreConfigDataUidGenerator,
283
        Collection $systemLoggers,
284
        EmitterInterface $emitter
285
    ) {
286 81
        $this->emitter = $emitter;
287 81
        $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...
288 81
        $this->registryProcessor = $registryProcessor;
289 81
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
290 81
    }
291
292
    /**
293
     * Return's the event emitter instance.
294
     *
295
     * @return \League\Event\EmitterInterface The event emitter instance
296
     */
297 9
    public function getEmitter()
298
    {
299 9
        return $this->emitter;
300
    }
301
302
    /**
303
     * Set's the name of the file to import
304
     *
305
     * @param string $filename The filename
306
     *
307
     * @return void
308
     */
309 13
    public function setFilename($filename)
310
    {
311 13
        $this->filename = $filename;
312 13
    }
313
314
    /**
315
     * Return's the name of the file to import.
316
     *
317
     * @return string The filename
318
     */
319 12
    public function getFilename()
320
    {
321 12
        return $this->filename;
322
    }
323
324
    /**
325
     * Set's the actual line number.
326
     *
327
     * @param integer $lineNumber The line number
328
     *
329
     * @return void
330
     */
331 1
    public function setLineNumber($lineNumber)
332
    {
333 1
        $this->lineNumber = $lineNumber;
334 1
    }
335
336
    /**
337
     * Return's the actual line number.
338
     *
339
     * @return integer The line number
340
     */
341 17
    public function getLineNumber()
342
    {
343 17
        return $this->lineNumber;
344
    }
345
346
    /**
347
     * Return's the default callback mappings.
348
     *
349
     * @return array The default callback mappings
350
     */
351 1
    public function getDefaultCallbackMappings()
352
    {
353 1
        return array();
354
    }
355
356
    /**
357
     * Load the default column values from the configuration.
358
     *
359
     * @return array The array with the default column values
360
     */
361 81 View Code Duplication
    public function getDefaultColumnValues()
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...
362
    {
363
364
        // initialize the array for the default column values
365 81
        $defaultColumnValues = array();
366
367
        // load the entity type from the execution context
368 81
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
369
370
        // load the column values from the configuration
371 81
        $columnValues = $this->getConfiguration()->getDefaultValues();
372
373
        // query whether or not default column values for the entity type are available
374 81
        if (isset($columnValues[$entityTypeCode])) {
375
            $defaultColumnValues = $columnValues[$entityTypeCode];
376
        }
377
378
        // return the default column values
379 81
        return $defaultColumnValues;
380
    }
381
382
    /**
383
     * Load the default header mappings from the configuration.
384
     *
385
     * @return array The array with the default header mappings
386
     */
387 81 View Code Duplication
    public function getDefaultHeaderMappings()
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...
388
    {
389
390
        // initialize the array for the default header mappings
391 81
        $defaultHeaderMappings = array();
392
393
        // load the entity type from the execution context
394 81
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
395
396
        // load the header mappings from the configuration
397 81
        $headerMappings = $this->getConfiguration()->getHeaderMappings();
398
399
        // query whether or not header mappings for the entity type are available
400 81
        if (isset($headerMappings[$entityTypeCode])) {
401
            $defaultHeaderMappings = $headerMappings[$entityTypeCode];
402
        }
403
404
        // return the default header mappings
405 81
        return $defaultHeaderMappings;
406
    }
407
408
    /**
409
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
410
     * If the passed value is NOT a valid date, NULL will be returned.
411
     *
412
     * @param string $value The value to format
413
     *
414
     * @return string|null The formatted date or NULL if the date is not valid
415
     * @throws \InvalidArgumentException Is thrown, if the passed can not be formatted according to the configured date format
416
     */
417
    public function formatDate($value)
418
    {
419
420
        // try to format the date according to the configured date format
421
        $formattedDate = $this->getDateConverter()->convert($value);
422
423
        // query whether or not the formatting was successufull
424
        if ($formattedDate === null) {
425
            throw new \InvalidArgumentException(
426
                sprintf('Can\'t format date "%s" according given format "%s"', $value, $this->getSourceDateFormat())
427
            );
428
        }
429
430
        // return the formatted date
431
        return $formattedDate;
432
    }
433
434
    /**
435
     * Extracts the elements of the passed value by exploding them
436
     * with the also passed delimiter.
437
     *
438
     * @param string      $value     The value to extract
439
     * @param string|null $delimiter The delimiter used to extrace the elements
440
     *
441
     * @return array The exploded values
442
     */
443
    public function explode($value, $delimiter = null)
444
    {
445
        return $this->getImportAdapter()->explode($value, $delimiter);
446
    }
447
448
    /**
449
     * Queries whether or not debug mode is enabled or not, default is TRUE.
450
     *
451
     * @return boolean TRUE if debug mode is enabled, else FALSE
452
     */
453 1
    public function isDebugMode()
454
    {
455 1
        return $this->getConfiguration()->isDebugMode();
456
    }
457
458
    /**
459
     * Return's the subject's execution context configuration.
460
     *
461
     * @return \TechDivision\Import\ExecutionContextInterface The execution context configuration to use
462
     */
463
    public function getExecutionContext()
464
    {
465
        return $this->getConfiguration()->getPluginConfiguration()->getExecutionContext();
466
    }
467
468
    /**
469
     * Set's the subject configuration.
470
     *
471
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration
472
     *
473
     * @return void
474
     */
475 81
    public function setConfiguration(SubjectConfigurationInterface $configuration)
476
    {
477 81
        $this->configuration = $configuration;
478 81
    }
479
480
    /**
481
     * Return's the subject configuration.
482
     *
483
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
484
     */
485 81
    public function getConfiguration()
486
    {
487 81
        return $this->configuration;
488
    }
489
490
    /**
491
     * Set's the import adapter instance.
492
     *
493
     * @param \TechDivision\Import\Adapter\ImportAdapterInterface $importAdapter The import adapter instance
494
     *
495
     * @return void
496
     */
497 1
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
498
    {
499 1
        $this->importAdapter = $importAdapter;
500 1
    }
501
502
    /**
503
     * Return's the import adapter instance.
504
     *
505
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
506
     */
507 1
    public function getImportAdapter()
508
    {
509 1
        return $this->importAdapter;
510
    }
511
512
    /**
513
     * Return's the RegistryProcessor instance to handle the running threads.
514
     *
515
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
516
     */
517 81
    public function getRegistryProcessor()
518
    {
519 81
        return $this->registryProcessor;
520
    }
521
522
    /**
523
     * Set's the unique serial for this import process.
524
     *
525
     * @param string $serial The unique serial
526
     *
527
     * @return void
528
     */
529 8
    public function setSerial($serial)
530
    {
531 8
        $this->serial = $serial;
532 8
    }
533
534
    /**
535
     * Return's the unique serial for this import process.
536
     *
537
     * @return string The unique serial
538
     */
539 1
    public function getSerial()
540
    {
541 1
        return $this->serial;
542
    }
543
544
    /**
545
     * Merge's the passed status into the actual one.
546
     *
547
     * @param array $status The status to MergeBuilder
548
     *
549
     * @return void
550
     */
551 4
    public function mergeStatus(array $status)
552
    {
553 4
        $this->status = array_replace_recursive($this->status, $status);
554 4
    }
555
556
    /**
557
     * Retur's the actual status.
558
     *
559
     * @return array The actual status
560
     */
561
    public function getStatus()
562
    {
563
        return $this->status;
564
    }
565
566
    /**
567
     * Return's the unique identifier for the actual invocation.
568
     *
569
     * @return string The unique identifier
570
     */
571 4
    public function getUniqueId()
572
    {
573 4
        return $this->uniqueId;
574
    }
575
576
    /**
577
     * Return's the source date format to use.
578
     *
579
     * @return string The source date format
580
     */
581
    public function getSourceDateFormat()
582
    {
583
        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...
584
    }
585
586
    /**
587
     * Return's the multiple field delimiter character to use, default value is comma (,).
588
     *
589
     * @return string The multiple field delimiter character
590
     */
591 1
    public function getMultipleFieldDelimiter()
592
    {
593 1
        return $this->getConfiguration()->getMultipleFieldDelimiter();
594
    }
595
596
    /**
597
     * Return's the multiple value delimiter character to use, default value is comma (|).
598
     *
599
     * @return string The multiple value delimiter character
600
     */
601 1
    public function getMultipleValueDelimiter()
602
    {
603 1
        return $this->getConfiguration()->getMultipleValueDelimiter();
604
    }
605
606
    /**
607
     * Intializes the previously loaded global data for exactly one bunch.
608
     *
609
     * @param string $serial The serial of the actual import
610
     *
611
     * @return void
612
     */
613 81
    public function setUp($serial)
614
    {
615
616
        // initialize the unique ID for the actual invocation
617 81
        $this->uniqueId = Uuid::uuid4()->toString();
618
619
        // load the status of the actual import
620 81
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
621
622
        // load the global data, if prepared initially
623 81
        if (isset($status[RegistryKeys::GLOBAL_DATA])) {
624 81
            $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
625 81
            $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
626 81
            $this->storeWebsites  = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
627 81
            $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
628 81
            $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
629
        }
630
631
        // merge the header mappings with the values found in the configuration
632 81
        $this->headerMappings = array_merge($this->headerMappings, $this->getDefaultHeaderMappings());
633
634
        // merge the callback mappings with the mappings from the child instance
635 81
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
636
637
        // merge the default column values with the values found in the configuration
638 81
        $this->defaultColumnValues = array_merge($this->defaultColumnValues, $this->getDefaultColumnValues());
639
640
        // load the available callbacks from the configuration
641 81
        $availableCallbacks = $this->getConfiguration()->getCallbacks();
642
643
        // merge the callback mappings the the one from the configuration file
644 81
        foreach ($availableCallbacks as $callbackMappings) {
645 81
            foreach ($callbackMappings as $attributeCode => $mappings) {
646
                // write a log message, that default callback configuration will
647
                // be overwritten with the one from the configuration file
648 81
                if (isset($this->callbackMappings[$attributeCode])) {
649 81
                    $this->getSystemLogger()->notice(
650 81
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
651
                    );
652
                }
653
654
                // override the attributes callbacks
655 81
                $this->callbackMappings[$attributeCode] = $mappings;
656
            }
657
        }
658 81
    }
659
660
    /**
661
     * Clean up the global data after importing the variants.
662
     *
663
     * @param string $serial The serial of the actual import
664
     *
665
     * @return void
666
     */
667 1
    public function tearDown($serial)
668
    {
669
670
        // load the registry processor
671 1
        $registryProcessor = $this->getRegistryProcessor();
672
673
        // update the source directory for the next subject
674 1
        foreach ($this->getStatus() as $key => $status) {
675 1
            $registryProcessor->mergeAttributesRecursive($key, $status);
676
        }
677
678
        // log a debug message with the new source directory
679 1
        $this->getSystemLogger()->debug(
680 1
            sprintf('Subject %s successfully updated status data for import %s', get_class($this), $serial)
681
        );
682 1
    }
683
684
    /**
685
     * Return's the target directory for the artefact export.
686
     *
687
     * @return string The target directory for the artefact export
688
     */
689 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...
690
    {
691
692
        // load the status from the registry processor
693 1
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
694
695
        // query whether or not a target directory (mandatory) has been configured
696 1
        if (isset($status[RegistryKeys::TARGET_DIRECTORY])) {
697 1
            return $status[RegistryKeys::TARGET_DIRECTORY];
698
        }
699
700
        // throw an exception if the root category is NOT available
701
        throw new \Exception(sprintf('Can\'t find a target directory in status data for import %s', $this->getSerial()));
702
    }
703
704
    /**
705
     * Register the passed observer with the specific type.
706
     *
707
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
708
     * @param string                                           $type     The type to register the observer with
709
     *
710
     * @return void
711
     */
712 6
    public function registerObserver(ObserverInterface $observer, $type)
713
    {
714
715
        // query whether or not the array with the callbacks for the
716
        // passed type has already been initialized, or not
717 6
        if (!isset($this->observers[$type])) {
718 6
            $this->observers[$type] = array();
719
        }
720
721
        // append the callback with the instance of the passed type
722 6
        $this->observers[$type][] = $observer;
723 6
    }
724
725
    /**
726
     * Register the passed callback with the specific type.
727
     *
728
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
729
     * @param string                                           $type     The type to register the callback with
730
     *
731
     * @return void
732
     */
733 2
    public function registerCallback(CallbackInterface $callback, $type)
734
    {
735
736
        // query whether or not the array with the callbacks for the
737
        // passed type has already been initialized, or not
738 2
        if (!isset($this->callbacks[$type])) {
739 2
            $this->callbacks[$type] = array();
740
        }
741
742
        // append the callback with the instance of the passed type
743 2
        $this->callbacks[$type][] = $callback;
744 2
    }
745
746
    /**
747
     * Return's the array with callbacks for the passed type.
748
     *
749
     * @param string $type The type of the callbacks to return
750
     *
751
     * @return array The callbacks
752
     */
753 1
    public function getCallbacksByType($type)
754
    {
755
756
        // initialize the array for the callbacks
757 1
        $callbacks = array();
758
759
        // query whether or not callbacks for the type are available
760 1
        if (isset($this->callbacks[$type])) {
761 1
            $callbacks = $this->callbacks[$type];
762
        }
763
764
        // return the array with the type's callbacks
765 1
        return $callbacks;
766
    }
767
768
    /**
769
     * Return's the array with the available observers.
770
     *
771
     * @return array The observers
772
     */
773 6
    public function getObservers()
774
    {
775 6
        return $this->observers;
776
    }
777
778
    /**
779
     * Return's the array with the available callbacks.
780
     *
781
     * @return array The callbacks
782
     */
783 1
    public function getCallbacks()
784
    {
785 1
        return $this->callbacks;
786
    }
787
788
    /**
789
     * Return's the callback mappings for this subject.
790
     *
791
     * @return array The array with the subject's callback mappings
792
     */
793 2
    public function getCallbackMappings()
794
    {
795 2
        return $this->callbackMappings;
796
    }
797
798
    /**
799
     * Imports the content of the file with the passed filename.
800
     *
801
     *
802
     * @param string $serial   The serial of the actual import
803
     * @param string $filename The filename to process
804
     *
805
     * @return void
806
     * @throws \Exception Is thrown, if the import can't be processed
807
     */
808 2
    public function import($serial, $filename)
809
    {
810
811
        try {
812
            // initialize the serial/filename
813 2
            $this->setSerial($serial);
814 2
            $this->setFilename($filename);
815
816
            // invoke the events that has to be fired before the artfact will be processed
817 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...
818 2
            $this->getEmitter()->emit($this->getEventName(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...
819
820
            // load the system logger instance
821 2
            $systemLogger = $this->getSystemLogger();
822
823
            // prepare the flag filenames
824 2
            $inProgressFilename = sprintf('%s.inProgress', $filename);
825 2
            $importedFilename = sprintf('%s.imported', $filename);
826 2
            $failedFilename = sprintf('%s.failed', $filename);
827
828
            // query whether or not the file has already been imported
829 2
            if ($this->isFile($failedFilename) ||
830 1
                $this->isFile($importedFilename) ||
831 2
                $this->isFile($inProgressFilename)
832
            ) {
833
                // log a debug message and exit
834 1
                $systemLogger->debug(sprintf('Import running, found inProgress file "%s"', $inProgressFilename));
835 1
                return;
836
            }
837
838
            // flag file as in progress
839 1
            $this->touch($inProgressFilename);
840
841
            // track the start time
842 1
            $startTime = microtime(true);
843
844
            // initialize the last time we've logged the counter with the processed rows per minute
845 1
            $this->lastLog = time();
846
847
            // log a message that the file has to be imported
848 1
            $systemLogger->info(
849 1
                sprintf('Now start processing file "%s"', basename($filename)),
850 1
                array('operation-name' => $operationName = $this->getFullOperationName())
851
            );
852
853
            // let the adapter process the file
854 1
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
855
856
            // track the time needed for the import in seconds
857 1
            $endTime = microtime(true) - $startTime;
858
859
            // log a message that the file has successfully been imported
860 1
            $systemLogger->info(
861 1
                sprintf('Successfully processed file "%s" with "%d" lines in "%f" s', basename($filename), $this->lineNumber, $endTime),
862 1
                array('operation-name' => $operationName)
863
            );
864
865
            // rename flag file, because import has been successfull
866 1
            if ($this->getConfiguration()->isCreatingImportedFile()) {
867 1
                $this->rename($inProgressFilename, $importedFilename);
868
            } else {
869
                $this->getFilesystemAdapter()->delete($inProgressFilename);
870
            }
871
872
            // update the status
873 1
            $this->mergeStatus(
874
                array(
875
                    RegistryKeys::STATUS => array(
876
                        RegistryKeys::FILES => array(
877
                            $filename => array(
878 1
                                $this->getUniqueId() => array(
879 1
                                    RegistryKeys::STATUS => 1,
880 1
                                    RegistryKeys::PROCESSED_ROWS => $this->getLineNumber()
881
                                )
882
                            )
883
                        )
884
                    )
885
                )
886
            );
887
888
            // invoke the events that has to be fired when the artfact has been successfully processed
889 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...
890 1
            $this->getEmitter()->emit($this->getEventName(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...
891
        } catch (\Exception $e) {
892
            // rename the flag file, because import failed and write the stack trace
893
            $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...
894
            $this->write($failedFilename, $e->__toString());
895
896
            // update the status with the error message
897
            $this->mergeStatus(
898
                array(
899
                    RegistryKeys::STATUS => array(
900
                        RegistryKeys::FILES => array(
901
                            $filename => array(
902
                                $this->getUniqueId() => array(
903
                                    RegistryKeys::STATUS         => 2,
904
                                    RegistryKeys::ERROR_MESSAGE  => $e->getMessage(),
905
                                    RegistryKeys::PROCESSED_ROWS => $this->getLineNumber()
906
                                )
907
                            )
908
                        )
909
                    )
910
                )
911
            );
912
913
            // invoke the events that has to be fired when the artfact can't be processed
914
            $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...
915
            $this->getEmitter()->emit($this->getEventName(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...
916
917
            // do not wrap the exception if not already done
918
            if ($e instanceof WrappedColumnException) {
919
                throw $e;
920
            }
921
922
            // else wrap and throw the exception
923
            throw $this->wrapException(array(), $e);
924
        }
925 1
    }
926
927
    /**
928
     * Imports the passed row into the database. If the import failed, the exception
929
     * will be catched and logged, but the import process will be continued.
930
     *
931
     * @param array $row The row with the data to be imported
932
     *
933
     * @return void
934
     */
935 7
    public function importRow(array $row)
936
    {
937
938
        // initialize the row
939 7
        $this->row = $row;
940
941
        // raise the line number and reset the skip row flag
942 7
        $this->lineNumber++;
943 7
        $this->skipRow = false;
944
945
        // invoke the events that has to be fired before the artfact's row will be processed
946 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...
947 7
        $this->getEmitter()->emit($this->getEventName(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...
948
949
        // initialize the headers with the columns from the first line
950 7
        if (sizeof($this->headers) === 0) {
951
            // invoke the events that has to be fired before the artfact's header row will be processed
952 1
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_HEADER_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...
953 1
            $this->getEmitter()->emit($this->getEventName(EventNames::SUBJECT_ARTEFACT_HEADER_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...
954
955
            // iterate over the column name => key an map the header names, if necessary
956 1
            foreach ($this->row as $value => $key) {
957 1
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
958
            }
959
960
            // iterate over the default column values to figure out whether or not the column exists
961 1
            foreach ($this->defaultColumnValues as $name => $value) {
962
                // do nothing, if the column already exists
963
                if (array_key_exists($key = $this->mapAttributeCodeByHeaderMapping($name), $this->headers)) {
964
                    continue;
965
                }
966
                // add the header and the default value for the column
967
                $this->headers[$key] = $columnKey = sizeof($this->headers);
968
                $this->columnValues[$columnKey] = $value;
969
            }
970
971
            // invoke the events that has to be fired when the artfact's header row has been successfully processed
972 1
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_HEADER_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...
973 1
            $this->getEmitter()->emit($this->getEventName(EventNames::SUBJECT_ARTEFACT_HEADER_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...
974
        } else {
975
            // merge the default column value into the actual row
976 6
            $this->row = array_replace($this->row, $this->columnValues);
977
978
            // load the available observers
979 6
            $availableObservers = $this->getObservers();
980
981
            // process the observers
982 6
            foreach ($availableObservers as $observers) {
983
                // invoke the pre-import/import and post-import observers
984
                /** @var \TechDivision\Import\Observers\ObserverInterface $observer */
985 6
                foreach ($observers as $observer) {
986
                    // query whether or not we have to skip the row
987 6
                    if ($this->skipRow) {
988
                        // log a debug message with the actual line nr/file information
989 1
                        $this->getSystemLogger()->debug(
990 1
                            $this->appendExceptionSuffix(
991 1
                                sprintf(
992 1
                                    'Skip processing operation "%s" after observer "%s"',
993 1
                                    $this->getFullOperationName(),
994 1
                                    get_class($observer)
995
                                )
996
                            )
997
                        );
998
999
                        // skip the row
1000 1
                        break 2;
1001
                    }
1002
1003
                    // if not, set the subject and process the observer
1004 6
                    if ($observer instanceof ObserverInterface) {
1005 6
                        $this->row = $observer->handle($this);
1006
                    }
1007
                }
1008
            }
1009
        }
1010
1011
        // query whether or not a minute has been passed
1012 7
        if ($this->lastLog < time() - 59) {
1013
            // log the number processed rows per minute
1014 7
            $this->getSystemLogger()->info(
1015 7
                sprintf(
1016 7
                    'Operation "%s" successfully processed "%d (%d)" rows per minute of file "%s"',
1017 7
                    $this->getFullOperationName(),
1018 7
                    $this->lineNumber - $this->lastLineNumber,
1019 7
                    $this->lineNumber,
1020 7
                    basename($this->getFilename())
1021
                )
1022
            );
1023
1024
            // reset the last log time and the line number
1025 7
            $this->lastLog = time();
1026 7
            $this->lastLineNumber = $this->lineNumber;
1027
        }
1028
1029
        // log a debug message with the actual line nr/file information
1030 7
        $this->getSystemLogger()->debug(
1031 7
            $this->appendExceptionSuffix(
1032 7
                sprintf(
1033 7
                    'Successfully processed operation "%s"',
1034 7
                    implode(' > ', $this->getConfiguration()->getConfiguration()->getOperationNames())
1035
                )
1036
            )
1037
        );
1038
1039
        // invoke the events that has to be fired when the artfact's row has been successfully processed
1040 7
        $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...
1041 7
        $this->getEmitter()->emit($this->getEventName(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...
1042 7
    }
1043
1044
    /**
1045
     * Queries whether or not that the subject needs an OK file to be processed.
1046
     *
1047
     * @return boolean TRUE if the subject needs an OK file, else FALSE
1048
     */
1049 1
    public function isOkFileNeeded()
1050
    {
1051 1
        return $this->getConfiguration()->isOkFileNeeded();
1052
    }
1053
1054
    /**
1055
     * Return's the default store.
1056
     *
1057
     * @return array The default store
1058
     */
1059
    public function getDefaultStore()
1060
    {
1061
        return $this->defaultStore;
1062
    }
1063
1064
    /**
1065
     * Return's the default store view code.
1066
     *
1067
     * @return array The default store view code
1068
     */
1069 5
    public function getDefaultStoreViewCode()
1070
    {
1071 5
        return $this->defaultStore[MemberNames::CODE];
1072
    }
1073
1074
    /**
1075
     * Set's the store view code the create the product/attributes for.
1076
     *
1077
     * @param string $storeViewCode The store view code
1078
     *
1079
     * @return void
1080
     */
1081 4
    public function setStoreViewCode($storeViewCode)
1082
    {
1083 4
        $this->storeViewCode = $storeViewCode;
1084 4
    }
1085
1086
    /**
1087
     * Return's the store view code the create the product/attributes for.
1088
     *
1089
     * @param string|null $default The default value to return, if the store view code has not been set
1090
     *
1091
     * @return string The store view code
1092
     */
1093 8
    public function getStoreViewCode($default = null)
1094
    {
1095
1096
        // return the store view code, if available
1097 8
        if ($this->storeViewCode !== null) {
1098 4
            return $this->storeViewCode;
1099
        }
1100
1101
        // if NOT and a default code is available
1102 4
        if ($default !== null) {
1103
            // return the default value
1104 3
            return $default;
1105
        }
1106
1107
        // return the default store view code
1108 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...
1109
    }
1110
1111
    /**
1112
     * Prepare's the store view code in the subject. If the store_view_code row doesn't contain
1113
     * any value, the default code of the default store view will be set.
1114
     *
1115
     * @return void
1116
     */
1117 2
    public function prepareStoreViewCode()
1118
    {
1119
1120
        // re-set the store view code
1121 2
        $this->setStoreViewCode(null);
1122
1123
        // initialize the store view code
1124 2
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
1125 2
            $this->setStoreViewCode($storeViewCode);
1126
        }
1127 2
    }
1128
1129
    /**
1130
     * Return's the store ID of the store with the passed store view code
1131
     *
1132
     * @param string $storeViewCode The store view code to return the store ID for
1133
     *
1134
     * @return integer The ID of the store with the passed ID
1135
     * @throws \Exception Is thrown, if the store with the actual code is not available
1136
     */
1137 4
    public function getStoreId($storeViewCode)
1138
    {
1139
1140
        // query whether or not, the requested store is available
1141 4
        if (isset($this->stores[$storeViewCode])) {
1142 3
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
1143
        }
1144
1145
        // throw an exception, if not
1146 1
        throw new \Exception(
1147 1
            sprintf(
1148 1
                'Found invalid store view code %s in file %s on line %d',
1149 1
                $storeViewCode,
1150 1
                $this->getFilename(),
1151 1
                $this->getLineNumber()
1152
            )
1153
        );
1154
    }
1155
1156
    /**
1157
     * Return's the store ID of the actual row, or of the default store
1158
     * if no store view code is set in the CSV file.
1159
     *
1160
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
1161
     *
1162
     * @return integer The ID of the actual store
1163
     * @throws \Exception Is thrown, if the store with the actual code is not available
1164
     */
1165 2
    public function getRowStoreId($default = null)
1166
    {
1167
1168
        // initialize the default store view code, if not passed
1169 2
        if ($default === null) {
1170 2
            $default = $this->getDefaultStoreViewCode();
1171
        }
1172
1173
        // load the store view code the create the product/attributes for
1174 2
        return $this->getStoreId($this->getStoreViewCode($default));
0 ignored issues
show
Bug introduced by
It seems like $default defined by $this->getDefaultStoreViewCode() on line 1170 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...
1175
    }
1176
1177
    /**
1178
     * Return's the root category for the actual view store.
1179
     *
1180
     * @return array The store's root category
1181
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
1182
     */
1183 2
    public function getRootCategory()
1184
    {
1185
1186
        // load the actual store view code
1187 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...
1188
1189
        // query weather or not we've a root category or not
1190 2
        if (isset($this->rootCategories[$storeViewCode])) {
1191 1
            return $this->rootCategories[$storeViewCode];
1192
        }
1193
1194
        // throw an exception if the root category is NOT available
1195 1
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1196
    }
1197
1198
    /**
1199
     * Return's the Magento configuration value.
1200
     *
1201
     * @param string  $path    The Magento path of the requested configuration value
1202
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1203
     * @param string  $scope   The scope the configuration value has been set
1204
     * @param integer $scopeId The scope ID the configuration value has been set
1205
     *
1206
     * @return mixed The configuration value
1207
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1208
     */
1209 5
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1210
    {
1211
1212
        // initialize the core config data
1213
        $coreConfigData = array(
1214 5
            MemberNames::PATH => $path,
1215 5
            MemberNames::SCOPE => $scope,
1216 5
            MemberNames::SCOPE_ID => $scopeId
1217
        );
1218
1219
        // generate the UID from the passed data
1220 5
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1221
1222
        // iterate over the core config data and try to find the requested configuration value
1223 5
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1224 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1225
        }
1226
1227
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1228 4
        if ($scope === ScopeKeys::SCOPE_STORES) {
1229
            // query whether or not the website with the passed ID is available
1230 2
            foreach ($this->storeWebsites as $storeWebsite) {
1231 2
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1232
                    // replace scope with 'websites' and website ID
1233 2
                    $coreConfigData = array_merge(
1234 2
                        $coreConfigData,
1235
                        array(
1236 2
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1237
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1238
                        )
1239
                    );
1240
1241
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1242 2
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1243
1244
                    // query whether or not, the configuration value on 'websites' level
1245 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...
1246 1
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1247
                    }
1248
                }
1249
            }
1250
        }
1251
1252
        // replace scope with 'default' and scope ID '0'
1253 3
        $coreConfigData = array_merge(
1254 3
            $coreConfigData,
1255
            array(
1256 3
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1257
                MemberNames::SCOPE_ID => 0
1258
            )
1259
        );
1260
1261
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1262 3
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1263
1264
        // query whether or not, the configuration value on 'default' level
1265 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...
1266 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1267
        }
1268
1269
        // if not, return the passed default value
1270 2
        if ($default !== null) {
1271 1
            return $default;
1272
        }
1273
1274
        // throw an exception if no value can be found
1275
        // in the Magento configuration
1276 1
        throw new \Exception(
1277 1
            sprintf(
1278 1
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1279 1
                $path,
1280 1
                $scope,
1281 1
                $scopeId
1282
            )
1283
        );
1284
    }
1285
1286
    /**
1287
     * Resolve the original column name for the passed one.
1288
     *
1289
     * @param string $columnName The column name that has to be resolved
1290
     *
1291
     * @return string|null The original column name
1292
     */
1293 2
    public function resolveOriginalColumnName($columnName)
1294
    {
1295
1296
        // try to load the original data
1297 2
        $originalData = $this->getOriginalData();
1298
1299
        // query whether or not original data is available
1300 2
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1301
            // query whether or not the original column name is available
1302 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1303 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1304
            }
1305
1306
            // query whether or a wildcard column name is available
1307 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1308 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1309
            }
1310
        }
1311
1312
        // return the original column name
1313 1
        return $columnName;
1314
    }
1315
1316
    /**
1317
     * Return's the original data if available, or an empty array.
1318
     *
1319
     * @return array The original data
1320
     */
1321 2
    public function getOriginalData()
1322
    {
1323
1324
        // initialize the array for the original data
1325 2
        $originalData = array();
1326
1327
        // query whether or not the column contains original data
1328 2
        if ($this->hasOriginalData()) {
1329
            // unerialize the original data from the column
1330 1
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1331
        }
1332
1333
        // return an empty array, if not
1334 2
        return $originalData;
1335
    }
1336
1337
    /**
1338
     * Query's whether or not the actual column contains original data like
1339
     * filename, line number and column names.
1340
     *
1341
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1342
     */
1343 2
    public function hasOriginalData()
1344
    {
1345 2
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1346
    }
1347
1348
    /**
1349
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1350
     * line number and column names and use it for a detailed exception message.
1351
     *
1352
     * @param array      $columnNames The column names that should be resolved and wrapped
1353
     * @param \Exception $parent      The exception we want to wrap
1354
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1355
     *
1356
     * @return \Exception the wrapped exception
1357
     */
1358 1
    public function wrapException(
1359
        array $columnNames = array(),
1360
        \Exception $parent = null,
1361
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1362
    ) {
1363
1364
        // initialize the message
1365 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...
1366
1367
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1368 1
        if ($this->hasOriginalData()) {
1369
            // load the original data
1370 1
            $originalData = $this->getOriginalData();
1371
1372
            // replace old filename and line number of the original message
1373 1
            $message = $this->appendExceptionSuffix(
1374 1
                $this->stripExceptionSuffix($message),
1375 1
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1376 1
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1377
            );
1378
        } else {
1379
            // append filename and line number to the original message
1380
            $message = $this->appendExceptionSuffix(
1381
                $this->stripExceptionSuffix($message),
1382
                $this->filename,
1383
                $this->lineNumber
1384
            );
1385
        }
1386
1387
        // query whether or not, column names has been passed
1388 1
        if (sizeof($columnNames) > 0) {
1389
            // prepare the original column names
1390 1
            $originalColumnNames = array();
1391 1
            foreach ($columnNames as $columnName) {
1392 1
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1393
            }
1394
1395
            // append the column information
1396 1
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1397
        }
1398
1399
        // create a new exception and wrap the parent one
1400 1
        return new $className($message, null, $parent);
1401
    }
1402
1403
    /**
1404
     * Strip's the exception suffix containing filename and line number from the
1405
     * passed message.
1406
     *
1407
     * @param string $message The message to strip the exception suffix from
1408
     *
1409
     * @return mixed The message without the exception suffix
1410
     */
1411 1
    public function stripExceptionSuffix($message)
1412
    {
1413 1
        return str_replace($this->appendExceptionSuffix(), '', $message);
1414
    }
1415
1416
    /**
1417
     * Append's the exception suffix containing filename and line number to the
1418
     * passed message. If no message has been passed, only the suffix will be
1419
     * returned
1420
     *
1421
     * @param string|null $message    The message to append the exception suffix to
1422
     * @param string|null $filename   The filename used to create the suffix
1423
     * @param string|null $lineNumber The line number used to create the suffx
1424
     *
1425
     * @return string The message with the appended exception suffix
1426
     */
1427 12
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1428
    {
1429
1430
        // query whether or not a filename has been passed
1431 12
        if ($filename === null) {
1432 12
            $filename = $this->getFilename();
1433
        }
1434
1435
        // query whether or not a line number has been passed
1436 12
        if ($lineNumber === null) {
1437 12
            $lineNumber = $this->getLineNumber();
1438
        }
1439
1440
        // if no message has been passed, only return the suffix
1441 12
        if ($message === null) {
1442 1
            return sprintf(' in file %s on line %d', basename($filename), $lineNumber);
1443
        }
1444
1445
        // concatenate the message with the suffix and return it
1446 12
        return sprintf('%s in file %s on line %d', $message, basename($filename), $lineNumber);
1447
    }
1448
1449
    /**
1450
     * Raises the value for the counter with the passed key by one.
1451
     *
1452
     * @param mixed $counterName The name of the counter to raise
1453
     *
1454
     * @return integer The counter's new value
1455
     */
1456 1
    public function raiseCounter($counterName)
1457
    {
1458
1459
        // raise the counter with the passed name
1460 1
        return $this->getRegistryProcessor()->raiseCounter(
1461 1
            RegistryKeys::COUNTERS,
1462 1
            $counterName
1463
        );
1464
    }
1465
1466
    /**
1467
     * Merge the passed array into the status of the actual import.
1468
     *
1469
     * @param array $status The status information to be merged
1470
     *
1471
     * @return void
1472
     */
1473 1
    public function mergeAttributesRecursive(array $status)
1474
    {
1475
1476
        // merge the passed status
1477 1
        return $this->getRegistryProcessor()->mergeAttributesRecursive(
1478 1
            RegistryKeys::STATUS,
1479 1
            $status
1480
        );
1481
    }
1482
1483
    /**
1484
     * Return's the entity type code to be used.
1485
     *
1486
     * @return string The entity type code to be used
1487
     */
1488
    public function getEntityTypeCode()
1489
    {
1490
1491
        // load the configuration specific entity type code from the plugin configuration
1492
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
1493
1494
        // try to map the entity type code
1495
        if (isset($this->entityTypeCodeMappings[$entityTypeCode])) {
1496
            $entityTypeCode = $this->entityTypeCodeMappings[$entityTypeCode];
1497
        }
1498
1499
        // return the (mapped) entity type code
1500
        return $entityTypeCode;
1501
    }
1502
1503
    /**
1504
     * Concatenates and returns the event name for the actual plugin and subject context.
1505
     *
1506
     * @param string $eventName The event name to concatenate
1507
     *
1508
     * @return string The concatenated event name
1509
     */
1510 9
    protected function getEventName($eventName)
1511
    {
1512 9
        return  sprintf(
1513 9
            '%s.%s.%s',
1514 9
            $this->getConfiguration()->getPluginConfiguration()->getId(),
1515 9
            $this->getConfiguration()->getId(),
1516 9
            $eventName
1517
        );
1518
    }
1519
1520
    /**
1521
     * Return's the full opration name, which consists of the Magento edition, the entity type code and the operation name.
1522
     *
1523
     * @param string $separator The separator used to seperate the elements
1524
     *
1525
     * @return string The full operation name
1526
     */
1527
    public function getFullOperationName($separator = '/')
1528
    {
1529
        return $this->getConfiguration()->getFullOperationName($separator);
1530
    }
1531
}
1532