Completed
Push — master ( 4787a2...a3ff39 )
by Tim
16s queued 11s
created

UrlKeyObserver::getProductBunchProcessor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Observers\UrlKeyObserver
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
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Observers;
22
23
use Zend\Filter\FilterInterface;
24
use TechDivision\Import\Utils\StoreViewCodes;
25
use TechDivision\Import\Utils\UrlKeyUtilInterface;
26
use TechDivision\Import\Utils\Filter\UrlKeyFilterTrait;
27
use TechDivision\Import\Utils\Generators\GeneratorInterface;
28
use TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface;
29
use TechDivision\Import\Product\Utils\MemberNames;
30
use TechDivision\Import\Product\Utils\ColumnKeys;
31
use TechDivision\Import\Product\Utils\ConfigurationKeys;
32
use TechDivision\Import\Product\Services\ProductBunchProcessorInterface;
33
use TechDivision\Import\Observers\ObserverFactoryInterface;
34
use TechDivision\Import\Subjects\SubjectInterface;
35
36
/**
37
 * Observer that extracts the URL key from the product name and adds a two new columns
38
 * with the their values.
39
 *
40
 * @author    Tim Wagner <[email protected]>
41
 * @copyright 2016 TechDivision GmbH <[email protected]>
42
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
43
 * @link      https://github.com/techdivision/import-product
44
 * @link      http://www.techdivision.com
45
 */
46
class UrlKeyObserver extends AbstractProductImportObserver implements ObserverFactoryInterface
47
{
48
49
    /**
50
     * The trait that provides string => URL key conversion functionality.
51
     *
52
     * @var \TechDivision\Import\Utils\Filter\UrlKeyFilterTrait
53
     */
54
    use UrlKeyFilterTrait;
55
56
    /**
57
     * The URL key utility instance.
58
     *
59
     * @var \TechDivision\Import\Utils\UrlKeyUtilInterface
60
     */
61
    protected $urlKeyUtil;
62
63
    /**
64
     * The product bunch processor instance.
65
     *
66
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
67
     */
68
    protected $productBunchProcessor;
69
70
    /**
71
     * The reverse sequence generator instance.
72
     *
73
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
74
     */
75
    protected $reverseSequenceGenerator;
76
77
    /**
78
     * The array with the root categories.
79
     *
80
     * @var array
81
     */
82
    protected $rootCategories = array();
83
84
    /**
85
     * Initialize the observer with the passed product bunch processor and filter instance.
86
     *
87
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor    The product bunch processor instance
88
     * @param \Zend\Filter\FilterInterface                                         $convertLiteralUrlFilter  The URL filter instance
89
     * @param \TechDivision\Import\Utils\UrlKeyUtilInterface                       $urlKeyUtil               The URL key utility instance
90
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface             $reverseSequenceGenerator The reverse sequence generator instance
91
     */
92
    public function __construct(
93
        ProductBunchProcessorInterface $productBunchProcessor,
94
        FilterInterface $convertLiteralUrlFilter,
95
        UrlKeyUtilInterface $urlKeyUtil,
96
        GeneratorInterface $reverseSequenceGenerator
97
    ) {
98
        $this->productBunchProcessor = $productBunchProcessor;
99
        $this->convertLiteralUrlFilter = $convertLiteralUrlFilter;
100
        $this->urlKeyUtil = $urlKeyUtil;
101
        $this->reverseSequenceGenerator = $reverseSequenceGenerator;
102
    }
103
104
    /**
105
     * Will be invoked by the observer visitor when a factory has been defined to create the observer instance.
106
     *
107
     * @param \TechDivision\Import\Subjects\SubjectInterface $subject The subject instance
108
     *
109
     * @return \TechDivision\Import\Observers\ObserverInterface The observer instance
110
     */
111
    public function createObserver(SubjectInterface $subject)
112
    {
113
114
        // load the root categories
115
        $rootCategories = $subject->getRootCategories();
0 ignored issues
show
Bug introduced by
The method getRootCategories() does not seem to exist on object<TechDivision\Impo...jects\SubjectInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
116
117
        // initialize the array with the root categories
118
        // by using the entity ID as index
119
        foreach ($rootCategories as $rootCategory) {
120
            $this->rootCategories[(int) $rootCategory[MemberNames::ENTITY_ID]] = $rootCategory;
121
        }
122
123
        // return the initialized instance
124
        return $this;
125
    }
126
127
    /**
128
     * Return's the product bunch processor instance.
129
     *
130
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
131
     */
132
    protected function getProductBunchProcessor() : ProductBunchProcessorInterface
133
    {
134
        return $this->productBunchProcessor;
135
    }
136
137
    /**
138
     * Returns the URL key utility instance.
139
     *
140
     * @return \TechDivision\Import\Utils\UrlKeyUtilInterface The URL key utility instance
141
     */
142
    protected function getUrlKeyUtil() : UrlKeyUtilInterface
143
    {
144
        return $this->urlKeyUtil;
145
    }
146
147
    /**
148
     * Returns the reverse sequence generator instance.
149
     *
150
     * @return \TechDivision\Import\Utils\Generators\GeneratorInterface The reverse sequence generator
151
     */
152
    protected function getReverseSequenceGenerator() : GeneratorInterface
153
    {
154
        return $this->reverseSequenceGenerator;
155
    }
156
157
    /**
158
     * Process the observer's business logic.
159
     *
160
     * @return void
161
     * @throws \Exception Is thrown, if either column "url_key" or "name" have a value set
162
     */
163
    protected function process()
164
    {
165
166
        // initialize the URL key, the entity and the product
167
        $urlKey = null;
168
        $entity = null;
0 ignored issues
show
Unused Code introduced by
$entity is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
169
        $product = array();
170
171
        // prepare the store view code
172
        $this->getSubject()->prepareStoreViewCode();
173
174
        // set the entity ID for the product with the passed SKU
175
        if ($entity = $this->loadProduct($sku = $this->getValue(ColumnKeys::SKU))) {
176
            $this->setIds($product = $entity);
177
        } else {
178
            $this->setIds(array());
179
            $product[MemberNames::ENTITY_ID] = $this->getReverseSequenceGenerator()->generate();
0 ignored issues
show
Bug introduced by
The call to generate() misses a required argument $entity.

This check looks for function calls that miss required arguments.

Loading history...
180
        }
181
182
        // query whether or not the URL key column has a value
183
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
184
            $urlKey = $this->getValue(ColumnKeys::URL_KEY);
185
        } else {
186
            // query whether or not the existing product `url_key` should be re-created from the product name
187
            if (is_array($entity) && !$this->getSubject()->getConfiguration()->getParam(ConfigurationKeys::UPDATE_URL_KEY_FROM_NAME, true)) {
188
                // if the product already exists and NO re-creation from the product name has to
189
                // be done, load the original `url_key`from the product and use that to proceed
190
                $urlKey = $this->loadUrlKey($this->getSubject(), $this->getPrimaryKey());
0 ignored issues
show
Documentation introduced by
$this->getSubject() is of type object<TechDivision\Impo...jects\SubjectInterface>, but the function expects a object<TechDivision\Impo...yAwareSubjectInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
191
            }
192
193
            // try to load the value from column `name` if URL key is still
194
            // empty, because we need it to process the the rewrites later on
195
            if ($urlKey === null || $urlKey === '' && $this->hasValue(ColumnKeys::NAME)) {
196
                $urlKey = $this->convertNameToUrlKey($this->getValue(ColumnKeys::NAME));
197
            }
198
        }
199
200
        // stop processing, if no URL key is available
201
        if ($urlKey === null || $urlKey === '') {
202
            // throw an exception, that the URL key can not be
203
            // initialized and we're in the default store view
204
            if ($this->getStoreViewCode(StoreViewCodes::ADMIN) === StoreViewCodes::ADMIN) {
205
                throw new \Exception(sprintf('Can\'t initialize the URL key for product "%s" because columns "url_key" or "name" have a value set for default store view', $sku));
206
            }
207
            // stop processing, because we're in a store
208
            // view row and a URL key is not mandatory
209
            return;
210
        }
211
212
        // set the unique URL key for further processing
213
        $this->setValue(ColumnKeys::URL_KEY, $this->makeUnique($this->getSubject(), $product, $urlKey, $this->getUrlPaths()));
0 ignored issues
show
Documentation introduced by
$this->getSubject() is of type object<TechDivision\Impo...jects\SubjectInterface>, but the function expects a object<TechDivision\Impo...yAwareSubjectInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
    }
215
216
    /**
217
     * Extract's the category from the comma separeted list of categories
218
     * in column `categories` and return's an array with their URL paths.
219
     *
220
     * @return string[] Array with the URL paths of the categories found in column `categories`
221
     */
222
    protected function getUrlPaths()
223
    {
224
225
        // initialize the array for the URL paths of the cateogries
226
        $urlPaths = array();
227
228
        // extract the categories from the column `categories`
229
        $paths = $this->getValue(ColumnKeys::CATEGORIES, array(), array($this, 'explode'));
230
231
        // the URL paths are store view specific, so we need
232
        // the store view code to load the appropriate ones
233
        $storeViewCode = $this->getStoreViewCode(StoreViewCodes::ADMIN);
234
235
        // iterate of the found categories, load their URL path as well as the URL path of
236
        // parent categories, if they have the anchor flag activated and add it the array
237
        foreach ($paths as $path) {
238
            // load the category based on the category path
239
            $category = $this->getCategoryByPath($path, $storeViewCode);
240
            // try to resolve the URL paths recursively
241
            $this->resolveUrlPaths($urlPaths, $category, $storeViewCode);
242
        }
243
244
        // return the array with the recursively resolved URL paths
245
        // of the categories that are related with the products
246
        return $urlPaths;
247
    }
248
249
    /**
250
     * Recursively resolves an array with the store view specific
251
     * URL paths of the passed category.
252
     *
253
     * @param array  $urlPaths       The array to append the URL paths to
254
     * @param array  $category       The category to resolve the list with URL paths
255
     * @param string $storeViewCode  The store view code to resolve the URL paths for
256
     * @param bool   $directRelation the flag whether or not the passed category is a direct relation to the product and has to added to the list
257
     *
258
     * @return void
259
     */
260
    protected function resolveUrlPaths(array &$urlPaths, array $category, string $storeViewCode, bool $directRelation = true)
261
    {
262
263
        // try to resolve the parent category IDs, but only if the parent or
264
        // the category itself is NOT a root category. The last case is
265
        // possible if the column directly contains the root category only
266
        if (isset($this->rootCategories[(int) $category[MemberNames::ENTITY_ID]]) === false &&
267
            isset($this->rootCategories[(int) $category[MemberNames::PARENT_ID]]) === false
268
        ) {
269
            // load the parent category
270
            $parent = $this->getCategory($category[MemberNames::PARENT_ID], $storeViewCode);
271
            // also resolve the URL paths for the parent category
272
            $this->resolveUrlPaths($urlPaths, $parent, $storeViewCode, false);
273
        }
274
275
        // query whether or not the URL path already
276
        // is part of the list (to avoid duplicates)
277
        if (in_array($category[MemberNames::URL_PATH], $urlPaths)) {
278
            return;
279
        }
280
281
        // append the URL path, either if we've a direct relation
282
        // or the category has the anchor flag actvated
283
        if ($directRelation === true || ($category[MemberNames::IS_ANCHOR] === 1 && $directRelation === false)) {
284
            $urlPaths[] = $category[MemberNames::URL_PATH];
285
        }
286
    }
287
288
    /**
289
     * Temporarily persist's the IDs of the passed product.
290
     *
291
     * @param array $product The product to temporarily persist the IDs for
292
     *
293
     * @return void
294
     */
295
    protected function setIds(array $product)
296
    {
297
        $this->setLastEntityId(isset($product[MemberNames::ENTITY_ID]) ? $product[MemberNames::ENTITY_ID] : null);
298
    }
299
300
    /**
301
     * Set's the ID of the product that has been created recently.
302
     *
303
     * @param string $lastEntityId The entity ID
304
     *
305
     * @return void
306
     */
307
    protected function setLastEntityId($lastEntityId)
308
    {
309
        $this->getSubject()->setLastEntityId($lastEntityId);
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 setLastEntityId() does only exist in the following implementations of said interface: TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Prod...\AbstractProductSubject, 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...
310
    }
311
312
    /**
313
     * Return's the PK to of the product.
314
     *
315
     * @return integer The PK to create the relation with
316
     */
317
    protected function getPrimaryKey()
318
    {
319
        $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\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...
320
    }
321
322
    /**
323
     * Load's and return's the product with the passed SKU.
324
     *
325
     * @param string $sku The SKU of the product to load
326
     *
327
     * @return array The product
328
     */
329
    protected function loadProduct($sku)
330
    {
331
        return $this->getProductBunchProcessor()->loadProduct($sku);
332
    }
333
334
    /**
335
     * Load's and return's the url_key with the passed primary ID.
336
     *
337
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject      The subject to load the URL key
338
     * @param int                                                       $primaryKeyId The ID from product
339
     *
340
     * @return string|null url_key or null
341
     */
342
    protected function loadUrlKey(UrlKeyAwareSubjectInterface $subject, $primaryKeyId)
343
    {
344
        return $this->getUrlKeyUtil()->loadUrlKey($subject, $primaryKeyId);
0 ignored issues
show
Bug introduced by
The method loadUrlKey() does not seem to exist on object<TechDivision\Impo...ls\UrlKeyUtilInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
345
    }
346
347
    /**
348
     * Return's the category with the passed path.
349
     *
350
     * @param string $path          The path of the category to return
351
     * @param string $storeViewCode The code of a store view, defaults to admin
352
     *
353
     * @return array The category
354
     */
355
    protected function getCategoryByPath($path, $storeViewCode = StoreViewCodes::ADMIN)
0 ignored issues
show
Unused Code introduced by
The parameter $storeViewCode is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
356
    {
357
        return $this->getSubject()->getCategoryByPath($path);
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 getCategoryByPath() does only exist in the following implementations of said interface: 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...
358
    }
359
360
    /**
361
     * Make's the passed URL key unique by adding the next number to the end.
362
     *
363
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject  The subject to make the URL key unique for
364
     * @param array                                                     $entity   The entity to make the URL key unique for
365
     * @param string                                                    $urlKey   The URL key to make unique
366
     * @param array                                                     $urlPaths The URL paths to make unique
367
     *
368
     * @return string The unique URL key
369
     */
370
    protected function makeUnique(UrlKeyAwareSubjectInterface $subject, array $entity, string $urlKey, array $urlPaths = array())
371
    {
372
        return $this->getUrlKeyUtil()->makeUnique($subject, $entity, $urlKey, $urlPaths);
0 ignored issues
show
Documentation introduced by
$entity is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Unused Code introduced by
The call to UrlKeyUtilInterface::makeUnique() has too many arguments starting with $urlKey.

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...
373
    }
374
375
    /**
376
     * Return's the array with the root categories.
377
     *
378
     * @return array The array with the root categories
379
     */
380
    public function getRootCategories()
381
    {
382
        return $this->getSubject()->getRootCategories();
0 ignored issues
show
Bug introduced by
The method getRootCategories() does not seem to exist on object<TechDivision\Impo...jects\SubjectInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
383
    }
384
385
    /**
386
     * Return's the category with the passed ID.
387
     *
388
     * @param integer $categoryId    The ID of the category to return
389
     * @param string  $storeViewCode The code of a store view, defaults to "admin"
390
     *
391
     * @return array The category data
392
     */
393
    protected function getCategory($categoryId, $storeViewCode = StoreViewCodes::ADMIN)
394
    {
395
        return $this->getSubject()->getCategory($categoryId, $storeViewCode);
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 getCategory() does only exist in the following implementations of said interface: 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...
396
    }
397
}
398