Completed
Push — master ( 70d444...d1ef88 )
by Tim
11s
created

UrlRewriteObserver::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
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\Product\Services\ProductBunchProcessorInterface;
24
use TechDivision\Import\Product\Utils\ColumnKeys;
25
use TechDivision\Import\Product\Utils\CoreConfigDataKeys;
26
use TechDivision\Import\Product\Utils\MemberNames;
27
use TechDivision\Import\Utils\StoreViewCodes;
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 array with the URL rewrites that has to be created.
71
     *
72
     * @var array
73
     */
74
    protected $urlRewrites = array();
75
76
    /**
77
     * The product bunch processor instance.
78
     *
79
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
80
     */
81
    protected $productBunchProcessor;
82
83
    /**
84
     * Initialize the observer with the passed product bunch processor instance.
85
     *
86
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor The product bunch processor instance
87
     */
88 3
    public function __construct(ProductBunchProcessorInterface $productBunchProcessor)
89
    {
90 3
        $this->productBunchProcessor = $productBunchProcessor;
91 3
    }
92
93
    /**
94
     * Return's the product bunch processor instance.
95
     *
96
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
97
     */
98 3
    protected function getProductBunchProcessor()
99
    {
100 3
        return $this->productBunchProcessor;
101
    }
102
103
    /**
104
     * Process the observer's business logic.
105
     *
106
     * @return void
107
     */
108 3
    protected function process()
109
    {
110
111
        // query whether or not, we've found a new SKU => means we've found a new product
112 3
        if ($this->hasBeenProcessed($this->getValue(ColumnKeys::SKU))) {
113
            return;
114
        }
115
116
        // try to load the URL key, return immediately if not possible
117 3
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
118 3
            $this->urlKey = $urlKey = $this->getValue(ColumnKeys::URL_KEY);
119 3
        } else {
120
            return;
121
        }
122
123
        // initialize the store view code
124 3
        $this->prepareStoreViewCode();
0 ignored issues
show
Deprecated Code introduced by
The method TechDivision\Import\Obse...:prepareStoreViewCode() 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...
125
126
        // prepare the URL rewrites
127 3
        $this->prepareUrlRewrites();
128
129
        // iterate over the categories and create the URL rewrites
130 3
        foreach ($this->urlRewrites as $categoryId => $urlRewrite) {
131
            // initialize and persist the URL rewrite
132 3
            if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) {
133
                // initialize URL rewrite and catagory ID
134 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...
135 3
                $this->entityId = $urlRewrite[MemberNames::ENTITY_ID];
136 3
                $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...
137
138
                // initialize and persist the URL rewrite product => category relation
139 3
                $urlRewriteProductCategory = $this->initializeUrlRewriteProductCategory(
140 3
                    $this->prepareUrlRewriteProductCategoryAttributes()
141 3
                );
142
143
                // persist the URL rewrite product category relation
144 3
                $this->persistUrlRewriteProductCategory($urlRewriteProductCategory);
145 3
            }
146 3
        }
147
148
        // if changed, override the URL key with the new one
149 3
        if ($urlKey !== $this->urlKey) {
150
            $this->setValue(ColumnKeys::URL_KEY, $this->urlKey);
151
        }
152 3
    }
153
154
    /**
155
     * Initialize the category product with the passed attributes and returns an instance.
156
     *
157
     * @param array $attr The category product attributes
158
     *
159
     * @return array The initialized category product
160
     */
161 2
    protected function initializeUrlRewrite(array $attr)
162
    {
163 2
        return $attr;
164
    }
165
166
    /**
167
     * Initialize the URL rewrite product => category relation with the passed attributes
168
     * and returns an instance.
169
     *
170
     * @param array $attr The URL rewrite product => category relation attributes
171
     *
172
     * @return array The initialized URL rewrite product => category relation
173
     */
174 2
    protected function initializeUrlRewriteProductCategory($attr)
175
    {
176 2
        return $attr;
177
    }
178
179
    /**
180
     * Prepare's the URL rewrites that has to be created/updated.
181
     *
182
     * @return void
183
     */
184 3
    protected function prepareUrlRewrites()
185
    {
186
187
        // (re-)initialize the array for the URL rewrites
188 3
        $this->urlRewrites = array();
189
190
        // load the root category, because we need that to create the default product URL rewrite
191 3
        $rootCategory = $this->getRootCategory();
192
193
        // query whether or not categories has to be used as product URL suffix
194 3
        $productCategoryIds = array();
195 3
        if ($this->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_USE_CATEGORIES, false)) {
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...
196
            // if yes, add the category IDs of the products
197 3
            $productCategoryIds = $this->getProductCategoryIds();
198 3
        }
199
200
        // at least, add the root category ID to the category => product relations
201 3
        $productCategoryIds[$rootCategory[MemberNames::ENTITY_ID]] = $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...
202
203
        // prepare the URL rewrites
204 3
        foreach ($productCategoryIds as $categoryId => $entityId) {
205
            // set category/entity ID
206 3
            $this->categoryId = $categoryId;
207 3
            $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...
208
209
            // prepare the attributes for each URL rewrite
210 3
            $this->urlRewrites[$categoryId] = $this->prepareAttributes();
211 3
        }
212 3
    }
213
214
    /**
215
     * Prepare the attributes of the entity that has to be persisted.
216
     *
217
     * @return array The prepared attributes
218
     */
219 3
    protected function prepareAttributes()
220
    {
221
222
        // load the store ID to use
223 3
        $storeId = $this->getRowStoreId();
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...
224
225
        // load the category to create the URL rewrite for
226 3
        $category = $this->getCategory($this->categoryId);
227
228
        // initialize the values
229 3
        $requestPath = $this->prepareRequestPath($category);
230 3
        $targetPath = $this->prepareTargetPath($category);
231 3
        $metadata = serialize($this->prepareMetadata($category));
232
233
        // return the prepared URL rewrite
234 3
        return $this->initializeEntity(
235
            array(
236 3
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
237 3
                MemberNames::ENTITY_ID        => $this->entityId,
238 3
                MemberNames::REQUEST_PATH     => $requestPath,
239 3
                MemberNames::TARGET_PATH      => $targetPath,
240 3
                MemberNames::REDIRECT_TYPE    => 0,
241 3
                MemberNames::STORE_ID         => $storeId,
242 3
                MemberNames::DESCRIPTION      => null,
243 3
                MemberNames::IS_AUTOGENERATED => 1,
244 3
                MemberNames::METADATA         => $metadata
245 3
            )
246 3
        );
247
    }
248
249
    /**
250
     * Prepare's the URL rewrite product => category relation attributes.
251
     *
252
     * @return array The prepared attributes
253
     */
254 3
    protected function prepareUrlRewriteProductCategoryAttributes()
255
    {
256
257
        // return the prepared product
258 3
        return $this->initializeEntity(
259
            array(
260 3
                MemberNames::PRODUCT_ID => $this->entityId,
261 3
                MemberNames::CATEGORY_ID => $this->categoryId,
262 3
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
263 3
            )
264 3
        );
265
    }
266
267
    /**
268
     * Prepare's the target path for a URL rewrite.
269
     *
270
     * @param array $category The categroy with the URL path
271
     *
272
     * @return string The target path
273
     */
274 3
    protected function prepareTargetPath(array $category)
275
    {
276
277
        // load the actual entity ID
278 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...
279
280
        // query whether or not, the category is the root category
281 3
        if ($this->isRootCategory($category)) {
282 3
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
283 3
        } else {
284 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
285
        }
286
287
        // return the target path
288 3
        return $targetPath;
289
    }
290
291
    /**
292
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
293
     *
294
     * @param array $category The categroy with the URL path
295
     *
296
     * @return string The request path
297
     * @throws \RuntimeException Is thrown, if the passed category has no or an empty value for attribute "url_path"
298
     */
299 3
    protected function prepareRequestPath(array $category)
300
    {
301
302
        // load the product URL suffix to use
303 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...
304
305
        // create a unique URL key, if this is a new URL rewrite
306 3
        $this->urlKey = $this->makeUrlKeyUnique($this->urlKey);
307
308
        // query whether or not, the category is the root category
309 3
        if ($this->isRootCategory($category)) {
310 3
            return sprintf('%s%s', $this->urlKey, $urlSuffix);
311
        } else {
312
            // query whether or not the category's "url_path" attribute, necessary to create a valid "request_path", is available
313 2
            if (isset($category[MemberNames::URL_PATH]) && $category[MemberNames::URL_PATH]) {
314 2
                return sprintf('%s/%s%s', $category[MemberNames::URL_PATH], $this->urlKey, $urlSuffix);
315
            }
316
        }
317
318
        // throw an exception if the category's "url_path" attribute is NOT available
319
        throw new \RuntimeException(
320
            sprintf(
321
                'Can\'t find mandatory attribute "%s" for category ID "%d", necessary to build a valid "request_path"',
322
                MemberNames::URL_PATH,
323
                $category[MemberNames::ENTITY_ID]
324
            )
325
        );
326
    }
327
328
    /**
329
     * Prepare's the URL rewrite's metadata with the passed category values.
330
     *
331
     * @param array $category The category used for preparation
332
     *
333
     * @return array The metadata
334
     */
335 3
    protected function prepareMetadata(array $category)
336
    {
337
338
        // initialize the metadata
339 3
        $metadata = array();
340
341
        // query whether or not, the passed category IS the root category
342 3
        if ($this->isRootCategory($category)) {
343 3
            return $metadata;
344
        }
345
346
        // if not, set the category ID in the metadata
347 2
        $metadata['category_id'] = $category[MemberNames::ENTITY_ID];
348
349
        // return the metadata
350 2
        return $metadata;
351
    }
352
353
    /**
354
     * Make's the passed URL key unique by adding the next number to the end.
355
     *
356
     * @param string $urlKey The URL key to make unique
357
     *
358
     * @return string The unique URL key
359
     */
360 3
    protected function makeUrlKeyUnique($urlKey)
361
    {
362
363
        // initialize the entity type ID
364 3
        $entityType = $this->getEntityType();
365 3
        $entityTypeId = $entityType[MemberNames::ENTITY_TYPE_ID];
366
367
        // initialize the query parameters
368 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...
369
370
        // initialize the counter
371 3
        $counter = 0;
372
373
        // initialize the counters
374 3
        $matchingCounters = array();
375 3
        $notMatchingCounters = array();
376
377
        // pre-initialze the URL key to query for
378 3
        $value = $urlKey;
379
380
        do {
381
            // try to load the attribute
382 3
            $productVarcharAttribute = $this->getProductBunchProcessor()
383 3
                                            ->loadProductVarcharAttributeByAttributeCodeAndEntityTypeIdAndStoreIdAndValue(
384 3
                                                MemberNames::URL_KEY,
385 3
                                                $entityTypeId,
386 3
                                                $storeId,
387
                                                $value
388 3
                                            );
389
390
            // try to load the product's URL key
391 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...
392
                // this IS the URL key of the passed entity
393
                if ($this->isUrlKeyOf($productVarcharAttribute)) {
394
                    $matchingCounters[] = $counter;
395
                } else {
396
                    $notMatchingCounters[] = $counter;
397
                }
398
399
                // prepare the next URL key to query for
400
                $value = sprintf('%s-%d', $urlKey, ++$counter);
401
            }
402
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
403 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...
404
405
        // sort the array ascending according to the counter
406 3
        asort($matchingCounters);
407 3
        asort($notMatchingCounters);
408
409
        // this IS the URL key of the passed entity => we've an UPDATE
410 3
        if (sizeof($matchingCounters) > 0) {
411
            // load highest counter
412
            $counter = end($matchingCounters);
413
            // if the counter is > 0, we've to append it to the new URL key
414
            if ($counter > 0) {
415
                $urlKey = sprintf('%s-%d', $urlKey, $counter);
416
            }
417 3
        } elseif (sizeof($notMatchingCounters) > 0) {
418
            // create a new URL key by raising the counter
419
            $newCounter = end($notMatchingCounters);
420
            $urlKey = sprintf('%s-%d', $urlKey, ++$newCounter);
421
        }
422
423
        // return the passed URL key, if NOT
424 3
        return $urlKey;
425
    }
426
427
    /**
428
     * Return's TRUE, if the passed URL key varchar value IS related with the actual PK.
429
     *
430
     * @param array $productVarcharAttribute The varchar value to check
431
     *
432
     * @return boolean TRUE if the URL key is related, else FALSE
433
     */
434
    protected function isUrlKeyOf(array $productVarcharAttribute)
435
    {
436
        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...
437
    }
438
439
    /**
440
     * Return's the entity type for the configured entity type code.
441
     *
442
     * @return array The requested entity type
443
     * @throws \Exception Is thrown, if the requested entity type is not available
444
     */
445 3
    protected function getEntityType()
446
    {
447 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...
448
    }
449
450
    /**
451
     * Return's the root category for the actual view store.
452
     *
453
     * @return array The store's root category
454
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
455
     */
456 3
    protected function getRootCategory()
457
    {
458 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...
459
    }
460
461
    /**
462
     * Return's TRUE if the passed category IS the root category, else FALSE.
463
     *
464
     * @param array $category The category to query
465
     *
466
     * @return boolean TRUE if the passed category IS the root category
467
     */
468 3
    protected function isRootCategory(array $category)
469
    {
470
471
        // load the root category
472 3
        $rootCategory = $this->getRootCategory();
473
474
        // compare the entity IDs and return the result
475 3
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
476
    }
477
478
    /**
479
     * Return's the list with category IDs the product is related with.
480
     *
481
     * @return array The product's category IDs
482
     */
483 3
    protected function getProductCategoryIds()
484
    {
485 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...
486
    }
487
488
    /**
489
     * Return's the category with the passed ID.
490
     *
491
     * @param integer $categoryId The ID of the category to return
492
     *
493
     * @return array The category data
494
     */
495 2
    protected function getCategory($categoryId)
496
    {
497 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...
498
    }
499
500
    /**
501
     * Persist's the URL rewrite with the passed data.
502
     *
503
     * @param array $row The URL rewrite to persist
504
     *
505
     * @return string The ID of the persisted entity
506
     */
507 3
    protected function persistUrlRewrite($row)
508
    {
509 3
        return $this->getProductBunchProcessor()->persistUrlRewrite($row);
510
    }
511
512
    /**
513
     * Persist's the URL rewrite product => category relation with the passed data.
514
     *
515
     * @param array $row The URL rewrite product => category relation to persist
516
     *
517
     * @return void
518
     */
519 3
    protected function persistUrlRewriteProductCategory($row)
520
    {
521 3
        return $this->getProductBunchProcessor()->persistUrlRewriteProductCategory($row);
522
    }
523
}
524