Completed
Push — 23.x ( b390a3 )
by Tim
04:13
created

MediaGalleryObserver::hasValue()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 1
nop 1
crap 6
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Media\Observers\MediaGalleryObserver
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-media
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Media\Observers;
22
23
use TechDivision\Import\Utils\BackendTypeKeys;
24
use TechDivision\Import\Product\Media\Utils\ColumnKeys;
25
use TechDivision\Import\Product\Media\Utils\MemberNames;
26
use TechDivision\Import\Product\Media\Utils\EntityTypeCodes;
27
use TechDivision\Import\Product\Media\Services\ProductMediaProcessorInterface;
28
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
29
use TechDivision\Import\Observers\StateDetectorInterface;
30
use TechDivision\Import\Observers\AttributeLoaderInterface;
31
use TechDivision\Import\Observers\DynamicAttributeObserverInterface;
32
33
/**
34
 * Observer that creates/updates the product's media gallery information.
35
 *
36
 * @author    Tim Wagner <[email protected]>
37
 * @copyright 2016 TechDivision GmbH <[email protected]>
38
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
39
 * @link      https://github.com/techdivision/import-product-media
40
 * @link      http://www.techdivision.com
41
 */
42
class MediaGalleryObserver extends AbstractProductImportObserver implements DynamicAttributeObserverInterface
43
{
44
45
    /**
46
     * The media gallery attribute code.
47
     *
48
     * @var string
49
     */
50
    const ATTRIBUTE_CODE = 'media_gallery';
51
52
    /**
53
     * The ID of the parent product the media is related to.
54
     *
55
     * @var integer
56
     */
57
    protected $parentId;
58
59
    /**
60
     * The ID of the persisted media gallery entity.
61
     *
62
     * @var integer
63
     */
64
    protected $valueId;
65
66
    /**
67
     * The product media processor instance.
68
     *
69
     * @var \TechDivision\Import\Product\Media\Services\ProductMediaProcessorInterface
70
     */
71
    protected $productMediaProcessor;
72
73
    /**
74
     * The attribute loader instance.
75
     *
76
     * @var \TechDivision\Import\Observers\AttributeLoaderInterface
77
     */
78
    protected $attributeLoader;
79
80
    /**
81
     * Initialize the "dymanmic" columns.
82
     *
83
     * @var array
84
     */
85
    protected $columns = array(
86
        EntityTypeCodes::CATALOG_PRODUCT_MEDIA_GALLERY_VALUE_TO_ENTITY => array(),
87
        EntityTypeCodes::CATALOG_PRODUCT_MEDIA_GALLERY => array(
88
            MemberNames::DISABLED => array(ColumnKeys::IMAGE_DISABLED, BackendTypeKeys::BACKEND_TYPE_INT),
89
            MemberNames::MEDIA_TYPE => array(ColumnKeys::MEDIA_TYPE, BackendTypeKeys::BACKEND_TYPE_VARCHAR)
90
        )
91
    );
92
93
    /**
94
     * Array with virtual column name mappings (this is a temporary
95
     * solution till techdivision/import#179 as been implemented).
96
     *
97
     * @var array
98
     * @todo https://github.com/techdivision/import/issues/179
99
     */
100
    protected $virtualMapping = array(MemberNames::DISABLED => ColumnKeys::IMAGE_DISABLED);
101
102
    /**
103
     * Initialize the observer with the passed product media processor instance.
104
     *
105
     * @param \TechDivision\Import\Product\Media\Services\ProductMediaProcessorInterface $productMediaProcessor The product media processor instance
106
     * @param \TechDivision\Import\Observers\AttributeLoaderInterface                    $attributeLoader       The attribute loader instance
107
     * @param \TechDivision\Import\Observers\StateDetectorInterface|null                 $stateDetector         The state detector instance to use
108
     */
109 View Code Duplication
    public function __construct(
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...
110
        ProductMediaProcessorInterface $productMediaProcessor,
111
        AttributeLoaderInterface $attributeLoader = null,
112
        StateDetectorInterface $stateDetector = null
113
    ) {
114
115
        // initialize the media processor and the dynamic attribute loader instance
116
        $this->productMediaProcessor = $productMediaProcessor;
117
        $this->attributeLoader = $attributeLoader;
118
119
        // pass the state detector to the parent method
120
        parent::__construct($stateDetector);
121
    }
122
123
    /**
124
     * Return's the product media processor instance.
125
     *
126
     * @return \TechDivision\Import\Product\Media\Services\ProductMediaProcessorInterface The product media processor instance
127
     */
128
    protected function getProductMediaProcessor()
129
    {
130
        return $this->productMediaProcessor;
131
    }
132
133
    /**
134
     * Query whether or not a value for the column with the passed name exists.
135
     *
136
     * @param string $name The column name to query for a valid value
137
     *
138
     * @return boolean TRUE if the value is set, else FALSE
139
     * @todo https://github.com/techdivision/import/issues/179
140
     */
141
    public function hasValue($name)
142
    {
143
        return parent::hasValue(isset($this->virtualMapping[$name]) ? $this->virtualMapping[$name] : $name);
144
    }
145
146
    /**
147
     * Process the observer's business logic.
148
     *
149
     * @return array The processed row
150
     */
151
    protected function process()
152
    {
153
154
        // try to load the product SKU and map it the entity ID and
155
        $this->parentId = $this->getValue(ColumnKeys::IMAGE_PARENT_SKU, null, array($this, 'mapParentSku'));
156
157
        // prepare the actual store view code
158
        $this->prepareStoreViewCode($this->getRow());
0 ignored issues
show
Unused Code introduced by
The call to MediaGalleryObserver::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...
159
160
        // initialize and persist the product media gallery
161
        $attr = $this->prepareDynamicMediaGalleryAttributes(EntityTypeCodes::CATALOG_PRODUCT_MEDIA_GALLERY, $this->prepareProductMediaGalleryAttributes());
162
        if ($this->hasChanges($productMediaGallery = $this->initializeProductMediaGallery($attr))) {
163
            // persist the media gallery data and temporarily persist value ID
164
            $this->setParentValueId($this->valueId = $this->persistProductMediaGallery($productMediaGallery));
0 ignored issues
show
Documentation Bug introduced by
The property $valueId was declared of type integer, but $this->persistProductMed...y($productMediaGallery) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
165
            // persist the product media gallery to entity data
166
            $attr = $this->prepareDynamicMediaGalleryAttributes(EntityTypeCodes::CATALOG_PRODUCT_MEDIA_GALLERY_VALUE_TO_ENTITY, $this->prepareProductMediaGalleryValueToEntityAttributes());
167
            if ($productMediaGalleryValueToEntity = $this->initializeProductMediaGalleryValueToEntity($attr)) {
168
                $this->persistProductMediaGalleryValueToEntity($productMediaGalleryValueToEntity);
169
            }
170
        }
171
172
        // temporarily persist parent ID
173
        $this->setParentId($this->parentId);
174
    }
175
176
    /**
177
     * Appends the dynamic attributes to the static ones and returns them.
178
     *
179
     * @param string $entityTypeCode   The entity type code load to append the dynamic attributes for
180
     * @param array  $staticAttributes The array with the static attributes to append the dynamic to
181
     *
182
     * @return array The array with all available attributes
183
     */
184
    protected function prepareDynamicMediaGalleryAttributes(string $entityTypeCode, array $staticAttributes) : array
185
    {
186
        return array_merge(
187
            $staticAttributes,
188
            $this->attributeLoader ? $this->attributeLoader->load($this, $this->columns[$entityTypeCode]) : array()
189
        );
190
    }
191
192
    /**
193
     * Prepare the product media gallery that has to be persisted.
194
     *
195
     * @return array The prepared product media gallery attributes
196
     */
197
    protected function prepareProductMediaGalleryAttributes()
198
    {
199
200
        // load the attribute ID of the media gallery EAV attribute
201
        $mediaGalleryAttribute = $this->getEavAttributeByAttributeCode(MediaGalleryObserver::ATTRIBUTE_CODE);
202
        $attributeId = $mediaGalleryAttribute[MemberNames::ATTRIBUTE_ID];
203
204
        // initialize the gallery data
205
        $image = $this->getValue(ColumnKeys::IMAGE_PATH_NEW);
206
207
        // initialize and return the entity
208
        return $this->initializeEntity(
209
            $this->loadRawEntity(
210
                EntityTypeCodes::CATALOG_PRODUCT_MEDIA_GALLERY,
211
                array(
212
                    MemberNames::ATTRIBUTE_ID => $attributeId,
213
                    MemberNames::VALUE        => $image
214
                )
215
            )
216
        );
217
    }
218
219
    /**
220
     * Prepare the product media gallery value to entity that has to be persisted.
221
     *
222
     * @return array The prepared product media gallery value to entity attributes
223
     */
224
    protected function prepareProductMediaGalleryValueToEntityAttributes()
225
    {
226
227
        // initialize and return the entity
228
        return $this->initializeEntity(
229
            $this->loadRawEntity(
230
                EntityTypeCodes::CATALOG_PRODUCT_MEDIA_GALLERY_VALUE_TO_ENTITY,
231
                array(
232
                    MemberNames::VALUE_ID  => $this->valueId,
233
                    MemberNames::ENTITY_ID => $this->parentId
234
                )
235
            )
236
        );
237
    }
238
239
    /**
240
     * Load's and return's a raw entity without primary key but the mandatory members only and nulled values.
241
     *
242
     * @param string $entityTypeCode The entity type code to return the raw entity for
243
     * @param array  $data           An array with data that will be used to initialize the raw entity with
244
     *
245
     * @return array The initialized entity
246
     */
247
    protected function loadRawEntity($entityTypeCode, array $data = array())
248
    {
249
        return $this->getProductMediaProcessor()->loadRawEntity($entityTypeCode, $data);
250
    }
251
252
    /**
253
     * Initialize the product media gallery with the passed attributes and returns an instance.
254
     *
255
     * @param array $attr The product media gallery attributes
256
     *
257
     * @return array The initialized product media gallery
258
     */
259
    protected function initializeProductMediaGallery(array $attr)
260
    {
261
        return $attr;
262
    }
263
264
    /**
265
     * Initialize the product media gallery value to entity with the passed attributes and returns an instance.
266
     *
267
     * @param array $attr The product media gallery value to entity attributes
268
     *
269
     * @return array|null The initialized product media gallery value to entity, or NULL if the product media gallery value to entity already exists
270
     */
271
    protected function initializeProductMediaGalleryValueToEntity(array $attr)
272
    {
273
        return $attr;
274
    }
275
276
    /**
277
     * Map's the passed SKU of the parent product to it's PK.
278
     *
279
     * @param string $parentSku The SKU of the parent product
280
     *
281
     * @return integer The primary key used to create relations
282
     */
283
    protected function mapParentSku($parentSku)
284
    {
285
        return $this->mapSkuToEntityId($parentSku);
286
    }
287
288
    /**
289
     * Set's the value ID of the created media gallery entry.
290
     *
291
     * @param integer $parentValueId The ID of the created media gallery entry
292
     *
293
     * @return void
294
     */
295
    protected function setParentValueId($parentValueId)
296
    {
297
        $this->getSubject()->setParentValueId($parentValueId);
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 setParentValueId() does only exist in the following implementations of said interface: TechDivision\Import\Prod...a\Subjects\MediaSubject.

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...
298
    }
299
300
    /**
301
     * Return the entity ID for the passed SKU.
302
     *
303
     * @param string $sku The SKU to return the entity ID for
304
     *
305
     * @return integer The mapped entity ID
306
     * @throws \Exception Is thrown if the SKU is not mapped yet
307
     */
308
    protected function mapSkuToEntityId($sku)
309
    {
310
        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...a\Subjects\MediaSubject, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject.

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...
311
    }
312
313
    /**
314
     * Set's the ID of the parent product to relate the variant with.
315
     *
316
     * @param integer $parentId The ID of the parent product
317
     *
318
     * @return void
319
     */
320
    protected function setParentId($parentId)
321
    {
322
        $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...a\Subjects\MediaSubject.

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...
323
    }
324
325
    /**
326
     * Return's the ID of the parent product to relate the variant with.
327
     *
328
     * @return integer The ID of the parent product
329
     */
330
    protected function getParentId()
331
    {
332
        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...a\Subjects\MediaSubject.

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...
333
    }
334
335
    /**
336
     * Query whether or not this is the parent ID.
337
     *
338
     * @param integer $parentId The PK of the parent image
339
     *
340
     * @return boolean TRUE if the PK euqals, else FALSE
341
     */
342
    protected function isParentId($parentId)
343
    {
344
        return $this->getParentId() === $parentId;
345
    }
346
347
    /**
348
     * Query whether or not this is the parent store view code.
349
     *
350
     * @param string $storeViewCode The actual store view code
351
     *
352
     * @return boolean TRUE if the store view code equals, else FALSE
353
     */
354
    protected function isParentStoreViewCode($storeViewCode)
355
    {
356
        return $this->getStoreViewCode() === $storeViewCode;
357
    }
358
359
    /**
360
     * Return's the default store view code.
361
     *
362
     * @return array The default store view code
363
     */
364
    protected function getDefaultStoreViewCode()
365
    {
366
        return $this->getSubject()->getDefaultStoreViewCode();
367
    }
368
369
    /**
370
     * Reset the position counter to 1.
371
     *
372
     * @return void
373
     * @deprecated Since 23.0.0
374
     */
375
    protected function resetPositionCounter()
376
    {
377
        $this->getSubject()->resetPositionCounter();
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 resetPositionCounter() does only exist in the following implementations of said interface: TechDivision\Import\Prod...a\Subjects\MediaSubject.

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...
378
    }
379
380
    /**
381
     * Return's the EAV attribute with the passed attribute code.
382
     *
383
     * @param string $attributeCode The attribute code
384
     *
385
     * @return array The array with the EAV attribute
386
     * @throws \Exception Is thrown if the attribute with the passed code is not available
387
     */
388
    protected function getEavAttributeByAttributeCode($attributeCode)
389
    {
390
        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...a\Subjects\MediaSubject, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, 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...
391
    }
392
393
    /**
394
     * Persist's the passed product media gallery data and return's the ID.
395
     *
396
     * @param array $productMediaGallery The product media gallery data to persist
397
     *
398
     * @return string The ID of the persisted entity
399
     */
400
    protected function persistProductMediaGallery($productMediaGallery)
401
    {
402
        return $this->getProductMediaProcessor()->persistProductMediaGallery($productMediaGallery);
403
    }
404
405
    /**
406
     * Persist's the passed product media gallery value to entity data.
407
     *
408
     * @param array $productMediaGalleryValueToEntity The product media gallery value to entity data to persist
409
     *
410
     * @return void
411
     */
412
    protected function persistProductMediaGalleryValueToEntity($productMediaGalleryValueToEntity)
413
    {
414
        $this->getProductMediaProcessor()->persistProductMediaGalleryValueToEntity($productMediaGalleryValueToEntity);
415
    }
416
}
417