Completed
Push — 16.x ( c47298...3844cc )
by
unknown
11:44 queued 09:48
created

AbstractSubject   F

Complexity

Total Complexity 116

Size/Duplication

Total Lines 1516
Duplicated Lines 3.96 %

Coupling/Cohesion

Components 2
Dependencies 20

Test Coverage

Coverage 85.25%

Importance

Changes 0
Metric Value
wmc 116
lcom 2
cbo 20
dl 60
loc 1516
ccs 312
cts 366
cp 0.8525
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 60 8
A tearDown() 0 30 5
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 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 getRowStoreId() 0 11 2
A getRootCategory() 0 14 2
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 getEntityTypeCode() 0 14 2
A getFullOperationName() 0 4 1
B import() 0 125 7
A getStoreId() 0 18 2
B getCoreConfigData() 6 76 8
A raiseCounter() 0 9 1
A mergeAttributesRecursive() 0 9 1
A getEventName() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

Complex Class

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

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

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

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

1
<?php
2
3
/**
4
 * TechDivision\Import\Subjects\AbstractSubject
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 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
     */
282 81
    public function __construct(
283
        RegistryProcessorInterface $registryProcessor,
284
        GeneratorInterface $coreConfigDataUidGenerator,
285
        Collection $systemLoggers,
286
        EmitterInterface $emitter
287
    ) {
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 81
    }
293
294
    /**
295
     * Return's the event emitter instance.
296
     *
297
     * @return \League\Event\EmitterInterface The event emitter instance
298
     */
299 9
    public function getEmitter()
300
    {
301 9
        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
     */
311 13
    public function setFilename($filename)
312
    {
313 13
        $this->filename = $filename;
314 13
    }
315
316
    /**
317
     * Return's the name of the file to import.
318
     *
319
     * @return string The filename
320
     */
321 12
    public function getFilename()
322
    {
323 12
        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
     */
333 1
    public function setLineNumber($lineNumber)
334
    {
335 1
        $this->lineNumber = $lineNumber;
336 1
    }
337
338
    /**
339
     * Return's the actual line number.
340
     *
341
     * @return integer The line number
342
     */
343 17
    public function getLineNumber()
344
    {
345 17
        return $this->lineNumber;
346
    }
347
348
    /**
349
     * Return's the default callback mappings.
350
     *
351
     * @return array The default callback mappings
352
     */
353 1
    public function getDefaultCallbackMappings()
354
    {
355 1
        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
     */
363 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...
364
    {
365
366
        // initialize the array for the default column values
367 81
        $defaultColumnValues = array();
368
369
        // load the entity type from the execution context
370 81
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
371
372
        // load the column values from the configuration
373 81
        $columnValues = $this->getConfiguration()->getDefaultValues();
374
375
        // query whether or not default column values for the entity type are available
376 81
        if (isset($columnValues[$entityTypeCode])) {
377
            $defaultColumnValues = $columnValues[$entityTypeCode];
378
        }
379
380
        // return the default column values
381 81
        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
     */
389 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...
390
    {
391
392
        // initialize the array for the default header mappings
393 81
        $defaultHeaderMappings = array();
394
395
        // load the entity type from the execution context
396 81
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
397
398
        // load the header mappings from the configuration
399 81
        $headerMappings = $this->getConfiguration()->getHeaderMappings();
400
401
        // query whether or not header mappings for the entity type are available
402 81
        if (isset($headerMappings[$entityTypeCode])) {
403
            $defaultHeaderMappings = $headerMappings[$entityTypeCode];
404
        }
405
406
        // return the default header mappings
407 81
        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
     */
455 1
    public function isDebugMode()
456
    {
457 1
        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
     */
477 81
    public function setConfiguration(SubjectConfigurationInterface $configuration)
478
    {
479 81
        $this->configuration = $configuration;
480 81
    }
481
482
    /**
483
     * Return's the subject configuration.
484
     *
485
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
486
     */
487 81
    public function getConfiguration()
488
    {
489 81
        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
     */
499 1
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
500
    {
501 1
        $this->importAdapter = $importAdapter;
502 1
    }
503
504
    /**
505
     * Return's the import adapter instance.
506
     *
507
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
508
     */
509 1
    public function getImportAdapter()
510
    {
511 1
        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
     */
519 81
    public function getRegistryProcessor()
520
    {
521 81
        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
     */
531 8
    public function setSerial($serial)
532
    {
533 8
        $this->serial = $serial;
534 8
    }
535
536
    /**
537
     * Return's the unique serial for this import process.
538
     *
539
     * @return string The unique serial
540
     */
541 1
    public function getSerial()
542
    {
543 1
        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
     */
553 4
    public function mergeStatus(array $status)
554
    {
555 4
        $this->status = array_replace_recursive($this->status, $status);
556 4
    }
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
     */
573 4
    public function getUniqueId()
574
    {
575 4
        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
     */
593 1
    public function getMultipleFieldDelimiter()
594
    {
595 1
        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
     */
603 1
    public function getMultipleValueDelimiter()
604
    {
605 1
        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
     */
615 81
    public function setUp($serial)
616
    {
617
618
        // initialize the unique ID for the actual invocation
619 81
        $this->uniqueId = Uuid::uuid4()->toString();
620
621
        // load the status of the actual import
622 81
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
623
624
        // 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 81
            $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
631
        }
632
633
        // merge the header mappings with the values found in the configuration
634 81
        $this->headerMappings = array_merge($this->headerMappings, $this->getDefaultHeaderMappings());
635
636
        // merge the callback mappings with the mappings from the child instance
637 81
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
638
639
        // merge the default column values with the values found in the configuration
640 81
        $this->defaultColumnValues = array_merge($this->defaultColumnValues, $this->getDefaultColumnValues());
641
642
        // load the available callbacks from the configuration
643 81
        $availableCallbacks = $this->getConfiguration()->getCallbacks();
644
645
        // merge the callback mappings the the one from the configuration file
646 81
        foreach ($availableCallbacks as $callbackMappings) {
647 81
            foreach ($callbackMappings as $attributeCode => $mappings) {
648
                // write a log message, that default callback configuration will
649
                // be overwritten with the one from the configuration file
650 81
                if (isset($this->callbackMappings[$attributeCode])) {
651 81
                    $this->getSystemLogger()->notice(
652 81
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
653
                    );
654
                }
655
656
                // override the attributes callbacks
657 81
                $this->callbackMappings[$attributeCode] = $mappings;
658
            }
659
        }
660
661
        // load the available observers
662 81
        $availableObservers = $this->getObservers();
663
664
        // process the observers
665 81
        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
        }
674 81
    }
675
676
    /**
677
     * Clean up the global data after importing the variants.
678
     *
679
     * @param string $serial The serial of the actual import
680
     *
681
     * @return void
682
     */
683 1
    public function tearDown($serial)
684
    {
685
686
        // load the registry processor
687 1
        $registryProcessor = $this->getRegistryProcessor();
688
689
        // update the source directory for the next subject
690 1
        foreach ($this->getStatus() as $key => $status) {
691 1
            $registryProcessor->mergeAttributesRecursive($key, $status);
692
        }
693
694
        // load the available observers
695 1
        $availableObservers = $this->getObservers();
696
697
        // process the observers
698 1
        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
        // log a debug message with the new source directory
709 1
        $this->getSystemLogger()->debug(
710 1
            sprintf('Subject %s successfully updated status data for import %s', get_class($this), $serial)
711
        );
712 1
    }
713
714
    /**
715
     * Return's the target directory for the artefact export.
716
     *
717
     * @return string The target directory for the artefact export
718
     */
719 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...
720
    {
721
722
        // load the status from the registry processor
723 1
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
724
725
        // query whether or not a target directory (mandatory) has been configured
726 1
        if (isset($status[RegistryKeys::TARGET_DIRECTORY])) {
727 1
            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
     */
742 6
    public function registerObserver(ObserverInterface $observer, $type)
743
    {
744
745
        // query whether or not the array with the callbacks for the
746
        // passed type has already been initialized, or not
747 6
        if (!isset($this->observers[$type])) {
748 6
            $this->observers[$type] = array();
749
        }
750
751
        // append the callback with the instance of the passed type
752 6
        $this->observers[$type][] = $observer;
753 6
    }
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
     */
763 2
    public function registerCallback(CallbackInterface $callback, $type)
764
    {
765
766
        // query whether or not the array with the callbacks for the
767
        // passed type has already been initialized, or not
768 2
        if (!isset($this->callbacks[$type])) {
769 2
            $this->callbacks[$type] = array();
770
        }
771
772
        // append the callback with the instance of the passed type
773 2
        $this->callbacks[$type][] = $callback;
774 2
    }
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
     */
783 1
    public function getCallbacksByType($type)
784
    {
785
786
        // initialize the array for the callbacks
787 1
        $callbacks = array();
788
789
        // query whether or not callbacks for the type are available
790 1
        if (isset($this->callbacks[$type])) {
791 1
            $callbacks = $this->callbacks[$type];
792
        }
793
794
        // return the array with the type's callbacks
795 1
        return $callbacks;
796
    }
797
798
    /**
799
     * Return's the array with the available observers.
800
     *
801
     * @return array The observers
802
     */
803 81
    public function getObservers()
804
    {
805 81
        return $this->observers;
806
    }
807
808
    /**
809
     * Return's the array with the available callbacks.
810
     *
811
     * @return array The callbacks
812
     */
813 1
    public function getCallbacks()
814
    {
815 1
        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
     */
823 2
    public function getCallbackMappings()
824
    {
825 2
        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
     */
838 2
    public function import($serial, $filename)
839
    {
840
841
        try {
842
            // initialize the serial/filename
843 2
            $this->setSerial($serial);
844 2
            $this->setFilename($filename);
845
846
            // 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 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...
849
850
            // load the system logger instance
851 2
            $systemLogger = $this->getSystemLogger();
852
853
            // prepare the flag filenames
854 2
            $inProgressFilename = sprintf('%s.inProgress', $filename);
855 2
            $importedFilename = sprintf('%s.imported', $filename);
856 2
            $failedFilename = sprintf('%s.failed', $filename);
857
858
            // query whether or not the file has already been imported
859 2
            if ($this->isFile($failedFilename) ||
860 1
                $this->isFile($importedFilename) ||
861 2
                $this->isFile($inProgressFilename)
862
            ) {
863
                // log a debug message and exit
864 1
                $systemLogger->debug(sprintf('Import running, found inProgress file "%s"', $inProgressFilename));
865 1
                return;
866
            }
867
868
            // flag file as in progress
869 1
            $this->touch($inProgressFilename);
870
871
            // track the start time
872 1
            $startTime = microtime(true);
873
874
            // initialize the last time we've logged the counter with the processed rows per minute
875 1
            $this->lastLog = time();
876
877
            // 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 1
                array('operation-name' => $operationName = $this->getFullOperationName())
881
            );
882
883
            // let the adapter process the file
884 1
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
885
886
            // track the time needed for the import in seconds
887 1
            $endTime = microtime(true) - $startTime;
888
889
            // log a notice that the file has successfully been imported,
890
            // 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 1
                    $this->skippedRows,
897
                    $endTime
898
                ),
899 1
                array('operation-name' => $operationName)
900
            );
901
902
            // rename flag file, because import has been successfull
903 1
            if ($this->getConfiguration()->isCreatingImportedFile()) {
904 1
                $this->rename($inProgressFilename, $importedFilename);
905
            } else {
906
                $this->getFilesystemAdapter()->delete($inProgressFilename);
907
            }
908
909
            // update the status
910 1
            $this->mergeStatus(
911
                array(
912
                    RegistryKeys::STATUS => array(
913
                        RegistryKeys::FILES => array(
914
                            $filename => array(
915 1
                                $this->getUniqueId() => array(
916 1
                                    RegistryKeys::STATUS => 1,
917 1
                                    RegistryKeys::PROCESSED_ROWS => $this->getLineNumber()
918
                                )
919
                            )
920
                        )
921
                    )
922
                )
923
            );
924
925
            // 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 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...
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
        }
962 1
    }
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
     */
972 7
    public function importRow(array $row)
973
    {
974
975
        // initialize the row
976 7
        $this->row = $row;
977
978
        // raise the line number and reset the skip row flag
979 7
        $this->lineNumber++;
980 7
        $this->skipRow = false;
981
982
        // 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 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...
985
986
        // initialize the headers with the columns from the first line
987 7
        if (sizeof($this->headers) === 0) {
988
            // 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 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...
991
992
            // iterate over the column name => key an map the header names, if necessary
993 1
            foreach ($this->row as $value => $key) {
994 1
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
995
            }
996
997
            // iterate over the default column values to figure out whether or not the column exists
998 1
            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
            // 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 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...
1011
        } else {
1012
            // merge the default column value into the actual row
1013 6
            $this->row = array_replace($this->row, $this->columnValues);
1014
1015
            // load the available observers
1016 6
            $availableObservers = $this->getObservers();
1017
1018
            // process the observers
1019 6
            foreach ($availableObservers as $observers) {
1020
                // invoke the pre-import/import and post-import observers
1021
                /** @var \TechDivision\Import\Observers\ObserverInterface $observer */
1022 6
                foreach ($observers as $observer) {
1023
                    // query whether or not we have to skip the row
1024 6
                    if ($this->skipRow) {
1025
                        // 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 1
                                    get_class($observer)
1032
                                )
1033
                            )
1034
                        );
1035
1036
                        // skip the row
1037 1
                        break 2;
1038
                    }
1039
1040
                    // if not, set the subject and process the observer
1041 6
                    if ($observer instanceof ObserverInterface) {
1042 6
                        $this->row = $observer->handle($this);
1043
                    }
1044
                }
1045
            }
1046
        }
1047
1048
        // query whether or not a minute has been passed
1049 7
        if ($this->lastLog < time() - 59) {
1050
            // 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 7
                    basename($this->getFilename())
1058
                )
1059
            );
1060
1061
            // reset the last log time and the line number
1062 7
            $this->lastLog = time();
1063 7
            $this->lastLineNumber = $this->lineNumber;
1064
        }
1065
1066
        // 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 7
                    implode(' > ', $this->getConfiguration()->getConfiguration()->getOperationNames())
1072
                )
1073
            )
1074
        );
1075
1076
        // 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 7
    }
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
     */
1086 1
    public function isOkFileNeeded()
1087
    {
1088 1
        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
     */
1106 5
    public function getDefaultStoreViewCode()
1107
    {
1108 5
        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
     */
1118 4
    public function setStoreViewCode($storeViewCode)
1119
    {
1120 4
        $this->storeViewCode = $storeViewCode;
1121 4
    }
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
     */
1130 8
    public function getStoreViewCode($default = null)
1131
    {
1132
1133
        // return the store view code, if available
1134 8
        if ($this->storeViewCode !== null) {
1135 4
            return $this->storeViewCode;
1136
        }
1137
1138
        // if NOT and a default code is available
1139 4
        if ($default !== null) {
1140
            // return the default value
1141 3
            return $default;
1142
        }
1143
1144
        // return the default store view code
1145 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...
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
     */
1154 2
    public function prepareStoreViewCode()
1155
    {
1156
1157
        // re-set the store view code
1158 2
        $this->setStoreViewCode(null);
1159
1160
        // initialize the store view code
1161 2
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
1162 2
            $this->setStoreViewCode($storeViewCode);
1163
        }
1164 2
    }
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
     */
1174 4
    public function getStoreId($storeViewCode)
1175
    {
1176
1177
        // query whether or not, the requested store is available
1178 4
        if (isset($this->stores[$storeViewCode])) {
1179 3
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
1180
        }
1181
1182
        // throw an exception, if not
1183 1
        throw new \Exception(
1184 1
            sprintf(
1185 1
                'Found invalid store view code %s in file %s on line %d',
1186
                $storeViewCode,
1187 1
                $this->getFilename(),
1188 1
                $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
     */
1202 2
    public function getRowStoreId($default = null)
1203
    {
1204
1205
        // initialize the default store view code, if not passed
1206 2
        if ($default === null) {
1207 2
            $default = $this->getDefaultStoreViewCode();
1208
        }
1209
1210
        // load the store view code the create the product/attributes for
1211 2
        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
     */
1220 2
    public function getRootCategory()
1221
    {
1222
1223
        // load the actual store view code
1224 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...
1225
1226
        // query weather or not we've a root category or not
1227 2
        if (isset($this->rootCategories[$storeViewCode])) {
1228 1
            return $this->rootCategories[$storeViewCode];
1229
        }
1230
1231
        // throw an exception if the root category is NOT available
1232 1
        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
     */
1246 5
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1247
    {
1248
1249
        // initialize the core config data
1250
        $coreConfigData = array(
1251 5
            MemberNames::PATH => $path,
1252 5
            MemberNames::SCOPE => $scope,
1253 5
            MemberNames::SCOPE_ID => $scopeId
1254
        );
1255
1256
        // generate the UID from the passed data
1257 5
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1258
1259
        // iterate over the core config data and try to find the requested configuration value
1260 5
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1261 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1262
        }
1263
1264
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1265 4
        if ($scope === ScopeKeys::SCOPE_STORES) {
1266
            // query whether or not the website with the passed ID is available
1267 2
            foreach ($this->storeWebsites as $storeWebsite) {
1268 2
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1269
                    // replace scope with 'websites' and website ID
1270 2
                    $coreConfigData = array_merge(
1271 2
                        $coreConfigData,
1272
                        array(
1273 2
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1274
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1275
                        )
1276
                    );
1277
1278
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1279 2
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1280
1281
                    // query whether or not, the configuration value on 'websites' level
1282 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...
1283 1
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1284
                    }
1285
                }
1286
            }
1287
        }
1288
1289
        // replace scope with 'default' and scope ID '0'
1290 3
        $coreConfigData = array_merge(
1291 3
            $coreConfigData,
1292
            array(
1293 3
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1294
                MemberNames::SCOPE_ID => 0
1295
            )
1296
        );
1297
1298
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1299 3
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1300
1301
        // query whether or not, the configuration value on 'default' level
1302 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...
1303 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1304
        }
1305
1306
        // if not, return the passed default value
1307 2
        if ($default !== null) {
1308 1
            return $default;
1309
        }
1310
1311
        // throw an exception if no value can be found
1312
        // in the Magento configuration
1313 1
        throw new \Exception(
1314 1
            sprintf(
1315 1
                '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
     */
1330 2
    public function resolveOriginalColumnName($columnName)
1331
    {
1332
1333
        // try to load the original data
1334 2
        $originalData = $this->getOriginalData();
1335
1336
        // query whether or not original data is available
1337 2
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1338
            // query whether or not the original column name is available
1339 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1340 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1341
            }
1342
1343
            // query whether or a wildcard column name is available
1344 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1345 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1346
            }
1347
        }
1348
1349
        // return the original column name
1350 1
        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
     */
1358 2
    public function getOriginalData()
1359
    {
1360
1361
        // initialize the array for the original data
1362 2
        $originalData = array();
1363
1364
        // query whether or not the column contains original data
1365 2
        if ($this->hasOriginalData()) {
1366
            // unerialize the original data from the column
1367 1
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1368
        }
1369
1370
        // return an empty array, if not
1371 2
        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
     */
1380 2
    public function hasOriginalData()
1381
    {
1382 2
        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
     */
1395 1
    public function wrapException(
1396
        array $columnNames = array(),
1397
        \Exception $parent = null,
1398
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1399
    ) {
1400
1401
        // initialize the message
1402 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...
1403
1404
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1405 1
        if ($this->hasOriginalData()) {
1406
            // load the original data
1407 1
            $originalData = $this->getOriginalData();
1408
1409
            // 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 1
                $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
        // query whether or not, column names has been passed
1425 1
        if (sizeof($columnNames) > 0) {
1426
            // prepare the original column names
1427 1
            $originalColumnNames = array();
1428 1
            foreach ($columnNames as $columnName) {
1429 1
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1430
            }
1431
1432
            // append the column information
1433 1
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1434
        }
1435
1436
        // create a new exception and wrap the parent one
1437 1
        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
     */
1448 1
    public function stripExceptionSuffix($message)
1449
    {
1450 1
        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
     */
1464 12
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1465
    {
1466
1467
        // query whether or not a filename has been passed
1468 12
        if ($filename === null) {
1469 12
            $filename = $this->getFilename();
1470
        }
1471
1472
        // query whether or not a line number has been passed
1473 12
        if ($lineNumber === null) {
1474 12
            $lineNumber = $this->getLineNumber();
1475
        }
1476
1477
        // if no message has been passed, only return the suffix
1478 12
        if ($message === null) {
1479 1
            return sprintf(' in file %s on line %d', basename($filename), $lineNumber);
1480
        }
1481
1482
        // concatenate the message with the suffix and return it
1483 12
        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
     */
1493 1
    public function raiseCounter($counterName)
1494
    {
1495
1496
        // raise the counter with the passed name
1497 1
        return $this->getRegistryProcessor()->raiseCounter(
1498 1
            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
     */
1510 1
    public function mergeAttributesRecursive(array $status)
1511
    {
1512
1513
        // merge the passed status
1514 1
        return $this->getRegistryProcessor()->mergeAttributesRecursive(
1515 1
            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
     */
1547 9
    protected function getEventName($eventName)
1548
    {
1549 9
        return  sprintf(
1550 9
            '%s.%s.%s',
1551 9
            $this->getConfiguration()->getPluginConfiguration()->getId(),
1552 9
            $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