Completed
Pull Request — master (#67)
by Tim
08:20
created

UrlRewriteObserver::getRowStoreId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Observers\UrlRewriteObserver
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 TechDivision\Import\Utils\StoreViewCodes;
24
use TechDivision\Import\Product\Utils\ColumnKeys;
25
use TechDivision\Import\Product\Utils\MemberNames;
26
use TechDivision\Import\Product\Utils\CoreConfigDataKeys;
27
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
28
use TechDivision\Import\Product\Services\ProductBunchProcessorInterface;
29
30
/**
31
 * Observer that creates/updates the product's URL rewrites.
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
37
 * @link      http://www.techdivision.com
38
 */
39
class UrlRewriteObserver extends AbstractProductImportObserver
40
{
41
42
    /**
43
     * The entity type to load the URL rewrites for.
44
     *
45
     * @var string
46
     */
47
    const ENTITY_TYPE = 'product';
48
49
    /**
50
     * The URL key from the CSV file column that has to be processed by the observer.
51
     *
52
     * @var string
53
     */
54
    protected $urlKey;
55
56
    /**
57
     * The actual category ID to process.
58
     *
59
     * @var integer
60
     */
61
    protected $categoryId;
62
63
    /**
64
     * The actual entity ID to process.
65
     *
66
     * @var integer
67
     */
68
    protected $entityId;
69
70
    /**
71
     * The array with the URL rewrites that has to be created.
72
     *
73
     * @var array
74
     */
75
    protected $urlRewrites = array();
76
77
    /**
78
     * The product bunch processor instance.
79
     *
80
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
81
     */
82 3
    protected $productBunchProcessor;
83
84
    /**
85
     * Initialize the observer with the passed product bunch processor instance.
86 3
     *
87
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor The product bunch processor instance
88
     */
89
    public function __construct(ProductBunchProcessorInterface $productBunchProcessor)
90
    {
91 3
        $this->productBunchProcessor = $productBunchProcessor;
92 3
    }
93
94
    /**
95
     * Return's the product bunch processor instance.
96
     *
97
     * @return \TechDivision\Import\Services\ProductBunchProcessorInterface The product bunch processor instance
98 3
     */
99
    protected function getProductBunchProcessor()
100
    {
101 3
        return $this->productBunchProcessor;
102
    }
103
104 3
    /**
105
     * Process the observer's business logic.
106 3
     *
107
     * @return void
108 3
     */
109 3
    protected function process()
110 3
    {
111
112
        // query whether or not, we've found a new SKU => means we've found a new product
113 3
        if ($this->hasBeenProcessed($this->getValue(ColumnKeys::SKU))) {
114 3
            return;
115
        }
116
117
        // try to load the URL key, return immediately if not possible
118 3
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
119
            $this->urlKey = $urlKey = $this->getValue(ColumnKeys::URL_KEY);
120
        } else {
121
            return;
122
        }
123 3
124
        // initialize the store view code
125
        $this->prepareStoreViewCode();
126 3
127
        // prepare the URL rewrites
128
        $this->prepareUrlRewrites();
129
130
        // iterate over the categories and create the URL rewrites
131
        foreach ($this->urlRewrites as $categoryId => $urlRewrite) {
132
            // initialize and persist the URL rewrite
133
            if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) {
134
                // initialize URL rewrite and catagory ID
135 2
                $this->categoryId = $categoryId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $categoryId can also be of type string. However, the property $categoryId is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
136
                $this->entityId = $urlRewrite[MemberNames::ENTITY_ID];
137 2
                $this->urlRewriteId = $this->persistUrlRewrite($urlRewrite);
0 ignored issues
show
Bug introduced by
The property urlRewriteId does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
138
139
                // initialize and persist the URL rewrite product => category relation
140
                $urlRewriteProductCategory = $this->initializeUrlRewriteProductCategory(
141
                    $this->prepareUrlRewriteProductCategoryAttributes()
142
                );
143
144
                // persist the URL rewrite product category relation
145
                $this->persistUrlRewriteProductCategory($urlRewriteProductCategory);
146
            }
147
        }
148 2
149
        // if changed, override the URL key with the new one
150 2
        if ($urlKey !== $this->urlKey) {
151
            $this->setValue(ColumnKeys::URL_KEY, $this->urlKey);
152
        }
153
    }
154
155
    /**
156
     * Initialize the category product with the passed attributes and returns an instance.
157
     *
158 3
     * @param array $attr The category product attributes
159
     *
160
     * @return array The initialized category product
161
     */
162 3
    protected function initializeUrlRewrite(array $attr)
163
    {
164
        return $attr;
165 3
    }
166
167
    /**
168 3
     * Initialize the URL rewrite product => category relation with the passed attributes
169 3
     * and returns an instance.
170
     *
171 3
     * @param array $attr The URL rewrite product => category relation attributes
172
     *
173
     * @return array The initialized URL rewrite product => category relation
174
     */
175 3
    protected function initializeUrlRewriteProductCategory($attr)
176
    {
177
        return $attr;
178 3
    }
179
180 3
    /**
181 3
     * Prepare's the URL rewrites that has to be created/updated.
182
     *
183
     * @return void
184 3
     */
185
    protected function prepareUrlRewrites()
186 3
    {
187
188
        // (re-)initialize the array for the URL rewrites
189
        $this->urlRewrites = array();
190
191
        // load the root category, because we need that to create the default product URL rewrite
192
        $rootCategory = $this->getRootCategory();
193
194
        // query whether or not categories has to be used as product URL suffix
195 3
        $productCategoryIds = array();
196
        if ($this->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_USE_CATEGORIES, false)) {
197 3
            // if yes, add the category IDs of the products
198
            $productCategoryIds = $this->getProductCategoryIds();
199
        }
200
201
        // at least, add the root category ID to the category => product relations
202
        $productCategoryIds[$rootCategory[MemberNames::ENTITY_ID]] = $this->getLastEntityId();
203
204
        // prepare the URL rewrites
205 3
        foreach ($productCategoryIds as $categoryId => $entityId) {
206
            // set category/entity ID
207
            $this->categoryId = $categoryId;
208
            $this->entityId = $entityId;
0 ignored issues
show
Documentation Bug introduced by
The property $entityId was declared of type integer, but $entityId is of type string. Maybe add a type cast?

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

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

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
209 3
210
            // prepare the attributes for each URL rewrite
211
            $this->urlRewrites[$categoryId] = $this->prepareAttributes();
212 3
        }
213
    }
214
215 3
    /**
216 3
     * Prepare the attributes of the entity that has to be persisted.
217 3
     *
218
     * @return array The prepared attributes
219
     */
220 3
    protected function prepareAttributes()
221
    {
222 3
223 3
        // load the store ID to use
224 3
        $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);
225 3
226 3
        // load the category to create the URL rewrite for
227 3
        $category = $this->getCategory($this->categoryId);
228 3
229 3
        // initialize the values
230 3
        $requestPath = $this->prepareRequestPath($category);
231
        $targetPath = $this->prepareTargetPath($category);
232
        $metadata = serialize($this->prepareMetadata($category));
233
234
        // return the prepared URL rewrite
235
        return $this->initializeEntity(
236
            array(
237
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
238
                MemberNames::ENTITY_ID        => $this->entityId,
239
                MemberNames::REQUEST_PATH     => $requestPath,
240 3
                MemberNames::TARGET_PATH      => $targetPath,
241
                MemberNames::REDIRECT_TYPE    => 0,
242
                MemberNames::STORE_ID         => $storeId,
243
                MemberNames::DESCRIPTION      => null,
244 3
                MemberNames::IS_AUTOGENERATED => 1,
245
                MemberNames::METADATA         => $metadata
246 3
            )
247 3
        );
248 3
    }
249
250
    /**
251
     * Prepare's the URL rewrite product => category relation attributes.
252
     *
253
     * @return arry The prepared attributes
254
     */
255
    protected function prepareUrlRewriteProductCategoryAttributes()
256
    {
257
258
        // return the prepared product
259
        return $this->initializeEntity(
260 3
            array(
261
                MemberNames::PRODUCT_ID => $this->entityId,
262
                MemberNames::CATEGORY_ID => $this->categoryId,
263
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
264 3
            )
265
        );
266
    }
267 3
268 3
    /**
269
     * Prepare's the target path for a URL rewrite.
270 2
     *
271
     * @param array $category The categroy with the URL path
272
     *
273
     * @return string The target path
274 3
     */
275
    protected function prepareTargetPath(array $category)
276
    {
277
278
        // load the actual entity ID
279
        $lastEntityId = $this->getLastEntityId();
280
281
        // query whether or not, the category is the root category
282
        if ($this->isRootCategory($category)) {
283
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
284 3
        } else {
285
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
286
        }
287
288 3
        // return the target path
289
        return $targetPath;
290
    }
291 3
292
    /**
293
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
294 3
     *
295 3
     * @param array $category The categroy with the URL path
296
     *
297 2
     * @return string The request path
298
     */
299
    protected function prepareRequestPath(array $category)
300
    {
301 3
302
        // load the product URL suffix to use
303
        $urlSuffix = $this->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_URL_SUFFIX, '.html');
304
305
        // create a unique URL key, if this is a new URL rewrite
306
        $this->urlKey = $this->makeUrlKeyUnique($this->urlKey);
307
308
        // query whether or not, the category is the root category
309
        if ($this->isRootCategory($category)) {
310
            $requestPath = sprintf('%s%s', $this->urlKey, $urlSuffix);
311 3
        } else {
312
            $requestPath = sprintf('%s/%s%s', $category[MemberNames::URL_PATH], $this->urlKey, $urlSuffix);
313
        }
314
315 3
        // return the request path
316
        return $requestPath;
317
    }
318 3
319 3
    /**
320
     * Prepare's the URL rewrite's metadata with the passed category values.
321
     *
322
     * @param array $category The category used for preparation
323 2
     *
324
     * @return array The metadata
325
     */
326 2
    protected function prepareMetadata(array $category)
327
    {
328
329
        // initialize the metadata
330
        $metadata = array();
331
332
        // query whether or not, the passed category IS the root category
333
        if ($this->isRootCategory($category)) {
334
            return $metadata;
335
        }
336
337
        // if not, set the category ID in the metadata
338
        $metadata['category_id'] = $category[MemberNames::ENTITY_ID];
339
340
        // return the metadata
341
        return $metadata;
342
    }
343
344
    /**
345
     * Make's the passed URL key unique by adding the next number to the end.
346
     *
347
     * @param string $urlKey The URL key to make unique
348
     *
349
     * @return string The unique URL key
350
     */
351
    protected function makeUrlKeyUnique($urlKey)
352
    {
353
354
        // initialize the entity type ID
355
        $entityType = $this->getEntityType();
356
        $entityTypeId = $entityType[MemberNames::ENTITY_TYPE_ID];
357 3
358
        // initialize the query parameters
359 3
        $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);
360
361
        // initialize the counter
362
        $counter = 0;
363
364
        // initialize the counters
365
        $matchingCounters = array();
366
        $notMatchingCounters = array();
367
368
        // pre-initialze the URL key to query for
369 3
        $value = $urlKey;
370
371
        do {
372
            // try to load the attribute
373 3
            $productVarcharAttribute = $this->getProductBunchProcessor()
374
                                            ->loadProductVarcharAttributeByAttributeCodeAndEntityTypeIdAndStoreIdAndValue(
375
                                                MemberNames::URL_KEY,
376 3
                                                $entityTypeId,
377
                                                $storeId,
378
                                                $value
379
                                            );
380
381
            // try to load the product's URL key
382
            if ($productVarcharAttribute) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $productVarcharAttribute 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...
383
                // this IS the URL key of the passed entity
384
                if ($this->isUrlKeyOf($productVarcharAttribute)) {
385
                    $matchingCounters[] = $counter;
386
                } else {
387
                    $notMatchingCounters[] = $counter;
388 3
                }
389
390 3
                // prepare the next URL key to query for
391
                $value = sprintf('%s-%d', $urlKey, ++$counter);
392
            }
393
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
394
        } while ($productVarcharAttribute);
0 ignored issues
show
Bug Best Practice introduced by
The expression $productVarcharAttribute 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...
395
396
        // sort the array ascending according to the counter
397
        asort($matchingCounters);
398 3
        asort($notMatchingCounters);
399
400 3
        // this IS the URL key of the passed entity => we've an UPDATE
401
        if (sizeof($matchingCounters) > 0) {
402
            // load highest counter
403
            $counter = end($matchingCounters);
404
            // if the counter is > 0, we've to append it to the new URL key
405
            if ($counter > 0) {
406
                $urlKey = sprintf('%s-%d', $urlKey, $counter);
407
            }
408
        } elseif (sizeof($notMatchingCounters) > 0) {
409
            // create a new URL key by raising the counter
410 2
            $newCounter = end($notMatchingCounters);
411
            $urlKey = sprintf('%s-%d', $urlKey, ++$newCounter);
412 2
        }
413
414
        // return the passed URL key, if NOT
415
        return $urlKey;
416
    }
417
418
    /**
419
     * Return's TRUE, if the passed URL key varchar value IS related with the actual PK.
420
     *
421
     * @param array $productVarcharAttribute The varchar value to check
422 3
     *
423
     * @return boolean TRUE if the URL key is related, else FALSE
424 3
     */
425
    protected function isUrlKeyOf(array $productVarcharAttribute)
426
    {
427
        return $this->getSubject()->isUrlKeyOf($productVarcharAttribute);
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 isUrlKeyOf() does only exist in the following implementations of said interface: TechDivision\Import\Product\Subjects\BunchSubject.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
428
    }
429
430
    /**
431
     * Return's the entity type for the configured entity type code.
432
     *
433
     * @return array The requested entity type
434 3
     * @throws \Exception Is thrown, if the requested entity type is not available
435
     */
436 3
    protected function getEntityType()
437
    {
438
        return $this->getSubject()->getEntityType();
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 getEntityType() does only exist in the following implementations of said interface: TechDivision\Import\Product\Subjects\BunchSubject.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
439
    }
440
441
    /**
442
     * Return's the root category for the actual view store.
443
     *
444
     * @return array The store's root category
445
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
446
     */
447
    protected function getRootCategory()
448
    {
449
        return $this->getSubject()->getRootCategory();
450
    }
451
452
    /**
453
     * Return's TRUE if the passed category IS the root category, else FALSE.
454
     *
455
     * @param array $category The category to query
456
     *
457
     * @return boolean TRUE if the passed category IS the root category
458
     */
459
    protected function isRootCategory(array $category)
460
    {
461
462
        // load the root category
463
        $rootCategory = $this->getRootCategory();
464
465
        // compare the entity IDs and return the result
466
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
467
    }
468
469
    /**
470
     * Return's the list with category IDs the product is related with.
471
     *
472
     * @return array The product's category IDs
473
     */
474
    protected function getProductCategoryIds()
475
    {
476
        return $this->getSubject()->getProductCategoryIds();
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 getProductCategoryIds() does only exist in the following implementations of said interface: TechDivision\Import\Product\Subjects\BunchSubject.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
477
    }
478
479
    /**
480
     * Return's the category with the passed ID.
481
     *
482
     * @param integer $categoryId The ID of the category to return
483
     *
484
     * @return array The category data
485
     */
486
    protected function getCategory($categoryId)
487
    {
488
        return $this->getSubject()->getCategory($categoryId);
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...
489
    }
490
491
    /**
492
     * Persist's the URL rewrite with the passed data.
493
     *
494
     * @param array $row The URL rewrite to persist
495
     *
496
     * @return string The ID of the persisted entity
497
     */
498
    protected function persistUrlRewrite($row)
499
    {
500
        return $this->getProductBunchProcessor()->persistUrlRewrite($row);
501
    }
502
503
    /**
504
     * Persist's the URL rewrite product => category relation with the passed data.
505
     *
506
     * @param array $row The URL rewrite product => category relation to persist
507
     *
508
     * @return void
509
     */
510
    protected function persistUrlRewriteProductCategory($row)
511
    {
512
        return $this->getProductBunchProcessor()->persistUrlRewriteProductCategory($row);
513
    }
514
}
515