Completed
Pull Request — master (#81)
by Tim
03:47
created

UrlRewriteObserver::isNotVisible()   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
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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\VisibilityKeys;
27
use TechDivision\Import\Product\Utils\CoreConfigDataKeys;
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 key for the category in the metadata.
51
     *
52
     * @var string
53
     */
54
    const CATEGORY_ID = 'category_id';
55
56
    /**
57
     * The URL key from the CSV file column that has to be processed by the observer.
58
     *
59
     * @var string
60
     */
61
    protected $urlKey;
62
63
    /**
64
     * The actual category ID to process.
65
     *
66
     * @var integer
67
     */
68
    protected $categoryId;
69
70
    /**
71
     * The actual entity ID to process.
72
     *
73
     * @var integer
74
     */
75
    protected $entityId;
76
77
    /**
78
     * The ID of the recently created URL rewrite.
79
     *
80
     * @var integer
81
     */
82
    protected $urlRewriteId;
83
84
    /**
85
     * The array with the URL rewrites that has to be created.
86
     *
87
     * @var array
88
     */
89
    protected $urlRewrites = array();
90
91
    /**
92
     * The array with the category IDs related with the product.
93
     *
94
     * @var array
95
     */
96
    protected $productCategoryIds = array();
97
98
    /**
99
     * The product bunch processor instance.
100
     *
101
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
102
     */
103
    protected $productBunchProcessor;
104
105
    /**
106
     * Initialize the observer with the passed product bunch processor instance.
107
     *
108
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor The product bunch processor instance
109
     */
110 3
    public function __construct(ProductBunchProcessorInterface $productBunchProcessor)
111
    {
112 3
        $this->productBunchProcessor = $productBunchProcessor;
113 3
    }
114
115
    /**
116
     * Return's the product bunch processor instance.
117
     *
118
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
119
     */
120 3
    protected function getProductBunchProcessor()
121
    {
122 3
        return $this->productBunchProcessor;
123
    }
124
125
    /**
126
     * Process the observer's business logic.
127
     *
128
     * @return void
129
     */
130 3
    protected function process()
131
    {
132
133
        // initialize the store view code
134 3
        $this->getSubject()->prepareStoreViewCode();
135
136
        // load the SKU and the store view code
137 3
        $sku = $this->getValue($this->getPrimaryKeyColumnName());
138 3
        $storeViewCode = $this->getSubject()->getStoreViewCode();
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 getStoreViewCode() 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...
139
140
        // only map the visibility of the product with the default store view
141 3
        if (!$this->hasBeenProcessed($sku)) {
142 3
            $this->addEntityIdVisibilityIdMapping($this->getValue(ColumnKeys::VISIBILITY));
143
        }
144
145
        // query whether or not the row has already been processed
146 3
        if ($this->storeViewHasBeenProcessed($sku, $storeViewCode)) {
147
            // log a message
148
            $this->getSubject()
149
                 ->getSystemLogger()
150
                 ->warning(
151
                     sprintf(
152
                         'URL rewrites for SKU + store view "%s" + "%s" has already been processed',
153
                         $sku,
154
                         $storeViewCode
155
                     )
156
                 );
157
158
            // return immediately
159
            return;
160
        };
161
162
        // try to load the URL key, return immediately if not possible
163 3
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
164 3
            $this->urlKey = $urlKey = $this->getValue(ColumnKeys::URL_KEY);
165
        } else {
166
            return;
167
        }
168
169
        // prepare the URL rewrites
170 3
        $this->prepareUrlRewrites();
171
172
        // do NOT create new URL rewrites, if the product is NOT visible (any more), BUT
173
        // handle existing URL rewrites, e. g. to remove and clean up the URL rewrites
174 3
        if ($this->isNotVisible()) {
175
            return;
176
        }
177
178
        // iterate over the categories and create the URL rewrites
179 3
        foreach ($this->urlRewrites as $categoryId => $urlRewrite) {
180
            // initialize and persist the URL rewrite
181 3
            if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) {
182
                // initialize URL rewrite and catagory ID
183 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...
184 3
                $this->entityId = $urlRewrite[MemberNames::ENTITY_ID];
185 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...
186
187
                // initialize and persist the URL rewrite product => category relation
188 3
                $urlRewriteProductCategory = $this->initializeUrlRewriteProductCategory(
189 3
                    $this->prepareUrlRewriteProductCategoryAttributes()
190
                );
191
192
                // persist the URL rewrite product category relation
193 3
                $this->persistUrlRewriteProductCategory($urlRewriteProductCategory);
194
            }
195
        }
196
197
        // if changed, e. g. the request path of the URL rewrite has been suffixed with a
198
        // number because another one with the same request path for an other entity and
199
        // a different store view already exists, then override the old URL key with the
200
        // new generated one
201 3
        if ($urlKey !== $this->urlKey) {
202
            $this->setValue(ColumnKeys::URL_KEY, $this->urlKey);
203
        }
204 3
    }
205
206
    /**
207
     * Initialize the category product with the passed attributes and returns an instance.
208
     *
209
     * @param array $attr The category product attributes
210
     *
211
     * @return array The initialized category product
212
     */
213 2
    protected function initializeUrlRewrite(array $attr)
214
    {
215 2
        return $attr;
216
    }
217
218
    /**
219
     * Initialize the URL rewrite product => category relation with the passed attributes
220
     * and returns an instance.
221
     *
222
     * @param array $attr The URL rewrite product => category relation attributes
223
     *
224
     * @return array The initialized URL rewrite product => category relation
225
     */
226 2
    protected function initializeUrlRewriteProductCategory($attr)
227
    {
228 2
        return $attr;
229
    }
230
231
    /**
232
     * Prepare's the URL rewrites that has to be created/updated.
233
     *
234
     * @return void
235
     */
236 3
    protected function prepareUrlRewrites()
237
    {
238
239
        // (re-)initialize the array for the URL rewrites and the product category IDs
240 3
        $this->urlRewrites = array();
241 3
        $this->productCategoryIds = array();
242
243
        // load the root category, because we need that to create the default product URL rewrite
244 3
        $rootCategory = $this->getRootCategory();
245
246
        // query whether or not categories has to be used as product URL suffix
247 3
        if ($this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_USE_CATEGORIES, false)) {
248
            // if yes, add the category IDs of the products
249 3
            foreach ($this->getProductCategoryIds() as $categoryId => $entityId) {
250 3
                $this->resolveCategoryIds($categoryId, $entityId, true);
251
            }
252
        }
253
254
        // at least, add the root category ID to the category => product relations
255 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...
256
257
        // prepare the URL rewrites
258 3
        foreach ($this->productCategoryIds as $categoryId => $entityId) {
259
            // set category/entity ID
260 3
            $this->entityId = $entityId;
261 3
            $this->categoryId = $categoryId;
262
263
            // prepare the attributes for each URL rewrite
264 3
            $this->urlRewrites[$categoryId] = $this->prepareAttributes();
265
        }
266 3
    }
267
268
    /**
269
     * Resolve's the parent categories of the category with the passed ID and relate's
270
     * it with the product with the passed ID, if the category is top level OR has the
271
     * anchor flag set.
272
     *
273
     * @param integer $categoryId The ID of the category to resolve the parents
274
     * @param integer $entityId   The ID of the product entity to relate the category with
275
     * @param boolean $topLevel   TRUE if the passed category has top level, else FALSE
276
     *
277
     * @return void
278
     */
279 3
    protected function resolveCategoryIds($categoryId, $entityId, $topLevel = false)
280
    {
281
282
        // return immediately if this is the absolute root node
283 3
        if ((integer) $categoryId === 1) {
284 3
            return;
285
        }
286
287
        // load the data of the category with the passed ID
288 3
        $category = $this->getCategory($categoryId);
289
290
        // query whether or not the product has already been related
291 3
        if (!isset($this->productCategoryIds[$categoryId])) {
292 3
            if ($topLevel) {
293
                // relate it, if the category is top level
294 3
                $this->productCategoryIds[$categoryId] = $entityId;
295
            } elseif ((integer) $category[MemberNames::IS_ANCHOR] === 1) {
296
                // also relate it, if the category is not top level, but has the anchor flag set
297
                $this->productCategoryIds[$categoryId] = $entityId;
298
            } else {
299
                $this->getSubject()
300
                     ->getSystemLogger()
301
                     ->debug(sprintf('Don\'t create URL rewrite for category "%s" because of missing anchor flag', $category[MemberNames::PATH]));
302
            }
303
        }
304
305
        // load the root category
306 3
        $rootCategory = $this->getRootCategory();
307
308
        // try to resolve the parent category IDs
309 3
        if ($rootCategory[MemberNames::ENTITY_ID] !== ($parentId = $category[MemberNames::PARENT_ID])) {
310 3
            $this->resolveCategoryIds($parentId, $entityId, false);
311
        }
312 3
    }
313
314
    /**
315
     * Prepare the attributes of the entity that has to be persisted.
316
     *
317
     * @return array The prepared attributes
318
     */
319 3
    protected function prepareAttributes()
320
    {
321
322
        // load the store ID to use
323 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...
324
325
        // load the category to create the URL rewrite for
326 3
        $category = $this->getCategory($this->categoryId);
327
328
        // initialize the values
329 3
        $requestPath = $this->prepareRequestPath($category);
330 3
        $targetPath = $this->prepareTargetPath($category);
331 3
        $metadata = serialize($this->prepareMetadata($category));
332
333
        // return the prepared URL rewrite
334 3
        return $this->initializeEntity(
335
            array(
336 3
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
337 3
                MemberNames::ENTITY_ID        => $this->entityId,
338 3
                MemberNames::REQUEST_PATH     => $requestPath,
339 3
                MemberNames::TARGET_PATH      => $targetPath,
340 3
                MemberNames::REDIRECT_TYPE    => 0,
341 3
                MemberNames::STORE_ID         => $storeId,
342 3
                MemberNames::DESCRIPTION      => null,
343 3
                MemberNames::IS_AUTOGENERATED => 1,
344 3
                MemberNames::METADATA         => $metadata
345
            )
346
        );
347
    }
348
349
    /**
350
     * Prepare's the URL rewrite product => category relation attributes.
351
     *
352
     * @return array The prepared attributes
353
     */
354 3
    protected function prepareUrlRewriteProductCategoryAttributes()
355
    {
356
357
        // return the prepared product
358 3
        return $this->initializeEntity(
359
            array(
360 3
                MemberNames::PRODUCT_ID => $this->entityId,
361 3
                MemberNames::CATEGORY_ID => $this->categoryId,
362 3
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
363
            )
364
        );
365
    }
366
367
    /**
368
     * Prepare's the target path for a URL rewrite.
369
     *
370
     * @param array $category The categroy with the URL path
371
     *
372
     * @return string The target path
373
     */
374 3
    protected function prepareTargetPath(array $category)
375
    {
376
377
        // load the actual entity ID
378 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...
379
380
        // query whether or not, the category is the root category
381 3
        if ($this->isRootCategory($category)) {
382 3
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
383
        } else {
384 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
385
        }
386
387
        // return the target path
388 3
        return $targetPath;
389
    }
390
391
    /**
392
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
393
     *
394
     * @param array $category The categroy with the URL path
395
     *
396
     * @return string The request path
397
     * @throws \RuntimeException Is thrown, if the passed category has no or an empty value for attribute "url_path"
398
     */
399 3
    protected function prepareRequestPath(array $category)
400
    {
401
402
        // load the product URL suffix to use
403 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...
404
405
        // create a unique URL key, if this is a new URL rewrite
406 3
        $this->urlKey = $this->makeUrlKeyUnique($this->urlKey);
407
408
        // query whether or not, the category is the root category
409 3
        if ($this->isRootCategory($category)) {
410 3
            return sprintf('%s%s', $this->urlKey, $urlSuffix);
411
        } else {
412
            // query whether or not the category's "url_path" attribute, necessary to create a valid "request_path", is available
413 2
            if (isset($category[MemberNames::URL_PATH]) && $category[MemberNames::URL_PATH]) {
414 2
                return sprintf('%s/%s%s', $category[MemberNames::URL_PATH], $this->urlKey, $urlSuffix);
415
            }
416
        }
417
418
        // throw an exception if the category's "url_path" attribute is NOT available
419
        throw new \RuntimeException(
420
            sprintf(
421
                'Can\'t find mandatory attribute "%s" for category ID "%d", necessary to build a valid "request_path"',
422
                MemberNames::URL_PATH,
423
                $category[MemberNames::ENTITY_ID]
424
            )
425
        );
426
    }
427
428
    /**
429
     * Prepare's the URL rewrite's metadata with the passed category values.
430
     *
431
     * @param array $category The category used for preparation
432
     *
433
     * @return array The metadata
434
     */
435 3
    protected function prepareMetadata(array $category)
436
    {
437
438
        // initialize the metadata
439 3
        $metadata = array();
440
441
        // query whether or not, the passed category IS the root category
442 3
        if ($this->isRootCategory($category)) {
443 3
            return $metadata;
444
        }
445
446
        // if not, set the category ID in the metadata
447 2
        $metadata[UrlRewriteObserver::CATEGORY_ID] = $category[MemberNames::ENTITY_ID];
448
449
        // return the metadata
450 2
        return $metadata;
451
    }
452
453
    /**
454
     * Make's the passed URL key unique by adding the next number to the end.
455
     *
456
     * @param string $urlKey The URL key to make unique
457
     *
458
     * @return string The unique URL key
459
     */
460 3
    protected function makeUrlKeyUnique($urlKey)
461
    {
462
463
        // initialize the entity type ID
464 3
        $entityType = $this->getEntityType();
465 3
        $entityTypeId = $entityType[MemberNames::ENTITY_TYPE_ID];
466
467
        // initialize the query parameters
468 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...
469
470
        // initialize the counter
471 3
        $counter = 0;
472
473
        // initialize the counters
474 3
        $matchingCounters = array();
475 3
        $notMatchingCounters = array();
476
477
        // pre-initialze the URL key to query for
478 3
        $value = $urlKey;
479
480
        do {
481
            // try to load the attribute
482 3
            $productVarcharAttribute = $this->getProductBunchProcessor()
483 3
                                            ->loadProductVarcharAttributeByAttributeCodeAndEntityTypeIdAndStoreIdAndValue(
484 3
                                                MemberNames::URL_KEY,
485 3
                                                $entityTypeId,
486 3
                                                $storeId,
487 3
                                                $value
488
                                            );
489
490
            // try to load the product's URL key
491 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...
492
                // this IS the URL key of the passed entity
493
                if ($this->isUrlKeyOf($productVarcharAttribute)) {
494
                    $matchingCounters[] = $counter;
495
                } else {
496
                    $notMatchingCounters[] = $counter;
497
                }
498
499
                // prepare the next URL key to query for
500
                $value = sprintf('%s-%d', $urlKey, ++$counter);
501
            }
502
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
503 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...
504
505
        // sort the array ascending according to the counter
506 3
        asort($matchingCounters);
507 3
        asort($notMatchingCounters);
508
509
        // this IS the URL key of the passed entity => we've an UPDATE
510 3
        if (sizeof($matchingCounters) > 0) {
511
            // load highest counter
512
            $counter = end($matchingCounters);
513
            // if the counter is > 0, we've to append it to the new URL key
514
            if ($counter > 0) {
515
                $urlKey = sprintf('%s-%d', $urlKey, $counter);
516
            }
517 3
        } elseif (sizeof($notMatchingCounters) > 0) {
518
            // create a new URL key by raising the counter
519
            $newCounter = end($notMatchingCounters);
520
            $urlKey = sprintf('%s-%d', $urlKey, ++$newCounter);
521
        }
522
523
        // return the passed URL key, if NOT
524 3
        return $urlKey;
525
    }
526
527
    /**
528
     * Query whether or not the actual entity is visible or not.
529
     *
530
     * @return boolean TRUE if the entity is NOT visible, else FALSE
531
     */
532 3
    protected function isNotVisible()
533
    {
534 3
        return $this->getEntityIdVisibilityIdMapping() === VisibilityKeys::VISIBILITY_NOT_VISIBLE;
535
    }
536
537
    /**
538
     * Return's the visibility for the passed entity ID, if it already has been mapped. The mapping will be created
539
     * by calling <code>\TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue</code> which will
540
     * be done by the <code>\TechDivision\Import\Product\Callbacks\VisibilityCallback</code>.
541
     *
542
     * @return integer The visibility ID
543
     * @throws \Exception Is thrown, if the entity ID has not been mapped
544
     * @see \TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue()
545
     */
546 3
    protected function getEntityIdVisibilityIdMapping()
547
    {
548 3
        return $this->getSubject()->getEntityIdVisibilityIdMapping();
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 getEntityIdVisibilityIdMapping() 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...
549
    }
550
551
    /**
552
     * Return's TRUE, if the passed URL key varchar value IS related with the actual PK.
553
     *
554
     * @param array $productVarcharAttribute The varchar value to check
555
     *
556
     * @return boolean TRUE if the URL key is related, else FALSE
557
     */
558
    protected function isUrlKeyOf(array $productVarcharAttribute)
559
    {
560
        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...
561
    }
562
563
    /**
564
     * Return's the entity type for the configured entity type code.
565
     *
566
     * @return array The requested entity type
567
     * @throws \Exception Is thrown, if the requested entity type is not available
568
     */
569 3
    protected function getEntityType()
570
    {
571 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...
572
    }
573
574
    /**
575
     * Return's the root category for the actual view store.
576
     *
577
     * @return array The store's root category
578
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
579
     */
580 3
    protected function getRootCategory()
581
    {
582 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...
583
    }
584
585
    /**
586
     * Return's TRUE if the passed category IS the root category, else FALSE.
587
     *
588
     * @param array $category The category to query
589
     *
590
     * @return boolean TRUE if the passed category IS the root category
591
     */
592 3
    protected function isRootCategory(array $category)
593
    {
594
595
        // load the root category
596 3
        $rootCategory = $this->getRootCategory();
597
598
        // compare the entity IDs and return the result
599 3
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
600
    }
601
602
    /**
603
     * Return's the list with category IDs the product is related with.
604
     *
605
     * @return array The product's category IDs
606
     */
607 3
    protected function getProductCategoryIds()
608
    {
609 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...
610
    }
611
612
    /**
613
     * Return's the category with the passed ID.
614
     *
615
     * @param integer $categoryId The ID of the category to return
616
     *
617
     * @return array The category data
618
     */
619 2
    protected function getCategory($categoryId)
620
    {
621 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...
622
    }
623
624
    /**
625
     * Persist's the URL rewrite with the passed data.
626
     *
627
     * @param array $row The URL rewrite to persist
628
     *
629
     * @return string The ID of the persisted entity
630
     */
631 3
    protected function persistUrlRewrite($row)
632
    {
633 3
        return $this->getProductBunchProcessor()->persistUrlRewrite($row);
634
    }
635
636
    /**
637
     * Persist's the URL rewrite product => category relation with the passed data.
638
     *
639
     * @param array $row The URL rewrite product => category relation to persist
640
     *
641
     * @return void
642
     */
643 3
    protected function persistUrlRewriteProductCategory($row)
644
    {
645 3
        return $this->getProductBunchProcessor()->persistUrlRewriteProductCategory($row);
646
    }
647
648
    /**
649
     * Return's the column name that contains the primary key.
650
     *
651
     * @return string the column name that contains the primary key
652
     */
653 3
    protected function getPrimaryKeyColumnName()
654
    {
655 3
        return ColumnKeys::SKU;
656
    }
657
658
    /**
659
     * Queries whether or not the passed SKU and store view code has already been processed.
660
     *
661
     * @param string $sku           The SKU to check been processed
662
     * @param string $storeViewCode The store view code to check been processed
663
     *
664
     * @return boolean TRUE if the SKU and store view code has been processed, else FALSE
665
     */
666 3
    protected function storeViewHasBeenProcessed($sku, $storeViewCode)
667
    {
668 3
        return $this->getSubject()->storeViewHasBeenProcessed($sku, $storeViewCode);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method storeViewHasBeenProcessed() 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...
669
    }
670
671
    /**
672
     * Add the entity ID => visibility mapping for the actual entity ID.
673
     *
674
     * @param string $visibility The visibility of the actual entity to map
675
     *
676
     * @return void
677
     */
678 3
    protected function addEntityIdVisibilityIdMapping($visibility)
679
    {
680 3
        $this->getSubject()->addEntityIdVisibilityIdMapping($visibility);
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 addEntityIdVisibilityIdMapping() 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...
681 3
    }
682
}
683