Completed
Push — master ( b3282a...39e6eb )
by Tim
10s
created

getAttributeSetByAttributeSetName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 9

Duplication

Lines 18
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 18
loc 18
ccs 0
cts 11
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Subjects\AbstractProductSubject
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-product
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Subjects;
22
23
use Psr\Log\LoggerInterface;
24
use TechDivision\Import\Utils\RegistryKeys;
25
use TechDivision\Import\Subjects\AbstractSubject;
26
use TechDivision\Import\Product\Utils\MemberNames;
27
use TechDivision\Import\Product\Services\ProductProcessorInterface;
28
use TechDivision\Import\Services\RegistryProcessorInterface;
29
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
30
31
/**
32
 * The abstract product subject implementation that provides basic product
33
 * handling business logic.
34
 *
35
 * @author    Tim Wagner <[email protected]>
36
 * @copyright 2016 TechDivision GmbH <[email protected]>
37
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
38
 * @link      https://github.com/techdivision/import-product
39
 * @link      http://www.techdivision.com
40
 */
41
abstract class AbstractProductSubject extends AbstractSubject
42
{
43
44
    /**
45
     * The processor to read/write the necessary product data.
46
     *
47
     * @var \TechDivision\Import\Product\Services\ProductProcessorInterface
48
     */
49
    protected $productProcessor;
50
51
    /**
52
     * The available stores.
53
     *
54
     * @var array
55
     */
56
    protected $stores = array();
57
58
    /**
59
     * The available store websites.
60
     *
61
     * @var array
62
     */
63
    protected $storeWebsites = array();
64
65
    /**
66
     * The available EAV attributes, grouped by their attribute set and the attribute set name as keys.
67
     *
68
     * @var array
69
     */
70
    protected $attributes = array();
71
72
    /**
73
     * The available tax classes.
74
     *
75
     * @var array
76
     */
77
    protected $taxClasses = array();
78
79
    /**
80
     * The available categories.
81
     *
82
     * @var array
83
     */
84
    protected $categories = array();
85
86
    /**
87
     * The available root categories.
88
     *
89
     * @var array
90
     */
91
    protected $rootCategories = array();
92
93
    /**
94
     * The ID of the product that has been created recently.
95
     *
96
     * @var string
97
     */
98
    protected $lastEntityId;
99
100
    /**
101
     * The SKU of the product that has been created recently.
102
     *
103
     * @var string
104
     */
105
    protected $lastSku;
106
107
    /**
108
     * The store view code the create the product/attributes for.
109
     *
110
     * @var string
111
     */
112
    protected $storeViewCode;
113
114
    /**
115
     * The default store.
116
     *
117
     * @var array
118
     */
119
    protected $defaultStore;
120
121
    /**
122
     * The Magento 2 configuration.
123
     *
124
     * @var array
125
     */
126
    protected $coreConfigData;
127
128
    /**
129
     * The mapping for the SKUs to the created entity IDs.
130
     *
131
     * @var array
132
     */
133
    protected $skuEntityIdMapping = array();
134
135
    /**
136
     * The mapping for the SKUs to the store view codes.
137
     *
138
     * @var array
139
     */
140
    protected $skuStoreViewCodeMapping = array();
141
142
    /**
143
     * Initialize the subject instance.
144
     *
145
     * @param \Psr\Log\LoggerInterface                                         $systemLogger      The system logger instance
146
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration     The subject configuration instance
147
     * @param \TechDivision\Import\Services\RegistryProcessorInterface         $registryProcessor The registry processor instance
148
     * @param \TechDivision\Import\Product\Services\ProductProcessorInterface  $productProcessor  The product processor instance
149
     */
150 3
    public function __construct(
151
        LoggerInterface $systemLogger,
152
        SubjectConfigurationInterface $configuration,
153
        RegistryProcessorInterface $registryProcessor,
154
        ProductProcessorInterface $productProcessor
155
    ) {
156
157
        // pass the arguments to the parent constructor
158 3
        parent::__construct($systemLogger, $configuration, $registryProcessor);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class TechDivision\Import\Subjects\AbstractSubject as the method __construct() does only exist in the following sub-classes of TechDivision\Import\Subjects\AbstractSubject: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
159
160
        // initialize the product processor
161 3
        $this->productProcessor = $productProcessor;
162 3
    }
163
164
    /**
165
     * Set's the product processor instance.
166
     *
167
     * @param \TechDivision\Import\Product\Services\ProductProcessorInterface $productProcessor The product processor instance
168
     *
169
     * @return void
170
     */
171 3
    public function setProductProcessor(ProductProcessorInterface $productProcessor)
172
    {
173 3
        $this->productProcessor = $productProcessor;
174 3
    }
175
176
    /**
177
     * Return's the product processor instance.
178
     *
179
     * @return \TechDivision\Import\Services\ProductProcessorInterface The product processor instance
180
     */
181 3
    public function getProductProcessor()
182
    {
183 3
        return $this->productProcessor;
184
    }
185
186
    /**
187
     * Set's the SKU of the last imported product.
188
     *
189
     * @param string $lastSku The SKU
190
     *
191
     * @return void
192
     */
193
    public function setLastSku($lastSku)
194
    {
195
        $this->lastSku = $lastSku;
196
    }
197
198
    /**
199
     * Return's the SKU of the last imported product.
200
     *
201
     * @return string|null The SKU
202
     */
203
    public function getLastSku()
204
    {
205
        return $this->lastSku;
206
    }
207
208
    /**
209
     * Set's the ID of the product that has been created recently.
210
     *
211
     * @param string $lastEntityId The entity ID
212
     *
213
     * @return void
214
     */
215 1
    public function setLastEntityId($lastEntityId)
216
    {
217 1
        $this->lastEntityId = $lastEntityId;
218 1
    }
219
220
    /**
221
     * Return's the ID of the product that has been created recently.
222
     *
223
     * @return string The entity Id
224
     */
225
    public function getLastEntityId()
226
    {
227
        return $this->lastEntityId;
228
    }
229
230
    /**
231
     * Queries whether or not the SKU has already been processed.
232
     *
233
     * @param string $sku The SKU to check been processed
234
     *
235
     * @return boolean TRUE if the SKU has been processed, else FALSE
236
     */
237
    public function hasBeenProcessed($sku)
238
    {
239
        return isset($this->skuEntityIdMapping[$sku]);
240
    }
241
242
    /**
243
     * Add the passed SKU => entity ID mapping.
244
     *
245
     * @param string $sku The SKU
246
     *
247
     * @return void
248
     */
249
    public function addSkuEntityIdMapping($sku)
250
    {
251
        $this->skuEntityIdMapping[$sku] = $this->getLastEntityId();
252
    }
253
254
    /**
255
     * Add the passed SKU => store view code mapping.
256
     *
257
     * @param string $sku           The SKU
258
     * @param string $storeViewCode The store view code
259
     *
260
     * @return void
261
     */
262
    public function addSkuStoreViewCodeMapping($sku, $storeViewCode)
263
    {
264
        $this->skuStoreViewCodeMapping[$sku] = $storeViewCode;
265
    }
266
267
    /**
268
     * Set's the store view code the create the product/attributes for.
269
     *
270
     * @param string $storeViewCode The store view code
271
     *
272
     * @return void
273
     */
274 3
    public function setStoreViewCode($storeViewCode)
275
    {
276 3
        $this->storeViewCode = $storeViewCode;
277 3
    }
278
279
    /**
280
     * Return's the store view code the create the product/attributes for.
281
     *
282
     * @param string|null $default The default value to return, if the store view code has not been set
283
     *
284
     * @return string The store view code
285
     */
286
    public function getStoreViewCode($default = null)
287
    {
288
289
        // return the store view code, if available
290
        if ($this->storeViewCode != null) {
291
            return $this->storeViewCode;
292
        }
293
294
        // if NOT and a default code is available
295
        if ($default != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $default of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
296
            // return the default value
297
            return $default;
298
        }
299
    }
300
301
    /**
302
     * Return's the default store.
303
     *
304
     * @return array The default store
305
     */
306
    public function getDefaultStore()
307
    {
308
        return $this->defaultStore;
309
    }
310
311
    /**
312
     * Intializes the previously loaded global data for exactly one bunch.
313
     *
314
     * @return void
315
     * @see \Importer\Csv\Actions\ProductImportAction::prepare()
316
     */
317
    public function setUp()
318
    {
319
320
        // load the status of the actual import
321
        $status = $this->getRegistryProcessor()->getAttribute($this->getSerial());
322
323
        // load the global data we've prepared initially
324
        $this->storeWebsites =  $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
325
        $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
326
        $this->taxClasses = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::TAX_CLASSES];
327
        $this->categories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CATEGORIES];
328
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
329
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
330
        $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
331
332
        // invoke the parent method
333
        parent::setUp();
334
    }
335
336
    /**
337
     * Clean up the global data after importing the bunch.
338
     *
339
     * @return void
340
     */
341
    public function tearDown()
342
    {
343
344
        // invoke the parent method
345
        parent::tearDown();
346
347
        // load the registry processor
348
        $registryProcessor = $this->getRegistryProcessor();
349
350
        // update the status with the SKU => entity ID mapping
351
        $registryProcessor->mergeAttributesRecursive(
352
            $this->getSerial(),
353
            array()
354
        );
355
356
        // update the status
357
        $registryProcessor->mergeAttributesRecursive(
358
            $this->getSerial(),
359
            array(
360
                RegistryKeys::FILES => array($this->getFilename() => array(RegistryKeys::STATUS => 1)),
361
                RegistryKeys::SKU_ENTITY_ID_MAPPING => $this->skuEntityIdMapping,
362
                RegistryKeys::SKU_STORE_VIEW_CODE_MAPPING => $this->skuStoreViewCodeMapping
363
            )
364
        );
365
    }
366
367
    /**
368
     * Return's the store ID of the actual row, or of the default store
369
     * if no store view code is set in the CSV file.
370
     *
371
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
372
     *
373
     * @return integer The ID of the actual store
374
     * @throws \Exception Is thrown, if the store with the actual code is not available
375
     */
376
    public function getRowStoreId($default = null)
377
    {
378
379
        // initialize the default store view code, if not passed
380
        if ($default == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $default of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
381
            $defaultStore = $this->getDefaultStore();
382
            $default = $defaultStore[MemberNames::CODE];
383
        }
384
385
        // load the store view code the create the product/attributes for
386
        $storeViewCode = $this->getStoreViewCode($default);
387
388
        // query whether or not, the requested store is available
389
        if (isset($this->stores[$storeViewCode])) {
390
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
391
        }
392
393
        // throw an exception, if not
394
        throw new \Exception(
395
            sprintf(
396
                'Found invalid store view code %s in file %s on line %d',
397
                $storeViewCode,
398
                $this->getFilename(),
399
                $this->getLineNumber()
400
            )
401
        );
402
    }
403
404
    /**
405
     * Return's the tax class ID for the passed tax class name.
406
     *
407
     * @param string $taxClassName The tax class name to return the ID for
408
     *
409
     * @return integer The tax class ID
410
     * @throws \Exception Is thrown, if the tax class with the requested name is not available
411
     */
412 View Code Duplication
    public function getTaxClassIdByTaxClassName($taxClassName)
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...
413
    {
414
415
        // query whether or not, the requested tax class is available
416
        if (isset($this->taxClasses[$taxClassName])) {
417
            return (integer) $this->taxClasses[$taxClassName][MemberNames::CLASS_ID];
418
        }
419
420
        // throw an exception, if not
421
        throw new \Exception(
422
            sprintf(
423
                'Found invalid tax class name %s in file %s on line %d',
424
                $taxClassName,
425
                $this->getFilename(),
426
                $this->getLineNumber()
427
            )
428
        );
429
    }
430
431
    /**
432
     * Return's the store website for the passed code.
433
     *
434
     * @param string $code The code of the store website to return the ID for
435
     *
436
     * @return integer The store website ID
437
     * @throws \Exception Is thrown, if the store website with the requested code is not available
438
     */
439 View Code Duplication
    public function getStoreWebsiteIdByCode($code)
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...
440
    {
441
442
        // query whether or not, the requested store website is available
443
        if (isset($this->storeWebsites[$code])) {
444
            return (integer) $this->storeWebsites[$code][MemberNames::WEBSITE_ID];
445
        }
446
447
        // throw an exception, if not
448
        throw new \Exception(
449
            sprintf(
450
                'Found invalid website code %s in file %s on line %d',
451
                $code,
452
                $this->getFilename(),
453
                $this->getLineNumber()
454
            )
455
        );
456
    }
457
458
    /**
459
     * Return's the category with the passed path.
460
     *
461
     * @param string $path The path of the category to return
462
     *
463
     * @return array The category
464
     * @throws \Exception Is thrown, if the requested category is not available
465
     */
466 View Code Duplication
    public function getCategoryByPath($path)
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...
467
    {
468
469
        // query whether or not the category with the passed path exists
470
        if (isset($this->categories[$path])) {
471
            return $this->categories[$path];
472
        }
473
474
        // throw an exception, if not
475
        throw new \Exception(
476
            sprintf(
477
                'Can\'t find category with path %s in file %s on line %d',
478
                $path,
479
                $this->getFilename(),
480
                $this->getLineNumber()
481
            )
482
        );
483
    }
484
485
    /**
486
     * Return's the category with the passed ID.
487
     *
488
     * @param integer $categoryId The ID of the category to return
489
     *
490
     * @return array The category data
491
     * @throws \Exception Is thrown, if the category is not available
492
     */
493
    public function getCategory($categoryId)
494
    {
495
496
        // try to load the category with the passed ID
497
        foreach ($this->categories as $category) {
498
            if ($category[MemberNames::ENTITY_ID] == $categoryId) {
499
                return $category;
500
            }
501
        }
502
503
        // throw an exception if the category is NOT available
504
        throw new \Exception(
505
            sprintf(
506
                'Can\'t load category with ID %d in file %s on line %d',
507
                $categoryId,
508
                $this->getFilename(),
509
                $this->getLineNumber()
510
            )
511
        );
512
    }
513
514
    /**
515
     * Return's the root category for the actual view store.
516
     *
517
     * @return array The store's root category
518
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
519
     */
520
    public function getRootCategory()
521
    {
522
523
        // load the default store
524
        $defaultStore = $this->getDefaultStore();
525
526
        // load the actual store view code
527
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
528
529
        // query weather or not we've a root category or not
530
        if (isset($this->rootCategories[$storeViewCode])) {
531
            return $this->rootCategories[$storeViewCode];
532
        }
533
534
        // throw an exception if the root category is NOT available
535
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
536
    }
537
}
538