Completed
Pull Request — master (#2)
by Tim
03:25
created

UrlRewriteObserver::initializeUrlRewrite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\UrlRewrite\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-url-rewrite
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\UrlRewrite\Observers;
22
23
use TechDivision\Import\Utils\StoreViewCodes;
24
use TechDivision\Import\Product\UrlRewrite\Utils\ColumnKeys;
25
use TechDivision\Import\Product\UrlRewrite\Utils\MemberNames;
26
use TechDivision\Import\Product\Utils\VisibilityKeys;
27
use TechDivision\Import\Product\Utils\CoreConfigDataKeys;
28
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
29
use TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface;
30
31
/**
32
 * Observer that creates/updates the product's URL rewrites.
33
 *
34
 * @author    Tim Wagner <[email protected]>
35
 * @copyright 2016 TechDivision GmbH <[email protected]>
36
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
37
 * @link      https://github.com/techdivision/import-product-url-rewrite
38
 * @link      http://www.techdivision.com
39
 */
40
class UrlRewriteObserver extends AbstractProductImportObserver
41
{
42
43
    /**
44
     * The entity type to load the URL rewrites for.
45
     *
46
     * @var string
47
     */
48
    const ENTITY_TYPE = 'product';
49
50
    /**
51
     * The key for the category in the metadata.
52
     *
53
     * @var string
54
     */
55
    const CATEGORY_ID = 'category_id';
56
57
    /**
58
     * The URL key from the CSV file column that has to be processed by the observer.
59
     *
60
     * @var string
61
     */
62
    protected $urlKey;
63
64
    /**
65
     * The actual category ID to process.
66
     *
67
     * @var integer
68
     */
69
    protected $categoryId;
70
71
    /**
72
     * The actual entity ID to process.
73
     *
74
     * @var integer
75
     */
76
    protected $entityId;
77
78
    /**
79
     * The ID of the recently created URL rewrite.
80
     *
81
     * @var integer
82
     */
83
    protected $urlRewriteId;
84
85
    /**
86
     * The array with the URL rewrites that has to be created.
87
     *
88
     * @var array
89
     */
90
    protected $urlRewrites = array();
91
92
    /**
93
     * The array with the category IDs related with the product.
94
     *
95
     * @var array
96
     */
97
    protected $productCategoryIds = array();
98
99
    /**
100
     * The product bunch processor instance.
101
     *
102
     * @var \TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface
103
     */
104
    protected $productUrlRewriteProcessor;
105
106
    /**
107
     * Initialize the observer with the passed product URL rewrite processor instance.
108
     *
109
     * @param \TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface $productUrlRewriteProcessor The product URL rewrite processor instance
110
     */
111
    public function __construct(ProductUrlRewriteProcessorInterface $productUrlRewriteProcessor)
112
    {
113
        $this->productUrlRewriteProcessor = $productUrlRewriteProcessor;
114
    }
115
116
    /**
117
     * Return's the product bunch processor instance.
118
     *
119
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
120
     */
121
    protected function getProductUrlRewriteProcessor()
122
    {
123
        return $this->productUrlRewriteProcessor;
124
    }
125
126
    /**
127
     * Process the observer's business logic.
128
     *
129
     * @return void
130
     */
131
    protected function process()
132
    {
133
134
        // initialize the store view code
135
        $this->getSubject()->prepareStoreViewCode();
136
137
        // load the SKU
138
        $sku = $this->getValue($this->getPrimaryKeyColumnName());
139
140
        // load the store view - if no store view has been set we use the admin store view as default
141
        $storeViewCode = $this->getSubject()->getStoreViewCode(StoreViewCodes::ADMIN);
142
143
        // only map the visibility of the product with the default store view
144
        if (!$this->hasBeenProcessed($sku)) {
145
            $this->addEntityIdVisibilityIdMapping($this->getValue(ColumnKeys::VISIBILITY));
146
        }
147
148
        // query whether or not we're in the admin store view
149
        if ($storeViewCode === StoreViewCodes::ADMIN) {
150
            // stop processing as we don't want to create URL rewrites for the admin store view
151
            return;
152
        }
153
154
        // query whether or not the row has already been processed
155
        if ($this->storeViewHasBeenProcessed($sku, $storeViewCode)) {
156
            // log a message
157
            $this->getSubject()
158
                 ->getSystemLogger()
159
                 ->warning(
160
                     sprintf(
161
                         'URL rewrites for SKU "%s" + store view code "%s" has already been processed',
162
                         $sku,
163
                         $storeViewCode
164
                     )
165
                 );
166
167
            // return immediately
168
            return;
169
        };
170
171
        // try to load the URL key, return immediately if not possible
172
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
173
            $this->urlKey = $urlKey = $this->getValue(ColumnKeys::URL_KEY);
174
        } else {
175
            return;
176
        }
177
178
        // prepare the URL rewrites
179
        $this->prepareUrlRewrites();
180
181
        // do NOT create new URL rewrites, if the product is NOT visible (any more), BUT
182
        // handle existing URL rewrites, e. g. to remove and clean up the URL rewrites
183
        if ($this->isNotVisible()) {
184
            return;
185
        }
186
187
        // iterate over the categories and create the URL rewrites
188
        foreach ($this->urlRewrites as $categoryId => $urlRewrite) {
189
            // initialize and persist the URL rewrite
190
            if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) {
191
                // initialize URL rewrite and catagory ID
192
                $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...
193
                $this->entityId = $urlRewrite[MemberNames::ENTITY_ID];
194
195
                try {
196
                    // persist the URL rewrite
197
                    $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...
198
199
                    // initialize and persist the URL rewrite product => category relation
200
                    $urlRewriteProductCategory = $this->initializeUrlRewriteProductCategory(
201
                        $this->prepareUrlRewriteProductCategoryAttributes()
202
                    );
203
204
                    // persist the URL rewrite product category relation
205
                    $this->persistUrlRewriteProductCategory($urlRewriteProductCategory);
206
207
                } catch (\Exception $e) {
208
                    // query whether or not debug mode has been enabled
209
                    if ($this->getSubject()->isDebugMode()) {
210
                        $this->getSubject()->getSystemLogger()->warning($this->getSubject()->appendExceptionSuffix($e->getMessage()));
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 appendExceptionSuffix() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Prod...jects\UrlRewriteSubject, 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...
211
                    } else {
212
                        throw $e;
213
                    }
214
                }
215
            }
216
        }
217
218
        // if changed, e. g. the request path of the URL rewrite has been suffixed with a
219
        // number because another one with the same request path for an other entity and
220
        // a different store view already exists, then override the old URL key with the
221
        // new generated one
222
        if ($urlKey !== $this->urlKey) {
223
            $this->setValue(ColumnKeys::URL_KEY, $this->urlKey);
224
        }
225
    }
226
227
    /**
228
     * Initialize the category product with the passed attributes and returns an instance.
229
     *
230
     * @param array $attr The category product attributes
231
     *
232
     * @return array The initialized category product
233
     */
234
    protected function initializeUrlRewrite(array $attr)
235
    {
236
        return $attr;
237
    }
238
239
    /**
240
     * Initialize the URL rewrite product => category relation with the passed attributes
241
     * and returns an instance.
242
     *
243
     * @param array $attr The URL rewrite product => category relation attributes
244
     *
245
     * @return array The initialized URL rewrite product => category relation
246
     */
247
    protected function initializeUrlRewriteProductCategory($attr)
248
    {
249
        return $attr;
250
    }
251
252
    /**
253
     * Prepare's the URL rewrites that has to be created/updated.
254
     *
255
     * @return void
256
     */
257
    protected function prepareUrlRewrites()
258
    {
259
260
        // (re-)initialize the array for the URL rewrites and the product category IDs
261
        $this->urlRewrites = array();
262
        $this->productCategoryIds = array();
263
264
        // load the root category, because we need that to create the default product URL rewrite
265
        $rootCategory = $this->getRootCategory();
266
267
        // query whether or not categories has to be used as product URL suffix
268
        if ($this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_USE_CATEGORIES, false)) {
269
            // if yes, add the category IDs of the products
270
            foreach ($this->getProductCategoryIds() as $categoryId => $entityId) {
271
                $this->resolveCategoryIds($categoryId, $entityId, true);
272
            }
273
        }
274
275
        // at least, add the root category ID to the category => product relations
276
        $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\Prod...jects\UrlRewriteSubject, 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...
277
278
        // prepare the URL rewrites
279
        foreach ($this->productCategoryIds as $categoryId => $entityId) {
280
            // set category/entity ID
281
            $this->entityId = $entityId;
282
            $this->categoryId = $categoryId;
283
284
            // prepare the attributes for each URL rewrite
285
            $this->urlRewrites[$categoryId] = $this->prepareAttributes();
286
        }
287
    }
288
289
    /**
290
     * Resolve's the parent categories of the category with the passed ID and relate's
291
     * it with the product with the passed ID, if the category is top level OR has the
292
     * anchor flag set.
293
     *
294
     * @param integer $categoryId The ID of the category to resolve the parents
295
     * @param integer $entityId   The ID of the product entity to relate the category with
296
     * @param boolean $topLevel   TRUE if the passed category has top level, else FALSE
297
     *
298
     * @return void
299
     */
300
    protected function resolveCategoryIds($categoryId, $entityId, $topLevel = false)
301
    {
302
303
        // return immediately if this is the absolute root node
304
        if ((integer) $categoryId === 1) {
305
            return;
306
        }
307
308
        // load the data of the category with the passed ID
309
        $category = $this->getCategory($categoryId);
310
311
        // query whether or not the product has already been related
312
        if (!isset($this->productCategoryIds[$categoryId])) {
313
            if ($topLevel) {
314
                // relate it, if the category is top level
315
                $this->productCategoryIds[$categoryId] = $entityId;
316
            } elseif ((integer) $category[MemberNames::IS_ANCHOR] === 1) {
317
                // also relate it, if the category is not top level, but has the anchor flag set
318
                $this->productCategoryIds[$categoryId] = $entityId;
319
            } else {
320
                $this->getSubject()
321
                     ->getSystemLogger()
322
                     ->debug(sprintf('Don\'t create URL rewrite for category "%s" because of missing anchor flag', $category[MemberNames::PATH]));
323
            }
324
        }
325
326
        // load the root category
327
        $rootCategory = $this->getRootCategory();
328
329
        // try to resolve the parent category IDs
330
        if ($rootCategory[MemberNames::ENTITY_ID] !== ($parentId = $category[MemberNames::PARENT_ID])) {
331
            $this->resolveCategoryIds($parentId, $entityId, false);
332
        }
333
    }
334
335
    /**
336
     * Prepare the attributes of the entity that has to be persisted.
337
     *
338
     * @return array The prepared attributes
339
     */
340
    protected function prepareAttributes()
341
    {
342
343
        // load the store ID to use
344
        $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...
345
346
        // load the category to create the URL rewrite for
347
        $category = $this->getCategory($this->categoryId);
348
349
        // initialize the values
350
        $requestPath = $this->prepareRequestPath($category);
351
        $targetPath = $this->prepareTargetPath($category);
352
        $metadata = serialize($this->prepareMetadata($category));
353
354
        // return the prepared URL rewrite
355
        return $this->initializeEntity(
356
            array(
357
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
358
                MemberNames::ENTITY_ID        => $this->entityId,
359
                MemberNames::REQUEST_PATH     => $requestPath,
360
                MemberNames::TARGET_PATH      => $targetPath,
361
                MemberNames::REDIRECT_TYPE    => 0,
362
                MemberNames::STORE_ID         => $storeId,
363
                MemberNames::DESCRIPTION      => null,
364
                MemberNames::IS_AUTOGENERATED => 1,
365
                MemberNames::METADATA         => $metadata
366
            )
367
        );
368
    }
369
370
    /**
371
     * Prepare's the URL rewrite product => category relation attributes.
372
     *
373
     * @return array The prepared attributes
374
     */
375
    protected function prepareUrlRewriteProductCategoryAttributes()
376
    {
377
378
        // return the prepared product
379
        return $this->initializeEntity(
380
            array(
381
                MemberNames::PRODUCT_ID => $this->entityId,
382
                MemberNames::CATEGORY_ID => $this->categoryId,
383
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
384
            )
385
        );
386
    }
387
388
    /**
389
     * Prepare's the target path for a URL rewrite.
390
     *
391
     * @param array $category The categroy with the URL path
392
     *
393
     * @return string The target path
394
     */
395
    protected function prepareTargetPath(array $category)
396
    {
397
398
        // load the actual entity ID
399
        $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...
400
401
        // query whether or not, the category is the root category
402
        if ($this->isRootCategory($category)) {
403
            $targetPath = sprintf('catalog/product/view/id/%d', $lastEntityId);
404
        } else {
405
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $lastEntityId, $category[MemberNames::ENTITY_ID]);
406
        }
407
408
        // return the target path
409
        return $targetPath;
410
    }
411
412
    /**
413
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
414
     *
415
     * @param array $category The categroy with the URL path
416
     *
417
     * @return string The request path
418
     * @throws \RuntimeException Is thrown, if the passed category has no or an empty value for attribute "url_path"
419
     */
420
    protected function prepareRequestPath(array $category)
421
    {
422
423
        // load the product URL suffix to use
424
        $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...
425
426
        // query whether or not, the category is the root category
427
        if ($this->isRootCategory($category)) {
428
            return sprintf('%s%s', $this->urlKey, $urlSuffix);
429
        } else {
430
            // query whether or not the category's "url_path" attribute, necessary to create a valid "request_path", is available
431
            if (isset($category[MemberNames::URL_PATH]) && $category[MemberNames::URL_PATH]) {
432
                return sprintf('%s/%s%s', $category[MemberNames::URL_PATH], $this->urlKey, $urlSuffix);
433
            }
434
        }
435
436
        // throw an exception if the category's "url_path" attribute is NOT available
437
        throw new \RuntimeException(
438
            sprintf(
439
                'Can\'t find mandatory attribute "%s" for category ID "%d", necessary to build a valid "request_path"',
440
                MemberNames::URL_PATH,
441
                $category[MemberNames::ENTITY_ID]
442
            )
443
        );
444
    }
445
446
    /**
447
     * Prepare's the URL rewrite's metadata with the passed category values.
448
     *
449
     * @param array $category The category used for preparation
450
     *
451
     * @return array The metadata
452
     */
453
    protected function prepareMetadata(array $category)
454
    {
455
456
        // initialize the metadata
457
        $metadata = array();
458
459
        // query whether or not, the passed category IS the root category
460
        if ($this->isRootCategory($category)) {
461
            return $metadata;
462
        }
463
464
        // if not, set the category ID in the metadata
465
        $metadata[UrlRewriteObserver::CATEGORY_ID] = $category[MemberNames::ENTITY_ID];
466
467
        // return the metadata
468
        return $metadata;
469
    }
470
471
    /**
472
     * Query whether or not the actual entity is visible or not.
473
     *
474
     * @return boolean TRUE if the entity is NOT visible, else FALSE
475
     */
476
    protected function isNotVisible()
477
    {
478
        return $this->getEntityIdVisibilityIdMapping() === VisibilityKeys::VISIBILITY_NOT_VISIBLE;
479
    }
480
481
    /**
482
     * Return's the visibility for the passed entity ID, if it already has been mapped. The mapping will be created
483
     * by calling <code>\TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue</code> which will
484
     * be done by the <code>\TechDivision\Import\Product\Callbacks\VisibilityCallback</code>.
485
     *
486
     * @return integer The visibility ID
487
     * @throws \Exception Is thrown, if the entity ID has not been mapped
488
     * @see \TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue()
489
     */
490
    protected function getEntityIdVisibilityIdMapping()
491
    {
492
        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, TechDivision\Import\Prod...jects\UrlRewriteSubject.

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...
493
    }
494
495
    /**
496
     * Return's the root category for the actual view store.
497
     *
498
     * @return array The store's root category
499
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
500
     */
501
    protected function getRootCategory()
502
    {
503
        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\Prod...jects\UrlRewriteSubject, 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...
504
    }
505
506
    /**
507
     * Return's TRUE if the passed category IS the root category, else FALSE.
508
     *
509
     * @param array $category The category to query
510
     *
511
     * @return boolean TRUE if the passed category IS the root category
512
     */
513
    protected function isRootCategory(array $category)
514
    {
515
516
        // load the root category
517
        $rootCategory = $this->getRootCategory();
518
519
        // compare the entity IDs and return the result
520
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
521
    }
522
523
    /**
524
     * Return's the list with category IDs the product is related with.
525
     *
526
     * @return array The product's category IDs
527
     */
528
    protected function getProductCategoryIds()
529
    {
530
        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...
531
    }
532
533
    /**
534
     * Return's the category with the passed ID.
535
     *
536
     * @param integer $categoryId The ID of the category to return
537
     *
538
     * @return array The category data
539
     */
540
    protected function getCategory($categoryId)
541
    {
542
        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, TechDivision\Import\Prod...jects\UrlRewriteSubject.

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
     * Persist's the URL rewrite with the passed data.
547
     *
548
     * @param array $row The URL rewrite to persist
549
     *
550
     * @return string The ID of the persisted entity
551
     */
552
    protected function persistUrlRewrite($row)
553
    {
554
        return $this->getProductUrlRewriteProcessor()->persistUrlRewrite($row);
555
    }
556
557
    /**
558
     * Persist's the URL rewrite product => category relation with the passed data.
559
     *
560
     * @param array $row The URL rewrite product => category relation to persist
561
     *
562
     * @return void
563
     */
564
    protected function persistUrlRewriteProductCategory($row)
565
    {
566
        return $this->getProductUrlRewriteProcessor()->persistUrlRewriteProductCategory($row);
567
    }
568
569
    /**
570
     * Return's the column name that contains the primary key.
571
     *
572
     * @return string the column name that contains the primary key
573
     */
574
    protected function getPrimaryKeyColumnName()
575
    {
576
        return ColumnKeys::SKU;
577
    }
578
579
    /**
580
     * Queries whether or not the passed SKU and store view code has already been processed.
581
     *
582
     * @param string $sku           The SKU to check been processed
583
     * @param string $storeViewCode The store view code to check been processed
584
     *
585
     * @return boolean TRUE if the SKU and store view code has been processed, else FALSE
586
     */
587
    protected function storeViewHasBeenProcessed($sku, $storeViewCode)
588
    {
589
        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, TechDivision\Import\Prod...jects\UrlRewriteSubject.

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...
590
    }
591
592
    /**
593
     * Add the entity ID => visibility mapping for the actual entity ID.
594
     *
595
     * @param string $visibility The visibility of the actual entity to map
596
     *
597
     * @return void
598
     */
599
    protected function addEntityIdVisibilityIdMapping($visibility)
600
    {
601
        $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, TechDivision\Import\Prod...jects\UrlRewriteSubject.

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...
602
    }
603
}
604