Completed
Push — 24.x ( 29905b...c80b29 )
by Tim
01:33
created

UrlRewriteObserver::prepareUrlRewrites()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 65

Duplication

Lines 7
Ratio 10.77 %

Code Coverage

Tests 16
CRAP Score 8.8343

Importance

Changes 0
Metric Value
dl 7
loc 65
ccs 16
cts 28
cp 0.5714
rs 8.1414
c 0
b 0
f 0
cc 6
nc 14
nop 0
crap 8.8343

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Product\UrlRewrite\Utils\CoreConfigDataKeys;
24
use TechDivision\Import\Utils\StoreViewCodes;
25
use TechDivision\Import\Product\Utils\VisibilityKeys;
26
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
27
use TechDivision\Import\Product\UrlRewrite\Utils\ColumnKeys;
28
use TechDivision\Import\Product\UrlRewrite\Utils\MemberNames;
29
use TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface;
30
use TechDivision\Import\Subjects\SubjectInterface;
31
32
/**
33
 * Observer that creates/updates the product's URL rewrites.
34
 *
35
 * @author    Tim Wagner <[email protected]>
36
 * @copyright 2016 TechDivision GmbH <[email protected]>
37
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
38
 * @link      https://github.com/techdivision/import-product-url-rewrite
39
 * @link      http://www.techdivision.com
40
 */
41
class UrlRewriteObserver extends AbstractProductImportObserver
42
{
43
44
    /**
45
     * The entity type to load the URL rewrites for.
46
     *
47
     * @var string
48
     */
49
    const ENTITY_TYPE = 'product';
50
51
    /**
52
     * The key for the category in the metadata.
53
     *
54
     * @var string
55
     */
56
    const CATEGORY_ID = 'category_id';
57
58
    /**
59
     * The URL key from the CSV file column that has to be processed by the observer.
60
     *
61
     * @var string
62
     */
63
    protected $urlKey;
64
65
    /**
66
     * The actual category ID to process.
67
     *
68
     * @var integer
69
     */
70
    protected $categoryId;
71
72
    /**
73
     * The actual entity ID to process.
74
     *
75
     * @var integer
76
     */
77
    protected $entityId;
78
79
    /**
80
     * The ID of the recently created URL rewrite.
81
     *
82
     * @var integer
83
     */
84
    protected $urlRewriteId;
85
86
    /**
87
     * The array with the URL rewrites that has to be created.
88
     *
89
     * @var array
90
     */
91
    protected $urlRewrites = array();
92
93
    /**
94
     * The array with the category IDs related with the product.
95
     *
96
     * @var array
97
     */
98
    protected $productCategoryIds = array();
99
100
    /**
101
     * The product bunch processor instance.
102
     *
103
     * @var \TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface
104
     */
105
    protected $productUrlRewriteProcessor;
106
107
    /**
108
     * Initialize the observer with the passed product URL rewrite processor instance.
109
     *
110
     * @param \TechDivision\Import\Product\UrlRewrite\Services\ProductUrlRewriteProcessorInterface $productUrlRewriteProcessor The product URL rewrite processor instance
111
     */
112 5
    public function __construct(ProductUrlRewriteProcessorInterface $productUrlRewriteProcessor)
113
    {
114 5
        $this->productUrlRewriteProcessor = $productUrlRewriteProcessor;
115 5
    }
116
117
    /**
118
     * Return's the product bunch processor instance.
119
     *
120
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
121
     */
122 3
    protected function getProductUrlRewriteProcessor()
123
    {
124 3
        return $this->productUrlRewriteProcessor;
125
    }
126
127
    /**
128
     * Will be invoked by the action on the events the listener has been registered for.
129
     *
130
     * @param \TechDivision\Import\Subjects\SubjectInterface $subject The subject instance
131
     *
132
     * @return array The modified row
133
     * @throws \Exception Is thrown, if the product is not available or no URL key has been specified
134
     * @see \TechDivision\Import\Observers\ObserverInterface::handle()
135
     */
136 3
    public function handle(SubjectInterface $subject)
137
    {
138
139
        // initialize the row
140 3
        $this->setSubject($subject);
141 3
        $this->setRow($subject->getRow());
142
143
        // try to load the entity ID for the product with the passed SKU
144 3
        if ($product = $this->loadProduct($sku = $this->getValue(ColumnKeys::SKU))) {
145 3
            $this->setLastEntityId($this->entityId = $product[MemberNames::ENTITY_ID]);
146 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147
            // prepare a log message
148
            $message = sprintf('Product with SKU "%s" can\'t be loaded to create URL rewrites', $sku);
149
            // query whether or not we're in debug mode
150
            if ($this->getSubject()->isDebugMode()) {
151
                $this->getSubject()->getSystemLogger()->warning($message);
152
                return $this->getRow();
153
            } else {
154
                throw new \Exception($this->appendExceptionSuffix($message));
155
            }
156
        }
157
158
        // try to load the URL key
159 3
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
160 3
            $this->urlKey = $this->getValue(ColumnKeys::URL_KEY);
161 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
            // prepare a log message
163
            $message = sprintf('Can\'t find a value in column "url_key" for product with SKU "%s"', $sku);
164
            // query whether or not we're in debug mode
165
            if ($this->getSubject()->isDebugMode()) {
166
                $this->getSubject()->getSystemLogger()->warning($message);
167
                return $this->getRow();
168
            } else {
169
                throw new \Exception($this->appendExceptionSuffix($message));
170
            }
171
        }
172
173
        // initialize the store view code
174 3
        $this->getSubject()->prepareStoreViewCode();
175
176
        // load the store view - if no store view has been set we use the admin store view as default
177 3
        $storeViewCode = $this->getSubject()->getStoreViewCode(StoreViewCodes::ADMIN);
178
179
        // query whether or not the row has already been processed
180 3 View Code Duplication
        if ($this->storeViewHasBeenProcessed($sku, $storeViewCode)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
            // log a message
182
            $this->getSubject()
183
                ->getSystemLogger()
184
                ->debug(
185
                    sprintf(
186
                        'URL rewrites for SKU "%s" + store view code "%s" has already been processed',
187
                        $sku,
188
                        $storeViewCode
189
                    )
190
                );
191
192
            // return without creating any rewrites
193
            return $this->getRow();
194
        };
195
196
        // stop processing as we don't want to create URL rewrites for the admin store view
197 3 View Code Duplication
        if ($storeViewCode === StoreViewCodes::ADMIN) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
198
            // log a message and return
199
            $this->getSubject()
200
                 ->getSystemLogger()
201
                 ->debug(
202
                     sprintf(
203
                         'Store with code "%s" is not active, no URL rewrites will be created for product with SKU "%s"',
204
                         $storeViewCode,
205
                         $sku
206
                     )
207
                 );
208
209
            // return without creating any rewrites
210
            return $this->getRow();
211
        }
212
213
        // stop processing if the store is NOT active
214 3 View Code Duplication
        if (!$this->getSubject()->storeIsActive($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 storeIsActive() does only exist in the following implementations of said interface: 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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
215
            // log a message and return
216
            $this->getSubject()
217
                 ->getSystemLogger()
218
                 ->debug(
219
                     sprintf(
220
                         'Store with code "%s" is not active, no URL rewrites will be created for product with SKU "%s"',
221
                         $storeViewCode,
222
                         $sku
223
                     )
224
                 );
225
226
            // return without creating any rewrites
227
            return $this->getRow();
228
        }
229
230
        // only map the visibility for the product row related to the default store view
231 3
        if (!$this->hasBeenProcessed($sku)) {
232 3
            $this->addEntityIdVisibilityIdMapping($this->getValue(ColumnKeys::VISIBILITY));
233
        }
234
235
        // process the functionality and return the row
236 3
        $this->process();
237
238
        // return the processed row
239 3
        return $this->getRow();
240
    }
241
242
    /**
243
     * Process the observer's business logic.
244
     *
245
     * @return void
246
     */
247 3
    protected function process()
248
    {
249
250
        // prepare the URL rewrites
251 3
        $this->prepareUrlRewrites();
252
253
        // iterate over the categories and create the URL rewrites
254 3
        foreach ($this->urlRewrites as $categoryId => $urlRewrite) {
255
            // initialize and persist the URL rewrite
256 3
            if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) {
257
                // initialize URL rewrite and catagory ID
258 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...
259
260
                try {
261
                    // persist the URL rewrite
262 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...
263
264
                    /*
265
                     * Attention! Stop processing, if this is a root category, because Magento needs explicitly
266
                     * NO URL rewrite product category relation to render canonical and meta og:url tag!
267
                     */
268 3
                    if ($this->isRootCategory($this->getCategory($categoryId))) {
269 3
                        continue;
270
                    }
271
272
                    // initialize and persist the URL rewrite product => category relation
273 2
                    $urlRewriteProductCategory = $this->initializeUrlRewriteProductCategory(
274 2
                        $this->prepareUrlRewriteProductCategoryAttributes()
275
                    );
276
277
                    // persist the URL rewrite product category relation
278 2
                    $this->persistUrlRewriteProductCategory($urlRewriteProductCategory);
279
                } catch (\Exception $e) {
280
                    // query whether or not debug mode has been enabled
281 View Code Duplication
                    if ($this->getSubject()->isDebugMode()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
                        $this->getSubject()
283
                             ->getSystemLogger()
284
                             ->warning($this->getSubject()->appendExceptionSuffix($e->getMessage()));
285
                    } else {
286 2
                        throw $e;
287
                    }
288
                }
289
            }
290
        }
291 3
    }
292
293
    /**
294
     * Initialize the category product with the passed attributes and returns an instance.
295
     *
296
     * @param array $attr The category product attributes
297
     *
298
     * @return array The initialized category product
299
     */
300 2
    protected function initializeUrlRewrite(array $attr)
301
    {
302 2
        return $attr;
303
    }
304
305
    /**
306
     * Initialize the URL rewrite product => category relation with the passed attributes
307
     * and returns an instance.
308
     *
309
     * @param array $attr The URL rewrite product => category relation attributes
310
     *
311
     * @return array The initialized URL rewrite product => category relation
312
     */
313 1
    protected function initializeUrlRewriteProductCategory($attr)
314
    {
315 1
        return $attr;
316
    }
317
318
    /**
319
     * Prepare's the URL rewrites that has to be created/updated.
320
     *
321
     * @return void
322
     */
323 3
    protected function prepareUrlRewrites()
324
    {
325
326
        // (re-)initialize the array for the URL rewrites and the product category IDs
327 3
        $this->urlRewrites = array();
328 3
        $this->productCategoryIds = array();
329
330
        // do NOT create new URL rewrites, if the product is NOT visible (any more), BUT
331
        // handle existing URL rewrites, e. g. to remove and clean up the URL rewrites
332 3
        if (!$this->isVisible()) {
333
             // log a message
334
             $this->getSubject()
335
                 ->getSystemLogger()
336
                 ->debug(
337
                     sprintf(
338
                         'Product with SKU "%s" is not visible, so no URL rewrites will be created',
339
                         $this->getValue(ColumnKeys::SKU)
340
                     )
341
                 );
342
343
             // return without creating any rewrites
344
             return;
345
        }
346
347
        // load the root category of the default store view (as we're in the
348
        // default row and does not have a store view code), because we need
349
        // that to create the default product URL rewrite
350 3
        $rootCategory = $this->getRootCategory();
351
352
        // at least, add the root category ID to the category => product relations
353 3
        $this->productCategoryIds[] = $rootCategory[MemberNames::ENTITY_ID];
354
355
        // load the store view code from the appropriate column
356 3
        $storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE);
357
358
        // load the category paths from the import file
359 3
        $paths = $this->getValue(ColumnKeys::CATEGORIES, array(), array($this, 'explode'));
360
361
        // append the category => product relations found
362 3
        foreach ($paths as $path) {
363
            try {
364
                // downgrade the path
365 2
                $path = implode('/', $this->explode($path, '/'));
366
                // try to load the category for the given path
367 2
                $category = $this->getCategoryByPath(trim($path), $storeViewCode);
368
                // resolve the product's categories recursively
369 2
                $this->resolveCategoryIds($category[MemberNames::ENTITY_ID], true, $storeViewCode);
370
            } catch (\Exception $e) {
371
                // query whether or not debug mode has been enabled
372 View Code Duplication
                if ($this->getSubject()->isDebugMode()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
373
                    $this->getSubject()
374
                         ->getSystemLogger()
375
                         ->warning($this->getSubject()->appendExceptionSuffix($e->getMessage()));
376
                } else {
377 2
                    throw $e;
378
                }
379
            }
380
        }
381
382
        // initialize the member varialbe with the category ID
383
        // and prepare the attributes for each URL rewrite
384 3
        foreach ($this->productCategoryIds as $this->categoryId) {
385 3
            $this->urlRewrites[$this->categoryId] = $this->prepareAttributes($storeViewCode);
386
        }
387 3
    }
388
389
    /**
390
     * Resolve's the parent categories of the category with the passed ID and relate's
391
     * it with the product with the passed ID, if the category is top level OR has the
392
     * anchor flag set.
393
     *
394
     * @param integer $categoryId    The ID of the category to resolve the parents
395
     * @param boolean $topLevel      TRUE if the passed category has top level, else FALSE
396
     * @param string  $storeViewCode The store view code to resolve the category IDs for
397
     *
398
     * @return void
399
     */
400 2
    protected function resolveCategoryIds($categoryId, $topLevel = false, $storeViewCode = StoreViewCodes::ADMIN)
401
    {
402
403
        // load the data of the category with the passed ID
404 2
        $category = $this->getCategory($categoryId, $storeViewCode);
405
406
        // return immediately if this is a root category
407 2
        if ($this->isRootCategory($category)) {
408 2
            return;
409
        }
410
411
        // create the product category relation for the current category
412 2
        $this->createProductCategoryRelation($category, $topLevel);
413
414
        // try to resolve the parent category IDs
415 2
        $this->resolveCategoryIds($category[MemberNames::PARENT_ID], false, $storeViewCode);
416 2
    }
417
418
    /**
419
     * Adds the entity product relation if necessary.
420
     *
421
     * @param array   $category The category to create the relation for
422
     * @param boolean $topLevel Whether or not the category has top level
423
     *
424
     * @return void
425
     */
426 4
    protected function createProductCategoryRelation($category, $topLevel)
427
    {
428
429
        // query whether or not the product has already been related
430 4
        if (in_array($category[MemberNames::ENTITY_ID], $this->productCategoryIds)) {
431
            return;
432
        }
433
434
        // load the backend configuration value for whether or not the catalog product rewrites should be generated
435 4
        $generateCategoryRewrites = $this->getGenerateCategoryProductRewritesOptionValue();
436
437
        // abort if generating product categories is disabled and category is not root
438 4
        if ($generateCategoryRewrites === false && $this->isRootCategory($category) === false) {
439 1
            return;
440
        }
441
442
        // create relation if the category is top level or has the anchor flag set
443 3
        if ($topLevel || (integer) $category[MemberNames::IS_ANCHOR] === 1) {
444 3
            $this->productCategoryIds[] = $category[MemberNames::ENTITY_ID];
445 3
            return;
446
        }
447
448
        // log a debug messsage that the URL rewrite has not
449
        // been created because of the missing anchor flag
450 2
        $this->getSubject()
451 2
            ->getSystemLogger()
452 2
            ->debug(
453 2
                sprintf(
454 2
                    'Don\'t create URL rewrite for category "%s" because of missing anchor flag',
455 2
                    $category[MemberNames::PATH]
456
                )
457
            );
458 2
    }
459
460
    /**
461
     * Returns the option value for whether or not to generate product catalog rewrites as well.
462
     *
463
     * @return bool
464
     */
465 4
    protected function getGenerateCategoryProductRewritesOptionValue()
466
    {
467 4
        return (bool) $this->getSubject()->getCoreConfigData(
468 4
            CoreConfigDataKeys::CATALOG_SEO_GENERATE_CATEGORY_PRODUCT_REWRITES,
469 4
            true
470
        );
471
    }
472
473
    /**
474
     * Prepare the attributes of the entity that has to be persisted.
475
     *
476
     * @param string $storeViewCode The store view code to prepare the attributes for
477
     *
478
     * @return array The prepared attributes
479
     */
480 3
    protected function prepareAttributes($storeViewCode)
481
    {
482
483
        // load the store ID to use
484 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...
485
486
        // load the category to create the URL rewrite for
487 3
        $category = $this->getCategory($this->categoryId, $storeViewCode);
488
489
        // initialize the values
490 3
        $metadata = $this->prepareMetadata($category);
491 3
        $targetPath = $this->prepareTargetPath($category);
492 3
        $requestPath = $this->prepareRequestPath($category);
493
494
        // return the prepared URL rewrite
495 3
        return $this->initializeEntity(
496
            array(
497 3
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
498 3
                MemberNames::ENTITY_ID        => $this->entityId,
499 3
                MemberNames::REQUEST_PATH     => $requestPath,
500 3
                MemberNames::TARGET_PATH      => $targetPath,
501 3
                MemberNames::REDIRECT_TYPE    => 0,
502 3
                MemberNames::STORE_ID         => $storeId,
503 3
                MemberNames::DESCRIPTION      => null,
504 3
                MemberNames::IS_AUTOGENERATED => 1,
505 3
                MemberNames::METADATA         => $metadata ? json_encode($metadata) : null
506
            )
507
        );
508
    }
509
510
    /**
511
     * Prepare's the URL rewrite product => category relation attributes.
512
     *
513
     * @return array The prepared attributes
514
     */
515 2
    protected function prepareUrlRewriteProductCategoryAttributes()
516
    {
517
518
        // return the prepared product
519 2
        return $this->initializeEntity(
520
            array(
521 2
                MemberNames::PRODUCT_ID     => $this->entityId,
522 2
                MemberNames::CATEGORY_ID    => $this->categoryId,
523 2
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
524
            )
525
        );
526
    }
527
528
    /**
529
     * Prepare's the target path for a URL rewrite.
530
     *
531
     * @param array $category The categroy with the URL path
532
     *
533
     * @return string The target path
534
     */
535 3
    protected function prepareTargetPath(array $category)
536
    {
537
538
        // query whether or not, the category is the root category
539 3
        if ($this->isRootCategory($category)) {
540 3
            $targetPath = sprintf('catalog/product/view/id/%d', $this->entityId);
541
        } else {
542 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $this->entityId, $category[MemberNames::ENTITY_ID]);
543
        }
544
545
        // return the target path
546 3
        return $targetPath;
547
    }
548
549
    /**
550
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
551
     *
552
     * @param array $category The categroy with the URL path
553
     *
554
     * @return string The request path
555
     * @throws \RuntimeException Is thrown, if the passed category has no or an empty value for attribute "url_path"
556
     */
557 3
    protected function prepareRequestPath(array $category)
558
    {
559
560
        // load the product URL suffix to use
561 3
        $urlSuffix = $this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_URL_SUFFIX, '.html');
562
563
        // query whether or not, the category is the root category
564 3
        if ($this->isRootCategory($category)) {
565 3
            return sprintf('%s%s', $this->urlKey, $urlSuffix);
566
        } else {
567
            // query whether or not the category's "url_path" attribute, necessary to create a valid "request_path", is available
568 2
            if (isset($category[MemberNames::URL_PATH]) && $category[MemberNames::URL_PATH]) {
569 2
                return sprintf('%s/%s%s', $category[MemberNames::URL_PATH], $this->urlKey, $urlSuffix);
570
            }
571
        }
572
573
        // throw an exception if the category's "url_path" attribute is NOT available
574
        throw new \RuntimeException(
575
            $this->appendExceptionSuffix(
576
                sprintf(
577
                    'Can\'t find mandatory attribute "%s" for category ID "%d", necessary to build a valid "request_path"',
578
                    MemberNames::URL_PATH,
579
                    $category[MemberNames::ENTITY_ID]
580
                )
581
            )
582
        );
583
    }
584
585
    /**
586
     * Prepare's the URL rewrite's metadata with the passed category values.
587
     *
588
     * @param array $category The category used for preparation
589
     *
590
     * @return array|null The metadata
591
     */
592 3
    protected function prepareMetadata(array $category)
593
    {
594
595
        // initialize the metadata
596 3
        $metadata = array();
597
598
        // query whether or not, the passed category IS the root category
599 3
        if ($this->isRootCategory($category)) {
600 3
            return;
601
        }
602
603
        // if not, set the category ID in the metadata
604 2
        $metadata[UrlRewriteObserver::CATEGORY_ID] = (string) $category[MemberNames::ENTITY_ID];
605
606
        // return the metadata
607 2
        return $metadata;
608
    }
609
610
    /**
611
     * Query whether or not the actual entity is visible.
612
     *
613
     * @return boolean TRUE if the entity is visible, else FALSE
614
     */
615 3
    protected function isVisible()
616
    {
617 3
        return $this->getEntityIdVisibilityIdMapping() !== VisibilityKeys::VISIBILITY_NOT_VISIBLE;
618
    }
619
620
    /**
621
     * Return's the visibility for the passed entity ID, if it already has been mapped. The mapping will be created
622
     * by calling <code>\TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue</code> which will
623
     * be done by the <code>\TechDivision\Import\Product\Callbacks\VisibilityCallback</code>.
624
     *
625
     * @return integer The visibility ID
626
     * @throws \Exception Is thrown, if the entity ID has not been mapped
627
     * @see \TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue()
628
     */
629 3
    protected function getEntityIdVisibilityIdMapping()
630
    {
631 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\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...
632
    }
633
634
    /**
635
     * Return's the root category for the actual view store.
636
     *
637
     * @return array The store's root category
638
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
639
     */
640 3
    protected function getRootCategory()
641
    {
642 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\Prod...jects\UrlRewriteSubject, TechDivision\Import\Subjects\AbstractEavSubject, TechDivision\Import\Subjects\AbstractSubject, TechDivision\Import\Subjects\MoveFilesSubject, TechDivision\Import\Subjects\ValidatorSubject.

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...
643
    }
644
645
    /**
646
     * Return's TRUE if the passed category IS the root category, else FALSE.
647
     *
648
     * @param array $category The category to query
649
     *
650
     * @return boolean TRUE if the passed category IS the root category
651
     */
652 3
    protected function isRootCategory(array $category)
653
    {
654 3
        return (int) $category[MemberNames::LEVEL] < 2;
655
    }
656
657
    /**
658
     * Return's the category with the passed path.
659
     *
660
     * @param string $path          The path of the category to return
661
     * @param string $storeViewCode The store view code of the category to return
662
     *
663
     * @return array The category
664
     */
665 2
    protected function getCategoryByPath($path, $storeViewCode = StoreViewCodes::ADMIN)
666
    {
667 2
        return $this->getSubject()->getCategoryByPath($path, $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 getCategoryByPath() 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...
668
    }
669
670
    /**
671
     * Return's the category with the passed ID.
672
     *
673
     * @param integer $categoryId    The ID of the category to return
674
     * @param string  $storeViewCode The store view code of category to return
675
     *
676
     * @return array The category data
677
     */
678 2
    protected function getCategory($categoryId, $storeViewCode = StoreViewCodes::ADMIN)
679
    {
680 2
        return $this->getSubject()->getCategory($categoryId, $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 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...
681
    }
682
683
    /**
684
     * Persist's the URL rewrite with the passed data.
685
     *
686
     * @param array $row The URL rewrite to persist
687
     *
688
     * @return string The ID of the persisted entity
689
     */
690 3
    protected function persistUrlRewrite($row)
691
    {
692 3
        return $this->getProductUrlRewriteProcessor()->persistUrlRewrite($row);
693
    }
694
695
    /**
696
     * Persist's the URL rewrite product => category relation with the passed data.
697
     *
698
     * @param array $row The URL rewrite product => category relation to persist
699
     *
700
     * @return void
701
     */
702 2
    protected function persistUrlRewriteProductCategory($row)
703
    {
704 2
        return $this->getProductUrlRewriteProcessor()->persistUrlRewriteProductCategory($row);
705
    }
706
707
    /**
708
     * Queries whether or not the passed SKU and store view code has already been processed.
709
     *
710
     * @param string $sku           The SKU to check been processed
711
     * @param string $storeViewCode The store view code to check been processed
712
     *
713
     * @return boolean TRUE if the SKU and store view code has been processed, else FALSE
714
     */
715 3
    protected function storeViewHasBeenProcessed($sku, $storeViewCode)
716
    {
717 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, 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...
718
    }
719
720
    /**
721
     * Add the entity ID => visibility mapping for the actual entity ID.
722
     *
723
     * @param string $visibility The visibility of the actual entity to map
724
     *
725
     * @return void
726
     */
727 3
    protected function addEntityIdVisibilityIdMapping($visibility)
728
    {
729 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\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...
730 3
    }
731
732
    /**
733
     * Set's the ID of the product that has been created recently.
734
     *
735
     * @param string $lastEntityId The entity ID
736
     *
737
     * @return void
738
     */
739 3
    protected function setLastEntityId($lastEntityId)
740
    {
741 3
        $this->getSubject()->setLastEntityId($lastEntityId);
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 setLastEntityId() does only exist in the following implementations of said interface: 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...
742 3
    }
743
744
    /**
745
     * Load's and return's the product with the passed SKU.
746
     *
747
     * @param string $sku The SKU of the product to load
748
     *
749
     * @return array The product
750
     */
751 3
    protected function loadProduct($sku)
752
    {
753 3
        return $this->getProductUrlRewriteProcessor()->loadProduct($sku);
754
    }
755
}
756