Completed
Push — pac-57--delete-eav-relation ( 528a2c...144892 )
by
unknown
02:51
created

AbstractSubject   F

Complexity

Total Complexity 111

Size/Duplication

Total Lines 1490
Duplicated Lines 4.03 %

Coupling/Cohesion

Components 2
Dependencies 19

Test Coverage

Coverage 86.65%

Importance

Changes 0
Metric Value
wmc 111
lcom 2
cbo 19
dl 60
loc 1490
ccs 318
cts 367
cp 0.8665
rs 0.8
c 0
b 0
f 0

58 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A getEmitter() 0 4 1
A setFilename() 0 4 1
A getFilename() 0 4 1
A setLineNumber() 0 4 1
A getLineNumber() 0 4 1
A getDefaultCallbackMappings() 0 4 1
A getDefaultColumnValues() 20 20 2
A getDefaultHeaderMappings() 20 20 2
A formatDate() 0 16 2
A explode() 0 4 1
A isDebugMode() 0 4 1
A getExecutionContext() 0 4 1
A setConfiguration() 0 4 1
A getConfiguration() 0 4 1
A setImportAdapter() 0 4 1
A getImportAdapter() 0 4 1
A getRegistryProcessor() 0 4 1
A setSerial() 0 4 1
A getSerial() 0 4 1
A mergeStatus() 0 4 1
A getStatus() 0 4 1
A getUniqueId() 0 4 1
A getSourceDateFormat() 0 4 1
A getMultipleFieldDelimiter() 0 4 1
A getMultipleValueDelimiter() 0 4 1
B setUp() 0 46 5
A tearDown() 0 16 2
A getTargetDir() 14 14 2
A registerObserver() 0 12 2
A registerCallback() 0 12 2
A getCallbacksByType() 0 14 2
A getObservers() 0 4 1
A getCallbacks() 0 4 1
A getCallbackMappings() 0 4 1
C import() 0 128 8
C importRow() 0 108 10
A isOkFileNeeded() 0 4 1
A getDefaultStore() 0 4 1
A getDefaultStoreViewCode() 0 4 1
A setStoreViewCode() 0 4 1
A getStoreViewCode() 0 17 3
A prepareStoreViewCode() 0 11 2
A getStoreId() 0 18 2
A getRowStoreId() 0 11 2
A getRootCategory() 0 14 2
B getCoreConfigData() 6 76 8
A resolveOriginalColumnName() 0 22 4
A getOriginalData() 0 15 2
A hasOriginalData() 0 4 2
A wrapException() 0 44 4
A stripExceptionSuffix() 0 4 1
A appendExceptionSuffix() 0 21 4
A raiseCounter() 0 9 1
A mergeAttributesRecursive() 0 9 1
A getEntityTypeCode() 0 14 2
A getEventName() 0 9 1
A getFullOperationName() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

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