Completed
Push — 22.x ( 5d76d6...8d2f76 )
by Tim
01:38
created

VariantSuperAttributeObserver::mergeEntity()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 8
cp 0
rs 10
c 0
b 0
f 0
cc 3
nc 1
nop 4
crap 12
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\EntityStatus;
24
use TechDivision\Import\Utils\StoreViewCodes;
25
use TechDivision\Import\Utils\BackendTypeKeys;
26
use TechDivision\Import\Observers\StateDetectorInterface;
27
use TechDivision\Import\Observers\AttributeLoaderInterface;
28
use TechDivision\Import\Observers\DynamicAttributeObserverInterface;
29
use TechDivision\Import\Product\Utils\RelationTypes;
30
use TechDivision\Import\Product\Variant\Utils\ColumnKeys;
31
use TechDivision\Import\Product\Variant\Utils\MemberNames;
32
use TechDivision\Import\Product\Variant\Utils\EntityTypeCodes;
33
use TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface;
34
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
35
use Doctrine\Common\Collections\Collection;
36
37
/**
38
 * Oberserver that provides functionality for the product variant super attributes replace operation.
39
 *
40
 * @author    Tim Wagner <[email protected]>
41
 * @copyright 2020 TechDivision GmbH <[email protected]>
42
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
43
 * @link      https://github.com/techdivision/import-product-variant
44
 * @link      http://www.techdivision.com
45
 */
46
class VariantSuperAttributeObserver extends AbstractProductImportObserver implements DynamicAttributeObserverInterface
47
{
48
49
    /**
50
     * The ID of the actual store to use.
51
     *
52
     * @var integer
53
     */
54
    protected $storeId;
55
56
    /**
57
     * The EAV attribute to handle.
58
     *
59
     * @var array
60
     */
61
    protected $eavAttribute;
62
63
    /**
64
     * The tempoarary stored product super attribute ID.
65
     *
66
     * @var integer
67
     */
68
    protected $productSuperAttributeId;
69
70
    /**
71
     * The product variant processor instance.
72
     *
73
     * @var \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface
74
     */
75
    protected $productVariantProcessor;
76
77
    /**
78
     * The attribute loader instance.
79
     *
80
     * @var \TechDivision\Import\Observers\AttributeLoaderInterface
81
     */
82
    protected $attributeLoader;
83
84
    /**
85
     * The collection with entity merger instances.
86
     *
87
     * @var \Doctrine\Common\Collections\Collection
88
     */
89
    protected $entityMergers;
90
91
    /**
92
     * Initialize the "dymanmic" columns.
93
     *
94
     * @var array
95
     */
96
    protected $columns = array(
97
        EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE => array(
98
            MemberNames::POSITION    => array(ColumnKeys::VARIANT_VARIATION_POSITION, BackendTypeKeys::BACKEND_TYPE_INT)
99
        ),
100
        EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL => array(
101
            MemberNames::VALUE       => array(ColumnKeys::VARIANT_VARIATION_LABEL, BackendTypeKeys::BACKEND_TYPE_VARCHAR),
102
            MemberNames::USE_DEFAULT => array(ColumnKeys::VARIANT_VARIATION_USE_DEFAULT, BackendTypeKeys::BACKEND_TYPE_INT)
103
        )
104
    );
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 \Doctrine\Common\Collections\Collection|null                                   $entityMergers           The collection with the entity merger instances
112
     * @param \TechDivision\Import\Observers\StateDetectorInterface|null                     $stateDetector           The state detector instance
113
     */
114
    public function __construct(
115
        ProductVariantProcessorInterface $productVariantProcessor,
116
        AttributeLoaderInterface $attributeLoader = null,
117
        Collection $entityMergers = null,
118
        StateDetectorInterface $stateDetector = null
119
    ) {
120
121
        // initialize the product variant processor and the attribute loader instance
122
        $this->productVariantProcessor = $productVariantProcessor;
123
        $this->attributeLoader = $attributeLoader;
124
        $this->entityMergers = $entityMergers;
125
126
127
        // pass the state detector to the parent method
128
        parent::__construct($stateDetector);
129
    }
130
131
    /**
132
     * Return's the product variant processor instance.
133
     *
134
     * @return \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface The product variant processor instance
135
     */
136
    protected function getProductVariantProcessor()
137
    {
138
        return $this->productVariantProcessor;
139
    }
140
141
    /**
142
     * Process the observer's business logic.
143
     *
144
     * @return array The processed row
145
     */
146
    protected function process()
147
    {
148
149
        // extract the child SKU and attribute code from the row
150
        $parentSku = $this->getValue(ColumnKeys::VARIANT_PARENT_SKU);
151
        $attributeCode = $this->getValue(ColumnKeys::VARIANT_ATTRIBUTE_CODE);
152
153
        // query whether or not the super attribute has already been processed
154
        if ($this->hasBeenProcessedRelation($parentSku, $attributeCode, RelationTypes::VARIANT_SUPER_ATTRIBUTE)) {
155
            return;
156
        }
157
158
        // prepare the store view code
159
        $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...
160
161
        // preserve the parent ID
162
        $this->setParentId($this->mapParentSku($parentSku));
163
164
        try {
165
            // load the EAV attribute with the found attribute code
166
            $this->setEavAttribute($this->getEavAttributeByAttributeCode($attributeCode));
167
        } catch (\Exception $e) {
168
            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...
169
        }
170
171
        try {
172
            // initialize and save the super attribute
173
            $attr = $this->prepareDynamicAttributes(EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE, $this->prepareProductSuperAttributeAttributes());
174
            if ($this->hasChanges($productSuperAttribute = $this->initializeProductSuperAttribute($attr))) {
175
                $this->persistProductSuperAttribute($productSuperAttribute);
176
            }
177
178
            // initialize and save the super attribute label
179
            $attr = $this->prepareDynamicAttributes(EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL, $this->prepareProductSuperAttributeLabelAttributes());
180
            if ($this->hasChanges($productSuperAttributeLabel = $this->initializeProductSuperAttributeLabel($attr))) {
181
                $this->persistProductSuperAttributeLabel($productSuperAttributeLabel);
182
            }
183
184
            // mark the super attribute as processed
185
            $this->addProcessedRelation($parentSku, $attributeCode, RelationTypes::VARIANT_SUPER_ATTRIBUTE);
186
        } catch (\Exception $e) {
187
            // prepare a more detailed error message
188
            $message = $this->appendExceptionSuffix(
189
                sprintf(
190
                    'Super attribute for SKU %s and attribute %s can\'t be created',
191
                    $parentSku,
192
                    $attributeCode
193
                )
194
            );
195
196
            // if we're NOT in debug mode, re-throw a more detailed exception
197
            $wrappedException = $this->wrapException(
198
                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...
199
                new \Exception($message, null, $e)
200
            );
201
202
            // query whether or not, debug mode is enabled
203
            if ($this->isDebugMode()) {
204
                // log a warning and return immediately
205
                $this->getSystemLogger()->warning($wrappedException->getMessage());
206
                return;
207
            }
208
209
            // else, throw the exception
210
            throw $wrappedException;
211
        }
212
    }
213
214
    /**
215
     * Merge's and return's the entity with the passed attributes and set's the
216
     * passed status.
217
     *
218
     * @param array       $entity         The entity to merge the attributes into
219
     * @param array       $attr           The attributes to be merged
220
     * @param string|null $changeSetName  The change set name to use
221
     * @param string|null $entityTypeCode The entity type code to use
222
     *
223
     * @return array The merged entity
224
     * @todo https://github.com/techdivision/import/issues/179
225
     */
226
    protected function mergeEntity(array $entity, array $attr, $changeSetName = null, $entityTypeCode = null)
227
    {
228
        return array_merge(
229
            $entity,
230
            ($this->entityMergers && $this->entityMergers->containsKey($entityTypeCode)) ? $this->entityMergers->get($entityTypeCode)->merge($this, $entity, $attr) : $attr,
231
            array(EntityStatus::MEMBER_NAME => $this->detectState($entity, $attr, $changeSetName))
232
        );
233
    }
234
235
    /**
236
     * Appends the dynamic attributes to the static ones and returns them.
237
     *
238
     * @param string $entityTypeCode   The entity type code load to append the dynamic attributes for
239
     * @param array  $staticAttributes The array with the static attributes to append the dynamic to
240
     *
241
     * @return array The array with all available attributes
242
     */
243
    protected function prepareDynamicAttributes(string $entityTypeCode, array $staticAttributes) : array
244
    {
245
        return array_merge(
246
            $staticAttributes,
247
            $this->attributeLoader ? $this->attributeLoader->load($this, $this->columns[$entityTypeCode]) : array()
248
        );
249
    }
250
251
    /**
252
     * Prepare the product super attribute attributes that has to be persisted.
253
     *
254
     * @return array The prepared product attribute attributes
255
     */
256
    protected function prepareProductSuperAttributeAttributes()
257
    {
258
259
        // load the parent ID
260
        $parentId = $this->getParentId();
261
262
        // load the attribute ID and position
263
        $attributeId = $this->getAttributeId();
264
265
        // initialize the attributes and return them
266
        return $this->initializeEntity(
267
            $this->loadRawEntity(
268
                EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE,
269
                array(
270
                    MemberNames::PRODUCT_ID   => $parentId,
271
                    MemberNames::ATTRIBUTE_ID => $attributeId
272
                )
273
            )
274
        );
275
    }
276
277
    /**
278
     * Prepare the product super attribute label attributes that has to be persisted.
279
     *
280
     * @return array The prepared product super attribute label attributes
281
     */
282
    protected function prepareProductSuperAttributeLabelAttributes()
283
    {
284
285
        // extract the parent/child ID as well as option value and variation label from the row
286
        $label = $this->getValue(ColumnKeys::VARIANT_VARIATION_LABEL);
287
        $useDefault = $this->getValue(ColumnKeys::VARIANT_VARIATION_USE_DEFAULT, 0);
288
289
        // query whether or not we've to create super attribute labels
290
        if (empty($label)) {
291
            $label = $this->getFrontendLabel();
292
        }
293
294
        // initialize the attributes and return them
295
        return $this->initializeEntity(
296
            $this->loadRawEntity(
297
                EntityTypeCodes::CATALOG_PRODUCT_SUPER_ATTRIBUTE_LABEL,
298
                array(
299
                    MemberNames::PRODUCT_SUPER_ATTRIBUTE_ID => $this->getProductSuperAttributeId(),
300
                    MemberNames::STORE_ID                   => $this->getRowStoreId(StoreViewCodes::ADMIN),
301
                    MemberNames::USE_DEFAULT                => $useDefault,
302
                    MemberNames::VALUE                      => $label
303
                )
304
            )
305
        );
306
    }
307
308
    /**
309
     * Load's and return's a raw entity without primary key but the mandatory members only and nulled values.
310
     *
311
     * @param string $entityTypeCode The entity type code to return the raw entity for
312
     * @param array  $data           An array with data that will be used to initialize the raw entity with
313
     *
314
     * @return array The initialized entity
315
     */
316
    protected function loadRawEntity($entityTypeCode, array $data = array())
317
    {
318
        return $this->getProductVariantProcessor()->loadRawEntity($entityTypeCode, $data);
319
    }
320
321
    /**
322
     * Initialize the product super attribute with the passed attributes and returns an instance.
323
     *
324
     * @param array $attr The product super attribute attributes
325
     *
326
     * @return array The initialized product super attribute
327
     */
328
    protected function initializeProductSuperAttribute(array $attr)
329
    {
330
        return $attr;
331
    }
332
333
    /**
334
     * Initialize the product super attribute label with the passed attributes and returns an instance.
335
     *
336
     * @param array $attr The product super attribute label attributes
337
     *
338
     * @return array The initialized product super attribute label
339
     */
340
    protected function initializeProductSuperAttributeLabel(array $attr)
341
    {
342
        return $attr;
343
    }
344
345
    /**
346
     * Set's the actual EAV attribute.
347
     *
348
     * @param array $eavAttribute The actual EAV attribute
349
     *
350
     * @return void
351
     */
352
    protected function setEavAttribute(array $eavAttribute)
353
    {
354
        $this->eavAttribute = $eavAttribute;
355
    }
356
357
    /**
358
     * Return's the actual EAV attribute.
359
     *
360
     * @return array The actual EAV attribute
361
     */
362
    protected function getEavAttribute()
363
    {
364
        return $this->eavAttribute;
365
    }
366
367
    /**
368
     * Return's the frontend label from the actual EAV attribute.
369
     *
370
     * @return string The frontend label
371
     */
372
    protected function getFrontendLabel()
373
    {
374
        return $this->eavAttribute[MemberNames::FRONTENT_LABEL];
375
    }
376
377
    /**
378
     * Return's the attribute ID from the actual EAV attribute.
379
     *
380
     * @return integer The attribute ID
381
     */
382
    protected function getAttributeId()
383
    {
384
        return $this->eavAttribute[MemberNames::ATTRIBUTE_ID];
385
    }
386
387
    /**
388
     * Set's the actual product super attribute ID.
389
     *
390
     * @param integer $productSuperAttributeId The product super attribute ID
391
     *
392
     * @return void
393
     */
394
    protected function setProductSuperAttributeId($productSuperAttributeId)
395
    {
396
        $this->productSuperAttributeId = $productSuperAttributeId;
397
    }
398
399
    /**
400
     * Return's the product super attribute ID.
401
     *
402
     * @return integer The product super attribute ID
403
     */
404
    protected function getProductSuperAttributeId()
405
    {
406
        return $this->productSuperAttributeId;
407
    }
408
409
    /**
410
     * Map's the passed SKU of the parent product to it's PK.
411
     *
412
     * @param string $parentSku The SKU of the parent product
413
     *
414
     * @return integer The primary key used to create relations
415
     */
416
    protected function mapParentSku($parentSku)
417
    {
418
        return $this->mapSkuToEntityId($parentSku);
419
    }
420
421
    /**
422
     * Return the entity ID for the passed SKU.
423
     *
424
     * @param string $sku The SKU to return the entity ID for
425
     *
426
     * @return integer The mapped entity ID
427
     * @throws \Exception Is thrown if the SKU is not mapped yet
428
     */
429
    protected function mapSkuToEntityId($sku)
430
    {
431
        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...
432
    }
433
434
    /**
435
     * Set's the ID of the parent product to relate the variant with.
436
     *
437
     * @param integer $parentId The ID of the parent product
438
     *
439
     * @return void
440
     */
441
    protected function setParentId($parentId)
442
    {
443
        $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...
444
    }
445
446
    /**
447
     * Return's the ID of the parent product to relate the variant with.
448
     *
449
     * @return integer The ID of the parent product
450
     */
451
    protected function getParentId()
452
    {
453
        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...
454
    }
455
456
    /**
457
     * Return's the store for the passed store code.
458
     *
459
     * @param string $storeCode The store code to return the store for
460
     *
461
     * @return array The requested store
462
     * @throws \Exception Is thrown, if the requested store is not available
463
     */
464
    protected function getStoreByStoreCode($storeCode)
465
    {
466
        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...
467
    }
468
469
    /**
470
     * Return's an array with the available stores.
471
     *
472
     * @return array The available stores
473
     */
474
    protected function getStores()
475
    {
476
        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...
477
    }
478
479
    /**
480
     * Return's the first EAV attribute for the passed attribute code.
481
     *
482
     * @param string $attributeCode The attribute code
483
     *
484
     * @return array The array with the EAV attribute
485
     */
486
    protected function getEavAttributeByAttributeCode($attributeCode)
487
    {
488
        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...
489
    }
490
491
    /**
492
     * Persist's the passed product super attribute data and return's the ID.
493
     *
494
     * @param array $productSuperAttribute The product super attribute data to persist
495
     *
496
     * @return void
497
     */
498
    protected function persistProductSuperAttribute($productSuperAttribute)
499
    {
500
        $this->setProductSuperAttributeId($this->getProductVariantProcessor()->persistProductSuperAttribute($productSuperAttribute));
501
    }
502
503
    /**
504
     * Persist's the passed product super attribute label data and return's the ID.
505
     *
506
     * @param array $productSuperAttributeLabel The product super attribute label data to persist
507
     *
508
     * @return void
509
     */
510
    protected function persistProductSuperAttributeLabel($productSuperAttributeLabel)
511
    {
512
        return $this->getProductVariantProcessor()->persistProductSuperAttributeLabel($productSuperAttributeLabel);
513
    }
514
}
515