Completed
Push — bugfix/replace-operation ( 35df7d )
by Tim
06:09
created

ProductUrlRewriteObserver::newArtefact()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\UrlRewrite\Observers\ProductUrlRewriteObserver
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-url-rewrite
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\UrlRewrite\Observers;
22
23
use TechDivision\Import\Observers\StateDetectorInterface;
24
use TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface;
25
use TechDivision\Import\Utils\MemberNames;
26
use TechDivision\Import\Utils\StoreViewCodes;
27
use TechDivision\Import\Product\UrlRewrite\Utils\ColumnKeys;
28
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
29
30
/**
31
 * Observer that extracts the URL rewrite data to a specific CSV file.
32
 *
33
 * @author    Tim Wagner <[email protected]>
34
 * @copyright 2016 TechDivision GmbH <[email protected]>
35
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
36
 * @link      https://github.com/techdivision/import-product-url-rewrite
37
 * @link      http://www.techdivision.com
38
 */
39
class ProductUrlRewriteObserver extends AbstractProductImportObserver
40
{
41
42
    /**
43
     * The artefact type.
44
     *
45
     * @var string
46
     */
47
    const ARTEFACT_TYPE = 'url-rewrite';
48
49
    /**
50
     * The image artefacts that has to be exported.
51
     *
52
     * @var array
53
     */
54
    protected $artefacts = array();
55
56
    /**
57
     * The admin row for initialize artefacts that has to be exported.
58
     *
59
     * @var array
60
     */
61
    protected $adminRow = array();
62
63
    /**
64
     * The product bunch processor instance.
65
     *
66
     * @var \TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface
67
     */
68
    protected $productUrlRewriteProcessor;
69
70
    /**
71
     * Initialize the observer with the passed product URL rewrite processor instance.
72
     *
73
     * @param \TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface $productUrlRewriteProcessor The product URL rewrite processor instance
74
     * @param \TechDivision\Import\Observers\StateDetectorInterface|null                           $stateDetector              The state detector instance to use
75
     */
76
    public function __construct(
77
        ProductUrlRewriteProcessorInterface $productUrlRewriteProcessor,
78
        StateDetectorInterface $stateDetector = null
79
    ) {
80
        $this->productUrlRewriteProcessor = $productUrlRewriteProcessor;
81
        parent::__construct($stateDetector);
82
    }
83
84
    /**
85
     * Return's the product bunch processor instance.
86
     *
87
     * @return \TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface The product URL rewrite processor instance
88
     */
89
    protected function getProductUrlRewriteProcessor()
90
    {
91
        return $this->productUrlRewriteProcessor;
92
    }
93
94
    /**
95
     * Process the observer's business logic.
96
     *
97
     * @return array The processed row
98
     */
99
    protected function process()
100
    {
101
102
        // initialize the product
103
        $product = null;
104
105
        // initialize the array for the artefacts and the store view codes
106
        $this->artefacts = array();
107
        $storeViewCodes = array();
108
109
        // load the SKU from the row
110
        $sku = $this->getValue(ColumnKeys::SKU);
111
112
        // prepare the store view code
113
        $this->getSubject()->prepareStoreViewCode();
114
115
        // try to load the store view code
116
        $storeViewCodeValue = $this->getSubject()->getStoreViewCode(StoreViewCodes::ADMIN);
117
118
        // query whether or not we've a store view code
119
        if ($storeViewCodeValue === StoreViewCodes::ADMIN) {
120
            // load product to see if already exist or new
121
            $product = $this->loadProduct($this->getValue(ColumnKeys::SKU));
122
123
            // init admin row for memory overload
124
            $this->adminRow = array();
125
126
            // remember the admin row on SKU to be safe on later process
127
            $this->adminRow[$sku] = array(
128
                ColumnKeys::CATEGORIES       => $this->getValue(ColumnKeys::CATEGORIES),
129
                ColumnKeys::PRODUCT_WEBSITES => $this->getValue(ColumnKeys::PRODUCT_WEBSITES),
130
                ColumnKeys::VISIBILITY       => $this->getValue(ColumnKeys::VISIBILITY),
131
                ColumnKeys::URL_KEY          => $this->getValue(ColumnKeys::URL_KEY)
132
            );
133
134
            // if not, load the websites the product is related with
135
            $websiteCodes = $this->getValue(ColumnKeys::PRODUCT_WEBSITES, array(), array($this, 'explode'));
136
137
            // load the store view codes of all websites
138
            foreach ($websiteCodes as $websiteCode) {
139
                $storeViewCodes = array_merge($storeViewCodes, $this->getStoreViewCodesByWebsiteCode($websiteCode));
140
            }
141
        } else {
142
            array_push($storeViewCodes, $storeViewCodeValue);
143
        }
144
145
        // iterate over the available image fields
146
        foreach ($storeViewCodes as $storeViewCode) {
147
            // iterate over the store view codes and query if artefacts are already available
148
            if ($this->hasArtefactsByTypeAndEntityId(ProductUrlRewriteObserver::ARTEFACT_TYPE, $lastEntityId = $this->getSubject()->getLastEntityId())) {
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 getLastEntityId() does only exist in the following implementations of said interface: TechDivision\Import\Observers\EntitySubjectImpl, TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Prod...jects\UrlRewriteSubject, 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...
149
                // if yes, load the artefacs
150
                $this->artefacts = $this->getArtefactsByTypeAndEntityId(ProductUrlRewriteObserver::ARTEFACT_TYPE, $lastEntityId);
151
                // query whether or not an URL Key has been set
152
                if ($this->hasValue(ColumnKeys::URL_KEY)) {
153
                    // initialize the flag that shows whether or not an artefact has already been available
154
                    $foundArtefactToUpdate = false;
155
156
                    // override the existing data with the store view specific one
157
                    for ($i = 0; $i < sizeof($this->artefacts); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
158
159
                        // query whether or not a URL key has be specfied and the store view codes are equal
160
                        if ($this->artefacts[$i][ColumnKeys::STORE_VIEW_CODE] === $storeViewCode) {
161
                            // set the flag to mark we've already found an attribute
162
                            $foundArtefactToUpdate = true;
163
164
                            // update the URL key
165
                            $this->artefacts[$i][ColumnKeys::URL_KEY] = $this->getValue(ColumnKeys::URL_KEY);
166
167
                            // update the visibility, if available
168
                            if ($this->hasValue(ColumnKeys::VISIBILITY)) {
169
                                $this->artefacts[$i][ColumnKeys::VISIBILITY] = $this->getValue(ColumnKeys::VISIBILITY);
170
                            }
171
172
                            // also update filename and line number
173
                            $this->artefacts[$i][ColumnKeys::ORIGINAL_DATA][ColumnKeys::ORIGINAL_FILENAME] = $this->getSubject()->getFilename();
174
                            $this->artefacts[$i][ColumnKeys::ORIGINAL_DATA][ColumnKeys::ORIGINAL_LINE_NUMBER] = $this->getSubject()->getLineNumber();
175
                        }
176
                    }
177
178
                    if (!$foundArtefactToUpdate) {
179
                        // if no arefacts are available, append new data
180
                        $this->createArtefact($sku, $storeViewCode);
181
                    }
182
                }
183
184
            } else {
185
                // on admin row and existing product check if url_key in database
186
                if ($storeViewCodeValue === StoreViewCodes::ADMIN && $product) {
187
                    // initialize store ID from store code
188
                    $storeId = $this->getSubject()->getRowStoreId($storeViewCode);
0 ignored issues
show
Bug introduced by
The method getRowStoreId() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getRow()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
189
                    // load the url_key attribute
190
                    $urlKey = $this->loadExistingUrlKey($product, $storeViewCode);
191
                    // if url_key attribute found and same store as searched
192
                    if ($urlKey && $urlKey[MemberNames::STORE_ID] == $storeId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $urlKey of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
193
                        // skip for artefact as default entry
194
                        continue;
195
                    }
196
                }
197
198
                // if no arefacts are available, append new data
199
                $this->createArtefact($sku, $storeViewCode);
200
            }
201
        }
202
203
        // append the artefacts that has to be exported to the subject
204
        $this->addArtefacts($this->artefacts);
205
    }
206
207
    /**
208
     * Tries to load the URL key for the passed product and store view code and return's it.
209
     *
210
     * @param array  $product       The product to return the URL key for
211
     * @param string $storeViewCode The store view code of the URL key
212
     *
213
     * @return array|null The array with the URL key attribute data
214
     */
215
    protected function loadExistingUrlKey(array $product, string $storeViewCode)
216
    {
217
218
        // initialize last entity as primary key
219
        $pk = $this->getPrimaryKeyId($product);
220
221
        // initialize the entity type ID
222
        $entityType = $this->getSubject()->getEntityType();
0 ignored issues
show
Bug introduced by
The method getEntityType() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getEntityTypeCode()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
223
        $entityTypeId = (integer) $entityType[MemberNames::ENTITY_TYPE_ID];
224
225
        // initialize store ID from store code
226
        $storeId = $this->getSubject()->getRowStoreId($storeViewCode);
0 ignored issues
show
Bug introduced by
The method getRowStoreId() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getRow()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
227
228
        // take a look if url_key already exist
229
        return $this->getProductUrlRewriteProcessor()->loadVarcharAttributeByAttributeCodeAndEntityTypeIdAndStoreIdAndPrimaryKey(
230
            ColumnKeys::URL_KEY,
231
            $entityTypeId,
232
            $storeId,
233
            $pk
234
        );
235
    }
236
237
    /**
238
     * @param array $product From loadProduct
239
     * @return mixed
240
     */
241
    protected function getPrimaryKeyId(array $product)
242
    {
243
        return $product[$this->getProductUrlRewriteProcessor()->getPrimaryKeyMemberName()];
244
    }
245
246
    /**
247
     * Creates a new artefact, pre-initialized with the values from the admin row.
248
     *
249
     * @param string $sku           The sku for the new url_key
250
     * @param string $storeViewCode The Storeview code
251
     *
252
     * @return void
253
     */
254
    protected function createArtefact(string $sku, string $storeViewCode) : void
255
    {
256
257
        // create the new artefact and return it
258
        $artefact = $this->newArtefact(
259
            array(
260
                ColumnKeys::SKU                => $sku,
261
                ColumnKeys::STORE_VIEW_CODE    => $storeViewCode,
262
                ColumnKeys::CATEGORIES         => $this->getValue(ColumnKeys::CATEGORIES, isset($this->adminRow[$sku][ColumnKeys::CATEGORIES]) ? $this->adminRow[$sku][ColumnKeys::CATEGORIES]: null),
263
                ColumnKeys::PRODUCT_WEBSITES   => $this->getValue(ColumnKeys::PRODUCT_WEBSITES, isset($this->adminRow[$sku][ColumnKeys::PRODUCT_WEBSITES]) ? $this->adminRow[$sku][ColumnKeys::PRODUCT_WEBSITES] : null),
264
                ColumnKeys::VISIBILITY         => $this->getValue(ColumnKeys::VISIBILITY, isset($this->adminRow[$sku][ColumnKeys::VISIBILITY]) ? $this->adminRow[$sku][ColumnKeys::VISIBILITY] : null),
265
                ColumnKeys::URL_KEY            => $this->getValue(ColumnKeys::URL_KEY, isset($this->adminRow[$sku][ColumnKeys::URL_KEY]) ? $this->adminRow[$sku][ColumnKeys::URL_KEY] : null)
266
            ),
267
            array(
268
                ColumnKeys::SKU                => ColumnKeys::SKU,
269
                ColumnKeys::STORE_VIEW_CODE    => ColumnKeys::STORE_VIEW_CODE,
270
                ColumnKeys::CATEGORIES         => ColumnKeys::CATEGORIES,
271
                ColumnKeys::PRODUCT_WEBSITES   => ColumnKeys::PRODUCT_WEBSITES,
272
                ColumnKeys::VISIBILITY         => ColumnKeys::VISIBILITY,
273
                ColumnKeys::URL_KEY            => ColumnKeys::URL_KEY,
274
            )
275
        );
276
277
        // append the artefact to the artefacts
278
        $this->artefacts[] = $artefact;
279
    }
280
281
    /**
282
     * Load's and return's the product with the passed SKU.
283
     *
284
     * @param string $sku The SKU of the product to load
285
     *
286
     * @return array The product
287
     */
288
    protected function loadProduct($sku)
289
    {
290
        return $this->getProductUrlRewriteProcessor()->loadProduct($sku);
291
    }
292
293
    /**
294
     * Returns an array with the codes of the store views related with the passed website code.
295
     *
296
     * @param string $websiteCode The code of the website to return the store view codes for
297
     *
298
     * @return array The array with the matching store view codes
299
     */
300
    protected function getStoreViewCodesByWebsiteCode($websiteCode)
301
    {
302
        return $this->getSubject()->getStoreViewCodesByWebsiteCode($websiteCode);
0 ignored issues
show
Bug introduced by
The method getStoreViewCodesByWebsiteCode() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getStoreViewCode()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
303
    }
304
305
    /**
306
     * Queries whether or not artefacts for the passed type and entity ID are available.
307
     *
308
     * @param string $type     The artefact type, e. g. configurable
309
     * @param string $entityId The entity ID to return the artefacts for
310
     *
311
     * @return boolean TRUE if artefacts are available, else FALSE
312
     */
313
    protected function hasArtefactsByTypeAndEntityId($type, $entityId)
314
    {
315
        return $this->getSubject()->hasArtefactsByTypeAndEntityId($type, $entityId);
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 hasArtefactsByTypeAndEntityId() does only exist in the following implementations of said interface: 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...
316
    }
317
318
    /**
319
     * Return the artefacts for the passed type and entity ID.
320
     *
321
     * @param string $type     The artefact type, e. g. configurable
322
     * @param string $entityId The entity ID to return the artefacts for
323
     *
324
     * @return array The array with the artefacts
325
     * @throws \Exception Is thrown, if no artefacts are available
326
     */
327
    protected function getArtefactsByTypeAndEntityId($type, $entityId)
328
    {
329
        return $this->getSubject()->getArtefactsByTypeAndEntityId($type, $entityId);
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 getArtefactsByTypeAndEntityId() 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...
330
    }
331
332
    /**
333
     * Create's and return's a new empty artefact entity.
334
     *
335
     * @param array $columns             The array with the column data
336
     * @param array $originalColumnNames The array with a mapping from the old to the new column names
337
     *
338
     * @return array The new artefact entity
339
     */
340
    protected function newArtefact(array $columns, array $originalColumnNames)
341
    {
342
        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...
343
    }
344
345
    /**
346
     * Add the passed product type artefacts to the product with the
347
     * last entity ID.
348
     *
349
     * @param array $artefacts The product type artefacts
350
     *
351
     * @return void
352
     * @uses \TechDivision\Import\Product\Media\Subjects\MediaSubject::getLastEntityId()
353
     */
354
    protected function addArtefacts(array $artefacts)
355
    {
356
        $this->getSubject()->addArtefacts(ProductUrlRewriteObserver::ARTEFACT_TYPE, $artefacts);
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...
357
    }
358
}
359