Completed
Push — master ( fd1e29...4787a2 )
by Tim
07:04 queued 05:46
created

UrlKeyObserver::setIds()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 1
nop 1
crap 6
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\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\Subjects\UrlKeyAwareSubjectInterface;
28
use TechDivision\Import\Product\Utils\MemberNames;
29
use TechDivision\Import\Product\Utils\ColumnKeys;
30
use TechDivision\Import\Product\Utils\ConfigurationKeys;
31
use TechDivision\Import\Product\Services\ProductBunchProcessorInterface;
32
use TechDivision\Import\Utils\Generators\GeneratorInterface;
33
34
/**
35
 * Observer that extracts the URL key from the product name and adds a two new columns
36
 * with the their values.
37
 *
38
 * @author    Tim Wagner <[email protected]>
39
 * @copyright 2016 TechDivision GmbH <[email protected]>
40
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
41
 * @link      https://github.com/techdivision/import-product
42
 * @link      http://www.techdivision.com
43
 */
44
class UrlKeyObserver extends AbstractProductImportObserver
45
{
46
47
    /**
48
     * The trait that provides string => URL key conversion functionality.
49
     *
50
     * @var \TechDivision\Import\Utils\Filter\UrlKeyFilterTrait
51
     */
52
    use UrlKeyFilterTrait;
53
54
    /**
55
     * The URL key utility instance.
56
     *
57
     * @var \TechDivision\Import\Utils\UrlKeyUtilInterface
58
     */
59
    protected $urlKeyUtil;
60
61
    /**
62
     * The product bunch processor instance.
63
     *
64
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
65
     */
66
    protected $productBunchProcessor;
67
68
    /**
69
     * The reverse sequence generator instance.
70
     *
71
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
72
     */
73
    protected $reverseSequenceGenerator;
74
75
    /**
76
     * Initialize the observer with the passed product bunch processor and filter instance.
77
     *
78
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor    The product bunch processor instance
79
     * @param \Zend\Filter\FilterInterface                                         $convertLiteralUrlFilter  The URL filter instance
80
     * @param \TechDivision\Import\Utils\UrlKeyUtilInterface                       $urlKeyUtil               The URL key utility instance
81
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface             $reverseSequenceGenerator The reverse sequence generator instance
82
     */
83
    public function __construct(
84
        ProductBunchProcessorInterface $productBunchProcessor,
85
        FilterInterface $convertLiteralUrlFilter,
86
        UrlKeyUtilInterface $urlKeyUtil,
87
        GeneratorInterface $reverseSequenceGenerator
88
    ) {
89
        $this->productBunchProcessor = $productBunchProcessor;
90
        $this->convertLiteralUrlFilter = $convertLiteralUrlFilter;
91
        $this->urlKeyUtil = $urlKeyUtil;
92
        $this->reverseSequenceGenerator = $reverseSequenceGenerator;
93
    }
94
95
    /**
96
     * Return's the product bunch processor instance.
97
     *
98
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
99
     */
100
    protected function getProductBunchProcessor() : ProductBunchProcessorInterface
101
    {
102
        return $this->productBunchProcessor;
103
    }
104
105
    /**
106
     * Returns the URL key utility instance.
107
     *
108
     * @return \TechDivision\Import\Utils\UrlKeyUtilInterface The URL key utility instance
109
     */
110
    protected function getUrlKeyUtil() : UrlKeyUtilInterface
111
    {
112
        return $this->urlKeyUtil;
113
    }
114
115
    /**
116
     * Returns the reverse sequence generator instance.
117
     *
118
     * @return \TechDivision\Import\Utils\Generators\GeneratorInterface The reverse sequence generator
119
     */
120
    protected function getReverseSequenceGenerator() : GeneratorInterface
121
    {
122
        return $this->reverseSequenceGenerator;
123
    }
124
125
    /**
126
     * Process the observer's business logic.
127
     *
128
     * @return void
129
     * @throws \Exception Is thrown, if either column "url_key" or "name" have a value set
130
     */
131
    protected function process()
132
    {
133
134
        // initialize the URL key, the entity and the product
135
        $urlKey = null;
136
        $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...
137
        $product = array();
138
139
        // prepare the store view code
140
        $this->getSubject()->prepareStoreViewCode();
141
142
        // set the entity ID for the product with the passed SKU
143
        if ($entity = $this->loadProduct($sku = $this->getValue(ColumnKeys::SKU))) {
144
            $this->setIds($product = $entity);
145
        } else {
146
            $this->setIds(array());
147
            $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...
148
        }
149
150
        // query whether or not the URL key column has a value
151
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
152
            $urlKey = $this->getValue(ColumnKeys::URL_KEY);
153
        } else {
154
            // query whether or not the existing product `url_key` should be re-created from the product name
155
            if (is_array($entity) && !$this->getSubject()->getConfiguration()->getParam(ConfigurationKeys::UPDATE_URL_KEY_FROM_NAME, true)) {
156
                // if the product already exists and NO re-creation from the product name has to
157
                // be done, load the original `url_key`from the product and use that to proceed
158
                $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...
159
            }
160
161
            // try to load the value from column `name` if URL key is still
162
            // empty, because we need it to process the the rewrites later on
163
            if ($urlKey === null || $urlKey === '' && $this->hasValue(ColumnKeys::NAME)) {
164
                $urlKey = $this->convertNameToUrlKey($this->getValue(ColumnKeys::NAME));
165
            }
166
        }
167
168
        // stop processing, if no URL key is available
169
        if ($urlKey === null || $urlKey === '') {
170
            // throw an exception, that the URL key can not be
171
            // initialized and we're in the default store view
172
            if ($this->getStoreViewCode(StoreViewCodes::ADMIN) === StoreViewCodes::ADMIN) {
173
                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));
174
            }
175
            // stop processing, because we're in a store
176
            // view row and a URL key is not mandatory
177
            return;
178
        }
179
180
        // set the unique URL key for further processing
181
        $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...
182
    }
183
184
    /**
185
     * Extract's the category from the comma separeted list of categories
186
     * in column `categories` and return's an array with their URL paths.
187
     *
188
     * @return string[] Array with the URL paths of the categories found in column `categories`
189
     */
190
    protected function getUrlPaths()
191
    {
192
193
        // initialize the array for the URL paths of the cateogries
194
        $urlPaths = array();
195
196
        // extract the categories from the column `categories`
197
        $categories = $this->getValue(ColumnKeys::CATEGORIES, array(), array($this, 'explode'));
198
199
        // the URL paths are store view specific, so we need
200
        // the store view code to load the appropriate ones
201
        $storeViewCode = $this->getStoreViewCode(StoreViewCodes::ADMIN);
202
203
        // iterate of the found categories, load their URL path as well as the URL path of
204
        // parent categories, if they have the anchor flag activated and add it the array
205
        foreach ($categories as $path) {
206
            // load the category based on the category path
207
            $category = $this->getCategoryByPath($path, $storeViewCode);
208
            // try to resolve the URL paths recursively
209
            $this->resolveUrlPaths($urlPaths, $category, $storeViewCode);
210
        }
211
212
        // return the array with the recursively resolved URL paths
213
        // of the categories that are related with the products
214
        return $urlPaths;
215
    }
216
217
    /**
218
     * Recursively resolves an array with the store view specific
219
     * URL paths of the passed category.
220
     *
221
     * @param array  $urlPaths       The array to append the URL paths to
222
     * @param array  $category       The category to resolve the list with URL paths
223
     * @param string $storeViewCode  The store view code to resolve the URL paths for
224
     * @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
225
     *
226
     * @return void
227
     */
228
    protected function resolveUrlPaths(array &$urlPaths, array $category, string $storeViewCode, bool $directRelation = true)
229
    {
230
231
        // load the root category
232
        $rootCategory = $this->getRootCategory();
233
234
        // try to resolve the parent category IDs, but only if either
235
        // the actual category nor it's parent is a root category
236
        if ($rootCategory[MemberNames::ENTITY_ID] !== $category[MemberNames::ENTITY_ID] &&
237
            $rootCategory[MemberNames::ENTITY_ID] !== $category[MemberNames::PARENT_ID]
238
        ) {
239
            // load the parent category
240
            $parent = $this->getCategory($category[MemberNames::PARENT_ID], $storeViewCode);
241
            // also resolve the URL paths for the parent category
242
            $this->resolveUrlPaths($urlPaths, $parent, $storeViewCode, false);
243
        }
244
245
        // query whether or not the URL path already
246
        // is part of the list (to avoid duplicates)
247
        if (in_array($category[MemberNames::URL_PATH], $urlPaths)) {
248
            return;
249
        }
250
251
        // append the URL path, either if we've a direct relation
252
        // or the category has the anchor flag actvated
253
        if ($directRelation === true || ($category[MemberNames::IS_ANCHOR] === 1 && $directRelation === false)) {
254
            $urlPaths[] = $category[MemberNames::URL_PATH];
255
        }
256
    }
257
258
    /**
259
     * Temporarily persist's the IDs of the passed product.
260
     *
261
     * @param array $product The product to temporarily persist the IDs for
262
     *
263
     * @return void
264
     */
265
    protected function setIds(array $product)
266
    {
267
        $this->setLastEntityId(isset($product[MemberNames::ENTITY_ID]) ? $product[MemberNames::ENTITY_ID] : null);
268
    }
269
270
    /**
271
     * Set's the ID of the product that has been created recently.
272
     *
273
     * @param string $lastEntityId The entity ID
274
     *
275
     * @return void
276
     */
277
    protected function setLastEntityId($lastEntityId)
278
    {
279
        $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...
280
    }
281
282
    /**
283
     * Return's the PK to of the product.
284
     *
285
     * @return integer The PK to create the relation with
286
     */
287
    protected function getPrimaryKey()
288
    {
289
        $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...
290
    }
291
292
    /**
293
     * Load's and return's the product with the passed SKU.
294
     *
295
     * @param string $sku The SKU of the product to load
296
     *
297
     * @return array The product
298
     */
299
    protected function loadProduct($sku)
300
    {
301
        return $this->getProductBunchProcessor()->loadProduct($sku);
302
    }
303
304
    /**
305
     * Load's and return's the url_key with the passed primary ID.
306
     *
307
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject      The subject to load the URL key
308
     * @param int                                                       $primaryKeyId The ID from product
309
     *
310
     * @return string|null url_key or null
311
     */
312
    protected function loadUrlKey(UrlKeyAwareSubjectInterface $subject, $primaryKeyId)
313
    {
314
        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...
315
    }
316
317
    /**
318
     * Return's the category with the passed path.
319
     *
320
     * @param string $path          The path of the category to return
321
     * @param string $storeViewCode The code of a store view, defaults to admin
322
     *
323
     * @return array The category
324
     */
325
    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...
326
    {
327
        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...
328
    }
329
330
    /**
331
     * Make's the passed URL key unique by adding the next number to the end.
332
     *
333
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject  The subject to make the URL key unique for
334
     * @param array                                                     $entity   The entity to make the URL key unique for
335
     * @param string                                                    $urlKey   The URL key to make unique
336
     * @param array                                                     $urlPaths The URL paths to make unique
337
     *
338
     * @return string The unique URL key
339
     */
340
    protected function makeUnique(UrlKeyAwareSubjectInterface $subject, array $entity, string $urlKey, array $urlPaths = array())
341
    {
342
        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...
343
    }
344
345
    /**
346
     * Return's the root category for the actual view store.
347
     *
348
     * @return array The store's root category
349
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
350
     */
351
    protected function getRootCategory()
352
    {
353
        return $this->getSubject()->getRootCategory();
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 getRootCategory() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\AbstractEavSubject, TechDivision\Import\Subjects\AbstractSubject, TechDivision\Import\Subjects\MoveFilesSubject, TechDivision\Import\Subjects\ValidatorSubject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
354
    }
355
356
    /**
357
     * Return's the category with the passed ID.
358
     *
359
     * @param integer $categoryId    The ID of the category to return
360
     * @param string  $storeViewCode The code of a store view, defaults to "admin"
361
     *
362
     * @return array The category data
363
     */
364
    protected function getCategory($categoryId, $storeViewCode = StoreViewCodes::ADMIN)
365
    {
366
        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...
367
    }
368
}
369