Completed
Push — 16.x ( 7c4702...19349c )
by
unknown
23s queued 10s
created

AbstractSubject::getDefaultColumnValues()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20

Duplication

Lines 20
Ratio 100 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

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