Completed
Push — master ( ea1c21...182e3a )
by Tim
12s
created

UrlRewriteObserver::process()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 40
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5.0634

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 19
cts 22
cp 0.8636
rs 8.439
c 0
b 0
f 0
cc 5
eloc 17
nc 7
nop 0
crap 5.0634
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\Services\ProductBunchProcessorInterface;
28
29
/**
30
 * Observer that creates/updates the product's URL rewrites.
31
 *
32
 * @author    Tim Wagner <[email protected]>
33
 * @copyright 2016 TechDivision GmbH <[email protected]>
34
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
35
 * @link      https://github.com/techdivision/import-product
36
 * @link      http://www.techdivision.com
37
 */
38
class UrlRewriteObserver extends AbstractProductImportObserver
39
{
40
41
    /**
42
     * The entity type to load the URL rewrites for.
43
     *
44
     * @var string
45
     */
46
    const ENTITY_TYPE = 'product';
47
48
    /**
49
     * The URL key from the CSV file column that has to be processed by the observer.
50
     *
51
     * @var string
52
     */
53
    protected $urlKey;
54
55
    /**
56
     * The actual category ID to process.
57
     *
58
     * @var integer
59
     */
60
    protected $categoryId;
61
62
    /**
63
     * The actual entity ID to process.
64
     *
65
     * @var integer
66
     */
67
    protected $entityId;
68
69
    /**
70
     * The ID of the recently created URL rewrite.
71
     *
72
     * @var integer
73
     */
74
    protected $urlRewriteId;
75
76
    /**
77
     * The array with the URL rewrites that has to be created.
78
     *
79
     * @var array
80
     */
81
    protected $urlRewrites = array();
82
83
    /**
84
     * The array with the category IDs related with the product.
85
     *
86
     * @var array
87
     */
88
    protected $productCategoryIds = array();
89
90
    /**
91
     * The product bunch processor instance.
92
     *
93
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
94
     */
95
    protected $productBunchProcessor;
96
97
    /**
98
     * Initialize the observer with the passed product bunch processor instance.
99
     *
100
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor The product bunch processor instance
101
     */
102 3
    public function __construct(ProductBunchProcessorInterface $productBunchProcessor)
103
    {
104 3
        $this->productBunchProcessor = $productBunchProcessor;
105 3
    }
106
107
    /**
108
     * Return's the product bunch processor instance.
109
     *
110
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
111
     */
112 3
    protected function getProductBunchProcessor()
113
    {
114 3
        return $this->productBunchProcessor;
115
    }
116
117
    /**
118
     * Process the observer's business logic.
119
     *
120
     * @return void
121
     */
122 3
    protected function process()
123
    {
124
125
        // try to load the URL key, return immediately if not possible
126 3
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
127 3
            $this->urlKey = $urlKey = $this->getValue(ColumnKeys::URL_KEY);
128 3
        } else {
129
            return;
130
        }
131
132
        // initialize the store view code
133 3
        $this->getSubject()->prepareStoreViewCode();
134
135
        // prepare the URL rewrites
136 3
        $this->prepareUrlRewrites();
137
138
        // iterate over the categories and create the URL rewrites
139 3
        foreach ($this->urlRewrites as $categoryId => $urlRewrite) {
140
            // initialize and persist the URL rewrite
141 3
            if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) {
142
                // initialize URL rewrite and catagory ID
143 3
                $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...
144 3
                $this->entityId = $urlRewrite[MemberNames::ENTITY_ID];
145 3
                $this->urlRewriteId = $this->persistUrlRewrite($urlRewrite);
0 ignored issues
show
Documentation Bug introduced by
The property $urlRewriteId was declared of type integer, but $this->persistUrlRewrite($urlRewrite) 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...
146
147
                // initialize and persist the URL rewrite product => category relation
148 3
                $urlRewriteProductCategory = $this->initializeUrlRewriteProductCategory(
149 3
                    $this->prepareUrlRewriteProductCategoryAttributes()
150 3
                );
151
152
                // persist the URL rewrite product category relation
153 3
                $this->persistUrlRewriteProductCategory($urlRewriteProductCategory);
154 3
            }
155 3
        }
156
157
        // if changed, override the URL key with the new one
158 3
        if ($urlKey !== $this->urlKey) {
159
            $this->setValue(ColumnKeys::URL_KEY, $this->urlKey);
160
        }
161 3
    }
162
163
    /**
164
     * Initialize the category product with the passed attributes and returns an instance.
165
     *
166
     * @param array $attr The category product attributes
167
     *
168
     * @return array The initialized category product
169
     */
170 2
    protected function initializeUrlRewrite(array $attr)
171
    {
172 2
        return $attr;
173
    }
174
175
    /**
176
     * Initialize the URL rewrite product => category relation with the passed attributes
177
     * and returns an instance.
178
     *
179
     * @param array $attr The URL rewrite product => category relation attributes
180
     *
181
     * @return array The initialized URL rewrite product => category relation
182
     */
183 2
    protected function initializeUrlRewriteProductCategory($attr)
184
    {
185 2
        return $attr;
186
    }
187
188
    /**
189
     * Prepare's the URL rewrites that has to be created/updated.
190
     *
191
     * @return void
192
     */
193 3
    protected function prepareUrlRewrites()
194
    {
195
196
        // (re-)initialize the array for the URL rewrites and the product category IDs
197 3
        $this->urlRewrites = array();
198 3
        $this->productCategoryIds = array();
199
200
        // load the root category, because we need that to create the default product URL rewrite
201 3
        $rootCategory = $this->getRootCategory();
202
203
        // query whether or not categories has to be used as product URL suffix
204 3
        if ($this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_USE_CATEGORIES, false)) {
205
            // if yes, add the category IDs of the products
206 3
            foreach ($this->getProductCategoryIds() as $categoryId => $entityId) {
207 3
                $this->resolveCategoryIds($categoryId, $entityId, true);
208 3
            }
209 3
        }
210
211
        // at least, add the root category ID to the category => product relations
212 3
        $this->productCategoryIds[$rootCategory[MemberNames::ENTITY_ID]] = $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...
213
214
        // prepare the URL rewrites
215 3
        foreach ($this->productCategoryIds as $categoryId => $entityId) {
216
            // set category/entity ID
217 3
            $this->entityId = $entityId;
218 3
            $this->categoryId = $categoryId;
219
220
            // prepare the attributes for each URL rewrite
221 3
            $this->urlRewrites[$categoryId] = $this->prepareAttributes();
222 3
        }
223 3
    }
224
225
    /**
226
     * Resolve's the parent categories of the category with the passed ID and relate's
227
     * it with the product with the passed ID, if the category is top level OR has the
228
     * anchor flag set.
229
     *
230
     * @param integer $categoryId The ID of the category to resolve the parents
231
     * @param integer $entityId   The ID of the product entity to relate the category with
232
     * @param boolean $topLevel   TRUE if the passed category has top level, else FALSE
233
     *
234
     * @return void
235
     */
236 3
    protected function resolveCategoryIds($categoryId, $entityId, $topLevel = false)
237
    {
238
239
        // return immediately if this is the absolute root node
240 3
        if ((integer) $categoryId === 1) {
241 3
            return;
242
        }
243
244
        // load the data of the category with the passed ID
245 3
        $category = $this->getCategory($categoryId);
246
247
        // query whether or not the product has already been related
248 3
        if (!isset($this->productCategoryIds[$categoryId])) {
249 3
            if ($topLevel) {
250
                // relate it, if the category is top level
251 3
                $this->productCategoryIds[$categoryId] = $entityId;
252 3
            } elseif ((integer) $category[MemberNames::IS_ANCHOR] === 1) {
253
                // relate it, if the category is not top level, but has the anchor flag set
254
                $this->productCategoryIds[$categoryId] = $entityId;
255
            } else {
256
                $this->getSubject()
257
                     ->getSystemLogger()
258
                     ->debug(sprintf('Can\'t create URL rewrite for category "%s" because of missing anchor flag', $category[MemberNames::PATH]));
259
            }
260 3
        }
261
262
        // load the root category
263 3
        $rootCategory = $this->getRootCategory();
264
265
        // try to resolve the parent category IDs
266 3
        if ($rootCategory[MemberNames::ENTITY_ID] !== ($parentId = $category[MemberNames::PARENT_ID])) {
267 3
            $this->resolveCategoryIds($parentId, $entityId, false);
268 3
        }
269 3
    }
270
271
    /**
272
     * Prepare the attributes of the entity that has to be persisted.
273
     *
274
     * @return array The prepared attributes
275
     */
276 3
    protected function prepareAttributes()
277
    {
278
279
        // load the store ID to use
280 3
        $storeId = $this->getSubject()->getRowStoreId();
0 ignored issues
show
Bug introduced by
The method getRowStoreId() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getRow()?

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

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

Loading history...
281
282
        // load the category to create the URL rewrite for
283 3
        $category = $this->getCategory($this->categoryId);
284
285
        // initialize the values
286 3
        $requestPath = $this->prepareRequestPath($category);
287 3
        $targetPath = $this->prepareTargetPath($category);
288 3
        $metadata = serialize($this->prepareMetadata($category));
289
290
        // return the prepared URL rewrite
291 3
        return $this->initializeEntity(
292
            array(
293 3
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
294 3
                MemberNames::ENTITY_ID        => $this->entityId,
295 3
                MemberNames::REQUEST_PATH     => $requestPath,
296 3
                MemberNames::TARGET_PATH      => $targetPath,
297 3
                MemberNames::REDIRECT_TYPE    => 0,
298 3
                MemberNames::STORE_ID         => $storeId,
299 3
                MemberNames::DESCRIPTION      => null,
300 3
                MemberNames::IS_AUTOGENERATED => 1,
301 3
                MemberNames::METADATA         => $metadata
302 3
            )
303 3
        );
304
    }
305
306
    /**
307
     * Prepare's the URL rewrite product => category relation attributes.
308
     *
309
     * @return array The prepared attributes
310
     */
311 3
    protected function prepareUrlRewriteProductCategoryAttributes()
312
    {
313
314
        // return the prepared product
315 3
        return $this->initializeEntity(
316
            array(
317 3
                MemberNames::PRODUCT_ID => $this->entityId,
318 3
                MemberNames::CATEGORY_ID => $this->categoryId,
319 3
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
320 3
            )
321 3
        );
322
    }
323
324
    /**
325
     * Prepare's the target path for a URL rewrite.
326
     *
327
     * @param array $category The categroy with the URL path
328
     *
329
     * @return string The target path
330
     */
331 3
    protected function prepareTargetPath(array $category)
332
    {
333
334
        // load the actual entity ID
335 3
        $lastEntityId = $this->getLastEntityId();
0 ignored issues
show
Deprecated Code introduced by
The method TechDivision\Import\Obse...rver::getLastEntityId() has been deprecated with message: Will be removed with version 1.0.0, use subject method instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
336
337
        // query whether or not, the category is the root category
338 3
        if ($this->isRootCategory($category)) {
339 3
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
340 3
        } else {
341 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
342
        }
343
344
        // return the target path
345 3
        return $targetPath;
346
    }
347
348
    /**
349
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
350
     *
351
     * @param array $category The categroy with the URL path
352
     *
353
     * @return string The request path
354
     * @throws \RuntimeException Is thrown, if the passed category has no or an empty value for attribute "url_path"
355
     */
356 3
    protected function prepareRequestPath(array $category)
357
    {
358
359
        // load the product URL suffix to use
360 3
        $urlSuffix = $this->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_URL_SUFFIX, '.html');
0 ignored issues
show
Deprecated Code introduced by
The method TechDivision\Import\Obse...er::getCoreConfigData() has been deprecated with message: Will be removed with version 1.0.0, use subject method instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
361
362
        // create a unique URL key, if this is a new URL rewrite
363 3
        $this->urlKey = $this->makeUrlKeyUnique($this->urlKey);
364
365
        // query whether or not, the category is the root category
366 3
        if ($this->isRootCategory($category)) {
367 3
            return sprintf('%s%s', $this->urlKey, $urlSuffix);
368
        } else {
369
            // query whether or not the category's "url_path" attribute, necessary to create a valid "request_path", is available
370 2
            if (isset($category[MemberNames::URL_PATH]) && $category[MemberNames::URL_PATH]) {
371 2
                return sprintf('%s/%s%s', $category[MemberNames::URL_PATH], $this->urlKey, $urlSuffix);
372
            }
373
        }
374
375
        // throw an exception if the category's "url_path" attribute is NOT available
376
        throw new \RuntimeException(
377
            sprintf(
378
                'Can\'t find mandatory attribute "%s" for category ID "%d", necessary to build a valid "request_path"',
379
                MemberNames::URL_PATH,
380
                $category[MemberNames::ENTITY_ID]
381
            )
382
        );
383
    }
384
385
    /**
386
     * Prepare's the URL rewrite's metadata with the passed category values.
387
     *
388
     * @param array $category The category used for preparation
389
     *
390
     * @return array The metadata
391
     */
392 3
    protected function prepareMetadata(array $category)
393
    {
394
395
        // initialize the metadata
396 3
        $metadata = array();
397
398
        // query whether or not, the passed category IS the root category
399 3
        if ($this->isRootCategory($category)) {
400 3
            return $metadata;
401
        }
402
403
        // if not, set the category ID in the metadata
404 2
        $metadata['category_id'] = $category[MemberNames::ENTITY_ID];
405
406
        // return the metadata
407 2
        return $metadata;
408
    }
409
410
    /**
411
     * Make's the passed URL key unique by adding the next number to the end.
412
     *
413
     * @param string $urlKey The URL key to make unique
414
     *
415
     * @return string The unique URL key
416
     */
417 3
    protected function makeUrlKeyUnique($urlKey)
418
    {
419
420
        // initialize the entity type ID
421 3
        $entityType = $this->getEntityType();
422 3
        $entityTypeId = $entityType[MemberNames::ENTITY_TYPE_ID];
423
424
        // initialize the query parameters
425 3
        $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);
0 ignored issues
show
Deprecated Code introduced by
The method TechDivision\Import\Obse...server::getRowStoreId() has been deprecated with message: Will be removed with version 1.0.0, use subject method instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
426
427
        // initialize the counter
428 3
        $counter = 0;
429
430
        // initialize the counters
431 3
        $matchingCounters = array();
432 3
        $notMatchingCounters = array();
433
434
        // pre-initialze the URL key to query for
435 3
        $value = $urlKey;
436
437
        do {
438
            // try to load the attribute
439 3
            $productVarcharAttribute = $this->getProductBunchProcessor()
440 3
                                            ->loadProductVarcharAttributeByAttributeCodeAndEntityTypeIdAndStoreIdAndValue(
441 3
                                                MemberNames::URL_KEY,
442 3
                                                $entityTypeId,
443 3
                                                $storeId,
444
                                                $value
445 3
                                            );
446
447
            // try to load the product's URL key
448 3
            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...
449
                // this IS the URL key of the passed entity
450
                if ($this->isUrlKeyOf($productVarcharAttribute)) {
451
                    $matchingCounters[] = $counter;
452
                } else {
453
                    $notMatchingCounters[] = $counter;
454
                }
455
456
                // prepare the next URL key to query for
457
                $value = sprintf('%s-%d', $urlKey, ++$counter);
458
            }
459
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
460 3
        } 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...
461
462
        // sort the array ascending according to the counter
463 3
        asort($matchingCounters);
464 3
        asort($notMatchingCounters);
465
466
        // this IS the URL key of the passed entity => we've an UPDATE
467 3
        if (sizeof($matchingCounters) > 0) {
468
            // load highest counter
469
            $counter = end($matchingCounters);
470
            // if the counter is > 0, we've to append it to the new URL key
471
            if ($counter > 0) {
472
                $urlKey = sprintf('%s-%d', $urlKey, $counter);
473
            }
474 3
        } elseif (sizeof($notMatchingCounters) > 0) {
475
            // create a new URL key by raising the counter
476
            $newCounter = end($notMatchingCounters);
477
            $urlKey = sprintf('%s-%d', $urlKey, ++$newCounter);
478
        }
479
480
        // return the passed URL key, if NOT
481 3
        return $urlKey;
482
    }
483
484
    /**
485
     * Return's TRUE, if the passed URL key varchar value IS related with the actual PK.
486
     *
487
     * @param array $productVarcharAttribute The varchar value to check
488
     *
489
     * @return boolean TRUE if the URL key is related, else FALSE
490
     */
491
    protected function isUrlKeyOf(array $productVarcharAttribute)
492
    {
493
        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...
494
    }
495
496
    /**
497
     * Return's the entity type for the configured entity type code.
498
     *
499
     * @return array The requested entity type
500
     * @throws \Exception Is thrown, if the requested entity type is not available
501
     */
502 3
    protected function getEntityType()
503
    {
504 3
        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...
505
    }
506
507
    /**
508
     * Return's the root category for the actual view store.
509
     *
510
     * @return array The store's root category
511
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
512
     */
513 3
    protected function getRootCategory()
514
    {
515 3
        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.

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...
516
    }
517
518
    /**
519
     * Return's TRUE if the passed category IS the root category, else FALSE.
520
     *
521
     * @param array $category The category to query
522
     *
523
     * @return boolean TRUE if the passed category IS the root category
524
     */
525 3
    protected function isRootCategory(array $category)
526
    {
527
528
        // load the root category
529 3
        $rootCategory = $this->getRootCategory();
530
531
        // compare the entity IDs and return the result
532 3
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
533
    }
534
535
    /**
536
     * Return's the list with category IDs the product is related with.
537
     *
538
     * @return array The product's category IDs
539
     */
540 3
    protected function getProductCategoryIds()
541
    {
542 3
        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...
543
    }
544
545
    /**
546
     * Return's the category with the passed ID.
547
     *
548
     * @param integer $categoryId The ID of the category to return
549
     *
550
     * @return array The category data
551
     */
552 2
    protected function getCategory($categoryId)
553
    {
554 2
        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...
555
    }
556
557
    /**
558
     * Persist's the URL rewrite with the passed data.
559
     *
560
     * @param array $row The URL rewrite to persist
561
     *
562
     * @return string The ID of the persisted entity
563
     */
564 3
    protected function persistUrlRewrite($row)
565
    {
566 3
        return $this->getProductBunchProcessor()->persistUrlRewrite($row);
567
    }
568
569
    /**
570
     * Persist's the URL rewrite product => category relation with the passed data.
571
     *
572
     * @param array $row The URL rewrite product => category relation to persist
573
     *
574
     * @return void
575
     */
576 3
    protected function persistUrlRewriteProductCategory($row)
577
    {
578 3
        return $this->getProductBunchProcessor()->persistUrlRewriteProductCategory($row);
579
    }
580
}
581