Completed
Push — 22.x ( a5931f )
by Tim
06:21
created

VariantSuperAttributeObserver::loadRawEntity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Variant\Observers\VariantSuperAttributeObserver
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 2020 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-variant
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Variant\Observers;
22
23
use TechDivision\Import\Utils\StoreViewCodes;
24
use TechDivision\Import\Utils\BackendTypeKeys;
25
use TechDivision\Import\Observers\StateDetectorInterface;
26
use TechDivision\Import\Observers\AttributeLoaderInterface;
27
use TechDivision\Import\Product\Utils\RelationTypes;
28
use TechDivision\Import\Product\Variant\Utils\ColumnKeys;
29
use TechDivision\Import\Product\Variant\Utils\MemberNames;
30
use TechDivision\Import\Product\Variant\Utils\EntityTypeCodes;
31
use TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface;
32
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
33
use TechDivision\Import\Observers\DynamicAttributeObserverInterface;
34
35
/**
36
 * Oberserver that provides functionality for the product variant super attributes replace operation.
37
 *
38
 * @author    Tim Wagner <[email protected]>
39
 * @copyright 2020 TechDivision GmbH <[email protected]>
40
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
41
 * @link      https://github.com/techdivision/import-product-variant
42
 * @link      http://www.techdivision.com
43
 */
44
class VariantSuperAttributeObserver extends AbstractProductImportObserver implements DynamicAttributeObserverInterface
45
{
46
47
    /**
48
     * The ID of the actual store to use.
49
     *
50
     * @var integer
51
     */
52
    protected $storeId;
53
54
    /**
55
     * The EAV attribute to handle.
56
     *
57
     * @var array
58
     */
59
    protected $eavAttribute;
60
61
    /**
62
     * The tempoarary stored product super attribute ID.
63
     *
64
     * @var integer
65
     */
66
    protected $productSuperAttributeId;
67
68
    /**
69
     * The product variant processor instance.
70
     *
71
     * @var \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface
72
     */
73
    protected $productVariantProcessor;
74
75
    /**
76
     * The attribute loader instance.
77
     *
78
     * @var \TechDivision\Import\Observers\AttributeLoaderInterface
79
     */
80
    protected $attributeLoader;
81
82
    /**
83
     * Initialize the "dymanmic" columns.
84
     *
85
     * @var array
86
     */
87
    protected $columns = array(
88
        EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE => array(
89
            MemberNames::POSITION    => array(ColumnKeys::VARIANT_VARIATION_POSITION, BackendTypeKeys::BACKEND_TYPE_INT)
90
        ),
91
        EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL => array(
92
            MemberNames::VALUE       => array(ColumnKeys::VARIANT_VARIATION_LABEL, BackendTypeKeys::BACKEND_TYPE_VARCHAR),
93
            MemberNames::USE_DEFAULT => array(ColumnKeys::VARIANT_VARIATION_USE_DEFAULT, BackendTypeKeys::BACKEND_TYPE_INT)
94
        )
95
    );
96
97
    /**
98
     * Array with virtual column name mappings (this is a temporary
99
     * solution till techdivision/import#179 as been implemented).
100
     *
101
     * @var array
102
     * @todo https://github.com/techdivision/import/issues/179
103
     */
104
    protected $virtualMapping = array(ColumnKeys::POSITION => ColumnKeys::VARIANT_VARIATION_POSITION);
105
106
    /**
107
     * Initialize the observer with the passed product variant processor instance.
108
     *
109
     * @param \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface $productVariantProcessor The product variant processor instance
110
     * @param \TechDivision\Import\Observers\AttributeLoaderInterface|null                   $attributeLoader         The attribute loader instance
111
     * @param \TechDivision\Import\Observers\StateDetectorInterface|null                     $stateDetector           The state detector instance
112
     */
113
    public function __construct(
114
        ProductVariantProcessorInterface $productVariantProcessor,
115
        AttributeLoaderInterface $attributeLoader = null,
116
        StateDetectorInterface $stateDetector = null
117
    ) {
118
119
        // initialize the product variant processor and the attribute loader instance
120
        $this->attributeLoader = $attributeLoader;
121
        $this->productVariantProcessor = $productVariantProcessor;
122
123
        // pass the state detector to the parent method
124
        parent::__construct($stateDetector);
125
    }
126
127
    /**
128
     * Return's the product variant processor instance.
129
     *
130
     * @return \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface The product variant processor instance
131
     */
132
    protected function getProductVariantProcessor()
133
    {
134
        return $this->productVariantProcessor;
135
    }
136
137
    /**
138
     * Query whether or not a value for the column with the passed name exists.
139
     *
140
     * @param string $name The column name to query for a valid value
141
     *
142
     * @return boolean TRUE if the value is set, else FALSE
143
     * @todo https://github.com/techdivision/import/issues/179
144
     */
145
    public function hasValue($name)
146
    {
147
        return parent::hasValue(isset($this->virtualMapping[$name]) ? $this->virtualMapping[$name] : $name);
148
    }
149
150
    /**
151
     * Process the observer's business logic.
152
     *
153
     * @return array The processed row
154
     */
155
    protected function process()
156
    {
157
158
        // extract the child SKU and attribute code from the row
159
        $parentSku = $this->getValue(ColumnKeys::VARIANT_PARENT_SKU);
160
        $attributeCode = $this->getValue(ColumnKeys::VARIANT_ATTRIBUTE_CODE);
161
162
        // query whether or not the super attribute has already been processed
163
        if ($this->hasBeenProcessedRelation($parentSku, $attributeCode, RelationTypes::VARIANT_SUPER_ATTRIBUTE)) {
164
            return;
165
        }
166
167
        // prepare the store view code
168
        $this->prepareStoreViewCode($this->getRow());
0 ignored issues
show
Unused Code introduced by
The call to VariantSuperAttributeObs...:prepareStoreViewCode() has too many arguments starting with $this->getRow().

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...
169
170
        // preserve the parent ID
171
        $this->setParentId($this->mapParentSku($parentSku));
172
173
        try {
174
            // load the EAV attribute with the found attribute code
175
            $this->setEavAttribute($this->getEavAttributeByAttributeCode($attributeCode));
176
        } catch (\Exception $e) {
177
            throw $this->wrapException(array(ColumnKeys::VARIANT_ATTRIBUTE_CODE), $e);
0 ignored issues
show
Documentation introduced by
array(\TechDivision\Impo...VARIANT_ATTRIBUTE_CODE) is of type array<integer,?>, but the function expects a string.

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...
178
        }
179
180
        try {
181
            // initialize and save the super attribute
182
            $attr = $this->prepareDynamicAttributes(EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE, $this->prepareProductSuperAttributeAttributes());
183
            if ($this->hasChanges($productSuperAttribute = $this->initializeProductSuperAttribute($attr))) {
184
                $this->persistProductSuperAttribute($productSuperAttribute);
185
            }
186
187
            // initialize and save the super attribute label
188
            $attr = $this->prepareDynamicAttributes(EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL, $this->prepareProductSuperAttributeLabelAttributes());
189
            if ($this->hasChanges($productSuperAttributeLabel = $this->initializeProductSuperAttributeLabel($attr))) {
190
                $this->persistProductSuperAttributeLabel($productSuperAttributeLabel);
191
            }
192
193
            // mark the super attribute as processed
194
            $this->addProcessedRelation($parentSku, $attributeCode, RelationTypes::VARIANT_SUPER_ATTRIBUTE);
195
        } catch (\Exception $e) {
196
            // prepare a more detailed error message
197
            $message = $this->appendExceptionSuffix(
198
                sprintf(
199
                    'Super attribute for SKU %s and attribute %s can\'t be created',
200
                    $parentSku,
201
                    $attributeCode
202
                )
203
            );
204
205
            // if we're NOT in debug mode, re-throw a more detailed exception
206
            $wrappedException = $this->wrapException(
207
                array(ColumnKeys::VARIANT_PARENT_SKU, ColumnKeys::VARIANT_ATTRIBUTE_CODE),
0 ignored issues
show
Documentation introduced by
array(\TechDivision\Impo...VARIANT_ATTRIBUTE_CODE) is of type array<integer,?>, but the function expects a string.

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...
208
                new \Exception($message, null, $e)
209
            );
210
211
            // query whether or not, debug mode is enabled
212
            if ($this->isDebugMode()) {
213
                // log a warning and return immediately
214
                $this->getSystemLogger()->warning($wrappedException->getMessage());
215
                return;
216
            }
217
218
            // else, throw the exception
219
            throw $wrappedException;
220
        }
221
    }
222
223
    /**
224
     * Appends the dynamic attributes to the static ones and returns them.
225
     *
226
     * @param string $entityTypeCode   The entity type code load to append the dynamic attributes for
227
     * @param array  $staticAttributes The array with the static attributes to append the dynamic to
228
     *
229
     * @return array The array with all available attributes
230
     */
231
    protected function prepareDynamicAttributes(string $entityTypeCode, array $staticAttributes) : array
232
    {
233
        return array_merge(
234
            $staticAttributes,
235
            $this->attributeLoader ? $this->attributeLoader->load($this, $this->columns[$entityTypeCode]) : array()
236
        );
237
    }
238
239
    /**
240
     * Prepare the product super attribute attributes that has to be persisted.
241
     *
242
     * @return array The prepared product attribute attributes
243
     */
244
    protected function prepareProductSuperAttributeAttributes()
245
    {
246
247
        // load the parent ID
248
        $parentId = $this->getParentId();
249
250
        // load the attribute ID and position
251
        $attributeId = $this->getAttributeId();
252
253
        // initialize the attributes and return them
254
        return $this->initializeEntity(
255
            $this->loadRawEntity(
256
                EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE,
257
                array(
258
                    MemberNames::PRODUCT_ID   => $parentId,
259
                    MemberNames::ATTRIBUTE_ID => $attributeId
260
                )
261
            )
262
        );
263
    }
264
265
    /**
266
     * Prepare the product super attribute label attributes that has to be persisted.
267
     *
268
     * @return array The prepared product super attribute label attributes
269
     */
270
    protected function prepareProductSuperAttributeLabelAttributes()
271
    {
272
273
        // extract the parent/child ID as well as option value and variation label from the row
274
        $label = $this->getValue(ColumnKeys::VARIANT_VARIATION_LABEL);
275
        $useDefault = $this->getValue(ColumnKeys::VARIANT_VARIATION_USE_DEFAULT, 0);
276
277
        // query whether or not we've to create super attribute labels
278
        if (empty($label)) {
279
            $label = $this->getFrontendLabel();
280
        }
281
282
        // initialize the attributes and return them
283
        return $this->initializeEntity(
284
            $this->loadRawEntity(
285
                EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL,
286
                array(
287
                    MemberNames::PRODUCT_SUPER_ATTRIBUTE_ID => $this->getProductSuperAttributeId(),
288
                    MemberNames::STORE_ID                   => $this->getRowStoreId(StoreViewCodes::ADMIN),
289
                    MemberNames::USE_DEFAULT                => $useDefault,
290
                    MemberNames::VALUE                      => $label
291
                )
292
            )
293
        );
294
    }
295
296
    /**
297
     * Load's and return's a raw entity without primary key but the mandatory members only and nulled values.
298
     *
299
     * @param string $entityTypeCode The entity type code to return the raw entity for
300
     * @param array  $data           An array with data that will be used to initialize the raw entity with
301
     *
302
     * @return array The initialized entity
303
     */
304
    protected function loadRawEntity($entityTypeCode, array $data = array())
305
    {
306
        return $this->getProductVariantProcessor()->loadRawEntity($entityTypeCode, $data);
307
    }
308
309
    /**
310
     * Initialize the product super attribute with the passed attributes and returns an instance.
311
     *
312
     * @param array $attr The product super attribute attributes
313
     *
314
     * @return array The initialized product super attribute
315
     */
316
    protected function initializeProductSuperAttribute(array $attr)
317
    {
318
        return $attr;
319
    }
320
321
    /**
322
     * Initialize the product super attribute label with the passed attributes and returns an instance.
323
     *
324
     * @param array $attr The product super attribute label attributes
325
     *
326
     * @return array The initialized product super attribute label
327
     */
328
    protected function initializeProductSuperAttributeLabel(array $attr)
329
    {
330
        return $attr;
331
    }
332
333
    /**
334
     * Set's the actual EAV attribute.
335
     *
336
     * @param array $eavAttribute The actual EAV attribute
337
     *
338
     * @return void
339
     */
340
    protected function setEavAttribute(array $eavAttribute)
341
    {
342
        $this->eavAttribute = $eavAttribute;
343
    }
344
345
    /**
346
     * Return's the actual EAV attribute.
347
     *
348
     * @return array The actual EAV attribute
349
     */
350
    protected function getEavAttribute()
351
    {
352
        return $this->eavAttribute;
353
    }
354
355
    /**
356
     * Return's the frontend label from the actual EAV attribute.
357
     *
358
     * @return string The frontend label
359
     */
360
    protected function getFrontendLabel()
361
    {
362
        return $this->eavAttribute[MemberNames::FRONTENT_LABEL];
363
    }
364
365
    /**
366
     * Return's the attribute ID from the actual EAV attribute.
367
     *
368
     * @return integer The attribute ID
369
     */
370
    protected function getAttributeId()
371
    {
372
        return $this->eavAttribute[MemberNames::ATTRIBUTE_ID];
373
    }
374
375
    /**
376
     * Set's the actual product super attribute ID.
377
     *
378
     * @param integer $productSuperAttributeId The product super attribute ID
379
     *
380
     * @return void
381
     */
382
    protected function setProductSuperAttributeId($productSuperAttributeId)
383
    {
384
        $this->productSuperAttributeId = $productSuperAttributeId;
385
    }
386
387
    /**
388
     * Return's the product super attribute ID.
389
     *
390
     * @return integer The product super attribute ID
391
     */
392
    protected function getProductSuperAttributeId()
393
    {
394
        return $this->productSuperAttributeId;
395
    }
396
397
    /**
398
     * Map's the passed SKU of the parent product to it's PK.
399
     *
400
     * @param string $parentSku The SKU of the parent product
401
     *
402
     * @return integer The primary key used to create relations
403
     */
404
    protected function mapParentSku($parentSku)
405
    {
406
        return $this->mapSkuToEntityId($parentSku);
407
    }
408
409
    /**
410
     * Return the entity ID for the passed SKU.
411
     *
412
     * @param string $sku The SKU to return the entity ID for
413
     *
414
     * @return integer The mapped entity ID
415
     * @throws \Exception Is thrown if the SKU is not mapped yet
416
     */
417
    protected function mapSkuToEntityId($sku)
418
    {
419
        return $this->getSubject()->mapSkuToEntityId($sku);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method mapSkuToEntityId() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Prod...Subjects\VariantSubject.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
420
    }
421
422
    /**
423
     * Set's the ID of the parent product to relate the variant with.
424
     *
425
     * @param integer $parentId The ID of the parent product
426
     *
427
     * @return void
428
     */
429
    protected function setParentId($parentId)
430
    {
431
        $this->getSubject()->setParentId($parentId);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method setParentId() does only exist in the following implementations of said interface: TechDivision\Import\Prod...Subjects\VariantSubject.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
432
    }
433
434
    /**
435
     * Return's the ID of the parent product to relate the variant with.
436
     *
437
     * @return integer The ID of the parent product
438
     */
439
    protected function getParentId()
440
    {
441
        return $this->getSubject()->getParentId();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getParentId() does only exist in the following implementations of said interface: TechDivision\Import\Prod...Subjects\VariantSubject.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
442
    }
443
444
    /**
445
     * Return's the store for the passed store code.
446
     *
447
     * @param string $storeCode The store code to return the store for
448
     *
449
     * @return array The requested store
450
     * @throws \Exception Is thrown, if the requested store is not available
451
     */
452
    protected function getStoreByStoreCode($storeCode)
453
    {
454
        return $this->getSubject()->getStoreByStoreCode($storeCode);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getStoreByStoreCode() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Prod...Subjects\VariantSubject.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
455
    }
456
457
    /**
458
     * Return's an array with the available stores.
459
     *
460
     * @return array The available stores
461
     */
462
    protected function getStores()
463
    {
464
        return $this->getSubject()->getStores();
0 ignored issues
show
Bug introduced by
The method getStores() does not seem to exist on object<TechDivision\Impo...jects\SubjectInterface>.

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...
465
    }
466
467
    /**
468
     * Return's the first EAV attribute for the passed attribute code.
469
     *
470
     * @param string $attributeCode The attribute code
471
     *
472
     * @return array The array with the EAV attribute
473
     */
474
    protected function getEavAttributeByAttributeCode($attributeCode)
475
    {
476
        return $this->getSubject()->getEavAttributeByAttributeCode($attributeCode);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getEavAttributeByAttributeCode() does only exist in the following implementations of said interface: TechDivision\Import\Observers\EntitySubjectImpl, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Prod...Subjects\VariantSubject, TechDivision\Import\Subjects\AbstractEavSubject, TechDivision\Import\Subjects\ValidatorSubject.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
477
    }
478
479
    /**
480
     * Persist's the passed product super attribute data and return's the ID.
481
     *
482
     * @param array $productSuperAttribute The product super attribute data to persist
483
     *
484
     * @return void
485
     */
486
    protected function persistProductSuperAttribute($productSuperAttribute)
487
    {
488
        $this->setProductSuperAttributeId($this->getProductVariantProcessor()->persistProductSuperAttribute($productSuperAttribute));
489
    }
490
491
    /**
492
     * Persist's the passed product super attribute label data and return's the ID.
493
     *
494
     * @param array $productSuperAttributeLabel The product super attribute label data to persist
495
     *
496
     * @return void
497
     */
498
    protected function persistProductSuperAttributeLabel($productSuperAttributeLabel)
499
    {
500
        return $this->getProductVariantProcessor()->persistProductSuperAttributeLabel($productSuperAttributeLabel);
501
    }
502
}
503