Completed
Push — 23.x ( b390a3...481f59 )
by Tim
01:55
created

ProductMediaObserver::loadImagesToDisable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 11
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 11
loc 11
ccs 0
cts 7
cp 0
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 6
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Media\Observers\ProductMediaObserver
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\Product\Media\Utils\ColumnKeys;
24
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
25
26
/**
27
 * Observer that extracts theproduct's media data from a CSV file to be added to media specifi CSV file.
28
 *
29
 * @author    Tim Wagner <[email protected]>
30
 * @copyright 2016 TechDivision GmbH <[email protected]>
31
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
32
 * @link      https://github.com/techdivision/import-product-media
33
 * @link      http://www.techdivision.com
34
 */
35
class ProductMediaObserver extends AbstractProductImportObserver
36
{
37
38
    /**
39
     * The artefact type.
40
     *
41
     * @var string
42
     */
43
    const ARTEFACT_TYPE = 'media';
44
45
    /**
46
     * The the default image label.
47
     *
48
     * @var string
49
     */
50
    const DEFAULT_IMAGE_LABEL = 'Image';
51
52
    /**
53
     * The default image position.
54
     *
55
     * @var int
56
     */
57
    const DEFAULT_IMAGE_POSITION = 0;
58
59
    /**
60
     * The array with the image information of on row before they'll be converted into artefacts.
61
     *
62
     * @var array
63
     */
64
    protected $images = array();
65
66
    /**
67
     * The image artefacts that has to be exported.
68
     *
69
     * @var array
70
     */
71
    protected $artefacts = array();
72
73
    /**
74
     * The array with names of the images that should be hidden on the product detail page.
75
     *
76
     * @var array
77
     */
78
    protected $imagesToHide = array();
79
80
    /**
81
     * The array with names of the images that has to be disabled.
82
     *
83
     * @var array
84
     */
85
    protected $disabledImages = array();
86
87
    /**
88
     * Holds the image values of the main row.
89
     *
90
     * @var array
91
     */
92
    protected $mainRow = array();
93
94
    /**
95
     * Process the observer's business logic.
96
     *
97
     * @return array The processed row
98
     */
99
    protected function process()
100
    {
101
102
        // reset the values of the parent row, if the SKU changes
103
        if ($this->isLastSku($this->getValue(ColumnKeys::SKU)) === false) {
104
            $this->mainRow = array();
105
        }
106
107
        // initialize the arrays for the images, the artefacts,
108
        // the hidden as well as the disabled images
109
        $this->images = array();
110
        $this->artefacts = array();
111
        $this->imagesToHide = array();
112
        $this->disabledImages = array();
113
114
        // load the images that has to be hidden on product
115
        // detail page or completely disabled
116
        $this->loadImagesToHide();
117
        $this->loadImagesToDisable();
118
119
        // process the images/additional images
120
        $this->processImages();
121
        $this->processAdditionalImages();
122
123
        // append the artefacts that has to be exported to the subject
124
        $this->addArtefacts($this->artefacts);
125
    }
126
127
    /**
128
     * Resolve's the value with the passed colum name from the actual row. If a callback will
129
     * be passed, the callback will be invoked with the found value as parameter. If
130
     * the value is NULL or empty, the default value will be returned.
131
     *
132
     * @param string        $name     The name of the column to return the value for
133
     * @param mixed|null    $default  The default value, that has to be returned, if the row's value is empty
134
     * @param callable|null $callback The callback that has to be invoked on the value, e. g. to format it
135
     *
136
     * @return mixed|null The, almost formatted, value
137
     * @see \TechDivision\Import\Observers\AbstractObserver::getValue()
138
     */
139
    protected function getImageValue($name, $default = null, callable $callback = null)
140
    {
141
142
        // query whether or not the a image value is available, return it if yes
143
        if ($this->hasValue($name) && $this->isLastSku($this->getValue(ColumnKeys::SKU)) === false) {
144
            return $this->mainRow[$name] = $this->getValue($name, $default, $callback);
145
        }
146
147
        // try to load it from the parent rows
148
        if (isset($this->mainRow[$name])) {
149
            return $this->mainRow[$name];
150
        }
151
    }
152
153
    /**
154
     * Parses the column and exports the image data to a separate file.
155
     *
156
     * @return void
157
     */
158
    protected function processImages()
159
    {
160
161
        // load the store view code
162
        $storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE);
163
        $attributeSetCode = $this->getValue(ColumnKeys::ATTRIBUTE_SET_CODE);
164
165
        // load the parent SKU from the row
166
        $parentSku = $this->getValue(ColumnKeys::SKU);
167
168
        // load the media roles
169
        $mediaRoles = $this->getMediaRoles();
170
171
        // iterate over the available image fields
172
        foreach ($mediaRoles as $mediaAttrColumnNames) {
173
            // query whether or not the column contains an image name
174
            if ($image = $this->getImageValue($imageColumnName = $mediaAttrColumnNames[ColumnKeys::IMAGE_PATH])) {
175
                // load the original image path and query whether or not an image with the name already exists
176
                if (isset($this->artefacts[$imagePath = $this->getInversedImageMapping($image)])) {
177
                    continue;
178
                }
179
180
                // initialize the label text
181
                $labelText = null;
182
                // query whether or not a custom label text has been passed
183
                if ($this->hasValue($labelColumnName = $mediaAttrColumnNames[ColumnKeys::IMAGE_LABEL])) {
184
                    $labelText = $this->getValue($mediaAttrColumnNames[ColumnKeys::IMAGE_LABEL]);
185
                }
186
187
                // initialize the default image position
188
                $position = null;
189
                // query and retrieve optional image position
190
                if ($this->hasValue($mediaAttrColumnNames[ColumnKeys::IMAGE_POSITION])) {
191
                    $position = $this->getValue($mediaAttrColumnNames[ColumnKeys::IMAGE_POSITION]);
192
                }
193
194
                // prepare the new base image
195
                $artefact = $this->newArtefact(
196
                    array(
197
                        ColumnKeys::STORE_VIEW_CODE        => $storeViewCode,
198
                        ColumnKeys::ATTRIBUTE_SET_CODE     => $attributeSetCode,
199
                        ColumnKeys::IMAGE_PARENT_SKU       => $parentSku,
200
                        ColumnKeys::IMAGE_PATH             => $imagePath,
201
                        ColumnKeys::IMAGE_PATH_NEW         => $image,
202
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => in_array($image, $this->imagesToHide) ? 1 : 0,
203
                        ColumnKeys::MEDIA_TYPE             => 'image',
204
                        ColumnKeys::IMAGE_LABEL            => $labelText,
205
                        ColumnKeys::IMAGE_POSITION         => $position,
206
                        ColumnKeys::IMAGE_DISABLED         => in_array($image, $this->disabledImages) ? 1 : 0,
207
                    ),
208
                    array(
209
                        ColumnKeys::STORE_VIEW_CODE        => ColumnKeys::STORE_VIEW_CODE,
210
                        ColumnKeys::ATTRIBUTE_SET_CODE     => ColumnKeys::ATTRIBUTE_SET_CODE,
211
                        ColumnKeys::IMAGE_PARENT_SKU       => ColumnKeys::SKU,
212
                        ColumnKeys::IMAGE_PATH             => $imageColumnName,
213
                        ColumnKeys::IMAGE_PATH_NEW         => $imageColumnName,
214
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => ColumnKeys::HIDE_FROM_PRODUCT_PAGE,
215
                        ColumnKeys::MEDIA_TYPE             => null,
216
                        ColumnKeys::IMAGE_LABEL            => $labelColumnName,
217
                        ColumnKeys::IMAGE_POSITION         => $mediaAttrColumnNames[ColumnKeys::IMAGE_POSITION],
218
                        ColumnKeys::IMAGE_DISABLED         => ColumnKeys::DISABLED_IMAGES
219
                    )
220
                );
221
222
                // append the base image to the artefacts
223
                $this->artefacts[$imagePath] = $artefact;
224
            }
225
        }
226
    }
227
228
    /**
229
     * Parses the column and exports the additional image data to a separate file.
230
     *
231
     * @return void
232
     */
233
    protected function processAdditionalImages()
234
    {
235
236
        // load the store view code
237
        $storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE);
238
        $attributeSetCode = $this->getValue(ColumnKeys::ATTRIBUTE_SET_CODE);
239
240
        // load the parent SKU from the row
241
        $parentSku = $this->getValue(ColumnKeys::SKU);
242
243
        // query whether or not, we've additional images
244
        if ($additionalImages = $this->getImageValue(ColumnKeys::ADDITIONAL_IMAGES, null, array($this, 'explode'))) {
245
            // expand the additional image labels, if available
246
            $additionalImageLabels = $this->getValue(ColumnKeys::ADDITIONAL_IMAGE_LABELS, array(), array($this, 'explode'));
247
            // retrieve the additional image positions
248
            $additionalImagePositions = $this->getValue(ColumnKeys::ADDITIONAL_IMAGE_POSITIONS, array(), array($this, 'explode'));
249
250
            // initialize the images with the found values
251
            foreach ($additionalImages as $key => $additionalImage) {
252
                // load the original image path and query whether or not an image with the name already exists
253
                if (isset($this->artefacts[$imagePath = $this->getInversedImageMapping($additionalImage)])) {
254
                    continue;
255
                }
256
257
                // prepare the additional image
258
                $artefact = $this->newArtefact(
259
                    array(
260
                        ColumnKeys::STORE_VIEW_CODE        => $storeViewCode,
261
                        ColumnKeys::ATTRIBUTE_SET_CODE     => $attributeSetCode,
262
                        ColumnKeys::IMAGE_PARENT_SKU       => $parentSku,
263
                        ColumnKeys::IMAGE_PATH             => $imagePath,
264
                        ColumnKeys::IMAGE_PATH_NEW         => $additionalImage,
265
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => in_array($additionalImage, $this->imagesToHide) ? 1 : 0,
266
                        ColumnKeys::MEDIA_TYPE             => 'image',
267
                        ColumnKeys::IMAGE_LABEL            => isset($additionalImageLabels[$key]) ? $additionalImageLabels[$key] : null,
268
                        ColumnKeys::IMAGE_POSITION         => isset($additionalImagePositions[$key]) ? $additionalImagePositions[$key] : null,
269
                        ColumnKeys::IMAGE_DISABLED         => in_array($additionalImage, $this->disabledImages) ? 1 : 0
270
                    ),
271
                    array(
272
                        ColumnKeys::STORE_VIEW_CODE        => ColumnKeys::STORE_VIEW_CODE,
273
                        ColumnKeys::ATTRIBUTE_SET_CODE     => ColumnKeys::ATTRIBUTE_SET_CODE,
274
                        ColumnKeys::IMAGE_PARENT_SKU       => ColumnKeys::SKU,
275
                        ColumnKeys::IMAGE_PATH             => ColumnKeys::ADDITIONAL_IMAGES,
276
                        ColumnKeys::IMAGE_PATH_NEW         => ColumnKeys::ADDITIONAL_IMAGES,
277
                        ColumnKeys::HIDE_FROM_PRODUCT_PAGE => ColumnKeys::HIDE_FROM_PRODUCT_PAGE,
278
                        ColumnKeys::MEDIA_TYPE             => null,
279
                        ColumnKeys::IMAGE_LABEL            => ColumnKeys::ADDITIONAL_IMAGE_LABELS,
280
                        ColumnKeys::IMAGE_POSITION         => ColumnKeys::ADDITIONAL_IMAGE_POSITIONS,
281
                        ColumnKeys::IMAGE_DISABLED         => ColumnKeys::DISABLED_IMAGES
282
                    )
283
                );
284
285
                // append the additional image to the artefacts
286
                $this->artefacts[$imagePath] = $artefact;
287
            }
288
        }
289
    }
290
291
    /**
292
     * Load the images that has to be hidden on the product detail page.
293
     *
294
     * @return void
295
     */
296 View Code Duplication
    protected function loadImagesToHide()
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...
297
    {
298
299
        // load the array with the images that has to be hidden
300
        $hideFromProductPage = $this->getValue(ColumnKeys::HIDE_FROM_PRODUCT_PAGE, array(), array($this, 'explode'));
301
302
        // map the image names, because probably they have been renamed by the upload functionality
303
        foreach ($hideFromProductPage as $filename) {
304
            $this->imagesToHide[] = $this->getImageMapping($filename);
305
        }
306
    }
307
308
    /**
309
     * Load the images that has to be disabled
310
     *
311
     * @return void
312
     */
313 View Code Duplication
    protected function loadImagesToDisable()
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...
314
    {
315
316
        // load the array with the images that has to be disabled
317
        $disabledImages = $this->getValue(ColumnKeys::DISABLED_IMAGES, array(), array($this, 'explode'));
318
319
        // map the image names, because probably they have been renamed by the upload functionality
320
        foreach ($disabledImages as $filename) {
321
            $this->disabledImages[] = $this->getImageMapping($filename);
322
        }
323
    }
324
325
    /**
326
     * Return's the array with the available image types and their label columns.
327
     *
328
     * @return array The array with the available image types
329
     */
330
    protected function getImageTypes()
331
    {
332
        return $this->getSubject()->getImageTypes();
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 getImageTypes() 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...
333
    }
334
335
    /**
336
     * Return's the default image label.
337
     *
338
     * @return string|null The default image label
339
     * @deprecated Since 23.0.0
340
     */
341
    protected function getDefaultImageLabel()
342
    {
343
        return ProductMediaObserver::DEFAULT_IMAGE_LABEL;
344
    }
345
346
    /**
347
     * Returns the default image position.
348
     *
349
     * @return int The default image position
350
     * @deprecated Since 23.0.0
351
     */
352
    protected function getDefaultImagePosition()
353
    {
354
        return ProductMediaObserver::DEFAULT_IMAGE_POSITION;
355
    }
356
357
    /**
358
     * Returns the mapped filename (which is the new filename).
359
     *
360
     * @param string $filename The filename to map
361
     *
362
     * @return string The mapped filename
363
     */
364
    protected function getImageMapping($filename)
365
    {
366
        return $this->getSubject()->getImageMapping($filename);
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 getImageMapping() does only exist in the following implementations of said interface: TechDivision\Import\Obse...s\FileUploadSubjectImpl, TechDivision\Import\Prod...a\Subjects\MediaSubject, 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...
367
    }
368
369
    /**
370
     * Returns the original filename for passed one (which is the new filename).
371
     *
372
     * @param string $newFilename The new filename to return the original one for
373
     *
374
     * @return string The original filename
375
     */
376
    protected function getInversedImageMapping($newFilename)
377
    {
378
        return $this->getSubject()->getInversedImageMapping($newFilename);
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 getInversedImageMapping() does only exist in the following implementations of said interface: TechDivision\Import\Obse...s\FileUploadSubjectImpl, TechDivision\Import\Prod...a\Subjects\MediaSubject, 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...
379
    }
380
381
    /**
382
     * Returns the media roles of the subject.
383
     *
384
     * @return mixed
385
     */
386
    protected function getMediaRoles()
387
    {
388
        return $this->getSubject()->getMediaRoles();
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 getMediaRoles() does only exist in the following implementations of said interface: 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...
389
    }
390
391
    /**
392
     * Create's and return's a new empty artefact entity.
393
     *
394
     * @param array $columns             The array with the column data
395
     * @param array $originalColumnNames The array with a mapping from the old to the new column names
396
     *
397
     * @return array The new artefact entity
398
     */
399
    protected function newArtefact(array $columns, array $originalColumnNames)
400
    {
401
        return $this->getSubject()->newArtefact($columns, $originalColumnNames);
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 newArtefact() does only exist in the following implementations of said interface: TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\ExportableTraitImpl.

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...
402
    }
403
404
    /**
405
     * Add the passed product type artefacts to the product with the
406
     * last entity ID.
407
     *
408
     * @param array $artefacts The product type artefacts
409
     *
410
     * @return void
411
     * @uses \TechDivision\Import\Product\Media\Subjects\MediaSubject::getLastEntityId()
412
     */
413
    protected function addArtefacts(array $artefacts)
414
    {
415
        $this->getSubject()->addArtefacts(ProductMediaObserver::ARTEFACT_TYPE, $artefacts, false);
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 addArtefacts() does only exist in the following implementations of said interface: TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\ExportableTraitImpl.

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...
416
    }
417
}
418