Completed
Push — master ( 5bcafe...c5dea9 )
by Tim
13s
created

UrlRewriteObserver::getPrimaryKeyColumnName()   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 0
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\Utils\VisibilityKeys;
25
use TechDivision\Import\Product\Utils\CoreConfigDataKeys;
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
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 3
    public function __construct(ProductUrlRewriteProcessorInterface $productUrlRewriteProcessor)
112
    {
113 3
        $this->productUrlRewriteProcessor = $productUrlRewriteProcessor;
114 3
    }
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 3
    protected function getProductUrlRewriteProcessor()
122
    {
123 3
        return $this->productUrlRewriteProcessor;
124
    }
125
126
    /**
127
     * Process the observer's business logic.
128
     *
129
     * @return void
130
     * @throws \Exception Is thrown, if the product is not available or no URL key has been specified
131
     */
132 3
    protected function process()
133
    {
134
135
        // try to load the entity ID for the product with the passed SKU
136 3
        if ($product = $this->loadProduct($sku = $this->getValue(ColumnKeys::SKU))) {
137 3
            $this->setLastEntityId($this->entityId = $product[MemberNames::ENTITY_ID]);
138 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...
139
            // prepare a log message
140
            $message = sprintf('Product with SKU "%s" can\'t be loaded to create URL rewrites', $sku);
141
            // query whether or not we're in debug mode
142
            if ($this->getSubject()->isDebugMode()) {
143
                $this->getSubject()->getSystemLogger()->warning($message);
144
                return;
145
            } else {
146
                throw new \Exception($message);
147
            }
148
        }
149
150
        // try to load the URL key
151 3
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
152 3
            $this->urlKey = $this->getValue(ColumnKeys::URL_KEY);
153 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...
154
            // prepare a log message
155
            $message = sprintf('Can\'t find a value in column "url_key" for product with SKU "%s"', $sku);
156
            // query whether or not we're in debug mode
157
            if ($this->getSubject()->isDebugMode()) {
158
                $this->getSubject()->getSystemLogger()->warning($message);
159
            } else {
160
                return;
161
                throw new \Exception($message);
0 ignored issues
show
Unused Code introduced by
throw new \Exception($message); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
162
            }
163
        }
164
165
        // initialize the store view code
166 3
        $this->getSubject()->prepareStoreViewCode();
167
168
        // load the store view - if no store view has been set we use the admin store view as default
169 3
        $storeViewCode = $this->getSubject()->getStoreViewCode(StoreViewCodes::ADMIN);
170
171
        // query whether or not the row has already been processed
172 3
        if ($this->storeViewHasBeenProcessed($sku, $storeViewCode)) {
173
            // log a message
174
            $this->getSubject()
175
                 ->getSystemLogger()
176
                 ->debug(
177
                     sprintf(
178
                         'URL rewrites for SKU "%s" + store view code "%s" has already been processed',
179
                         $sku,
180
                         $storeViewCode
181
                     )
182
                 );
183
184
            // return without creating any rewrites
185
            return;
186
        };
187
188
        // stop processing as we don't want to create URL rewrites for the admin store view
189 3
        if ($storeViewCode === StoreViewCodes::ADMIN) {
190
            // log a message and return
191
            $this->getSubject()
192
                 ->getSystemLogger()
193
                 ->debug(
194
                     sprintf(
195
                         'Store with code "%s" is not active, no URL rewrites will be created for product with SKU "%s"',
196
                         $storeViewCode,
197
                         $sku
198
                     )
199
                 );
200
201
            // return without creating any rewrites
202
            return;
203
        }
204
205
        // stop processing if the store is NOT active
206 3
        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...
207
            // log a message and return
208
            $this->getSubject()
209
                 ->getSystemLogger()
210
                 ->debug(
211
                     sprintf(
212
                         'Store with code "%s" is not active, no URL rewrites will be created for product with SKU "%s"',
213
                         $storeViewCode,
214
                         $sku
215
                     )
216
                 );
217
218
            // return without creating any rewrites
219
            return;
220
        }
221
222
        // only map the visibility for the product row related to the default store view
223 3
        if (!$this->hasBeenProcessed($sku)) {
224 3
            $this->addEntityIdVisibilityIdMapping($this->getValue(ColumnKeys::VISIBILITY));
225
        }
226
227
        // do NOT create new URL rewrites, if the product is NOT visible (any more), BUT
228
        // handle existing URL rewrites, e. g. to remove and clean up the URL rewrites
229 3
        if (!$this->isVisible()) {
230
            // log a message
231
            $this->getSubject()
232
                 ->getSystemLogger()
233
                 ->debug(
234
                     sprintf(
235
                         'Product with SKU "%s" is not visible, so no URL rewrites will be created',
236
                         $sku
237
                     )
238
                 );
239
240
            // return without creating any rewrites
241
            return;
242
        }
243
244
        // prepare the URL rewrites
245 3
        $this->prepareUrlRewrites();
246
247
        // iterate over the categories and create the URL rewrites
248 3
        foreach ($this->urlRewrites as $categoryId => $urlRewrite) {
249
            // initialize and persist the URL rewrite
250 3
            if ($urlRewrite = $this->initializeUrlRewrite($urlRewrite)) {
251
                // initialize URL rewrite and catagory ID
252 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...
253
254
                try {
255
                    // persist the URL rewrite
256 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...
257
258
                    // initialize and persist the URL rewrite product => category relation
259 3
                    $urlRewriteProductCategory = $this->initializeUrlRewriteProductCategory(
260 3
                        $this->prepareUrlRewriteProductCategoryAttributes()
261
                    );
262
263
                    // persist the URL rewrite product category relation
264 3
                    $this->persistUrlRewriteProductCategory($urlRewriteProductCategory);
265
266
                } catch (\Exception $e) {
267
                    // query whether or not debug mode has been enabled
268
                    if ($this->getSubject()->isDebugMode()) {
269
                        $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...
270
                    } else {
271 3
                        throw $e;
272
                    }
273
                }
274
            }
275
        }
276 3
    }
277
278
    /**
279
     * Initialize the category product with the passed attributes and returns an instance.
280
     *
281
     * @param array $attr The category product attributes
282
     *
283
     * @return array The initialized category product
284
     */
285 2
    protected function initializeUrlRewrite(array $attr)
286
    {
287 2
        return $attr;
288
    }
289
290
    /**
291
     * Initialize the URL rewrite product => category relation with the passed attributes
292
     * and returns an instance.
293
     *
294
     * @param array $attr The URL rewrite product => category relation attributes
295
     *
296
     * @return array The initialized URL rewrite product => category relation
297
     */
298 2
    protected function initializeUrlRewriteProductCategory($attr)
299
    {
300 2
        return $attr;
301
    }
302
303
    /**
304
     * Prepare's the URL rewrites that has to be created/updated.
305
     *
306
     * @return void
307
     */
308 3
    protected function prepareUrlRewrites()
309
    {
310
311
        // (re-)initialize the array for the URL rewrites and the product category IDs
312 3
        $this->urlRewrites = array();
313 3
        $this->productCategoryIds = array();
314
315
        // load the root category, because we need that to create the default product URL rewrite
316 3
        $rootCategory = $this->getRootCategory();
317
318
        // at least, add the root category ID to the category => product relations
319 3
        $this->productCategoryIds[] = $rootCategory[MemberNames::ENTITY_ID];
320
321
        // query whether or not categories has to be used as product URL suffix
322 3
        if ($this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_USE_CATEGORIES, false)) {
323
            // append the category => product relations found
324 3
            foreach ($this->getValue(ColumnKeys::CATEGORIES, array(), array($this, 'explode')) as $path) {
325
                // load the category for the found path
326 2
                $category = $this->getCategoryByPath(trim($path));
327
                // resolve the product's categories recursively
328 2
                $this->resolveCategoryIds($category[MemberNames::ENTITY_ID], true);
329
            }
330
        }
331
332
        // prepare the URL rewrites
333 3
        foreach ($this->productCategoryIds as $categoryId) {
334
            // set the category ID
335 3
            $this->categoryId = $categoryId;
336
337
            // prepare the attributes for each URL rewrite
338 3
            $this->urlRewrites[$categoryId] = $this->prepareAttributes();
339
        }
340 3
    }
341
342
    /**
343
     * Resolve's the parent categories of the category with the passed ID and relate's
344
     * it with the product with the passed ID, if the category is top level OR has the
345
     * anchor flag set.
346
     *
347
     * @param integer $categoryId The ID of the category to resolve the parents
348
     * @param boolean $topLevel   TRUE if the passed category has top level, else FALSE
349
     *
350
     * @return void
351
     */
352 2
    protected function resolveCategoryIds($categoryId, $topLevel = false)
353
    {
354
355
        // return immediately if this is the absolute root node
356 2
        if ((integer) $categoryId === 1) {
357 2
            return;
358
        }
359
360
        // load the data of the category with the passed ID
361 2
        $category = $this->getCategory($categoryId);
362
363
        // query whether or not the product has already been related
364 2
        if (!in_array($categoryId, $this->productCategoryIds)) {
365 2
            if ($topLevel) {
366
                // relate it, if the category is top level
367 2
                $this->productCategoryIds[] = $categoryId;
368 2
            } elseif ((integer) $category[MemberNames::IS_ANCHOR] === 1) {
369
                // also relate it, if the category is not top level, but has the anchor flag set
370
                $this->productCategoryIds[] = $categoryId;
371
            } else {
372 2
                $this->getSubject()
373 2
                     ->getSystemLogger()
374 2
                     ->debug(sprintf('Don\'t create URL rewrite for category "%s" because of missing anchor flag', $category[MemberNames::PATH]));
375
            }
376
        }
377
378
        // load the root category
379 2
        $rootCategory = $this->getRootCategory();
380
381
        // try to resolve the parent category IDs
382 2
        if ($rootCategory[MemberNames::ENTITY_ID] !== ($parentId = $category[MemberNames::PARENT_ID])) {
383 2
            $this->resolveCategoryIds($parentId, false);
384
        }
385 2
    }
386
387
    /**
388
     * Prepare the attributes of the entity that has to be persisted.
389
     *
390
     * @return array The prepared attributes
391
     */
392 3
    protected function prepareAttributes()
393
    {
394
395
        // load the store ID to use
396 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...
397
398
        // load the category to create the URL rewrite for
399 3
        $category = $this->getCategory($this->categoryId);
400
401
        // initialize the values
402 3
        $requestPath = $this->prepareRequestPath($category);
403 3
        $targetPath = $this->prepareTargetPath($category);
404 3
        $metadata = serialize($this->prepareMetadata($category));
405
406
        // return the prepared URL rewrite
407 3
        return $this->initializeEntity(
408
            array(
409 3
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
410 3
                MemberNames::ENTITY_ID        => $this->entityId,
411 3
                MemberNames::REQUEST_PATH     => $requestPath,
412 3
                MemberNames::TARGET_PATH      => $targetPath,
413 3
                MemberNames::REDIRECT_TYPE    => 0,
414 3
                MemberNames::STORE_ID         => $storeId,
415 3
                MemberNames::DESCRIPTION      => null,
416 3
                MemberNames::IS_AUTOGENERATED => 1,
417 3
                MemberNames::METADATA         => $metadata
418
            )
419
        );
420
    }
421
422
    /**
423
     * Prepare's the URL rewrite product => category relation attributes.
424
     *
425
     * @return array The prepared attributes
426
     */
427 3
    protected function prepareUrlRewriteProductCategoryAttributes()
428
    {
429
430
        // return the prepared product
431 3
        return $this->initializeEntity(
432
            array(
433 3
                MemberNames::PRODUCT_ID => $this->entityId,
434 3
                MemberNames::CATEGORY_ID => $this->categoryId,
435 3
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
436
            )
437
        );
438
    }
439
440
    /**
441
     * Prepare's the target path for a URL rewrite.
442
     *
443
     * @param array $category The categroy with the URL path
444
     *
445
     * @return string The target path
446
     */
447 3
    protected function prepareTargetPath(array $category)
448
    {
449
450
        // query whether or not, the category is the root category
451 3
        if ($this->isRootCategory($category)) {
452 3
            $targetPath = sprintf('catalog/product/view/id/%d', $this->entityId);
453
        } else {
454 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $this->entityId, $category[MemberNames::ENTITY_ID]);
455
        }
456
457
        // return the target path
458 3
        return $targetPath;
459
    }
460
461
    /**
462
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
463
     *
464
     * @param array $category The categroy with the URL path
465
     *
466
     * @return string The request path
467
     * @throws \RuntimeException Is thrown, if the passed category has no or an empty value for attribute "url_path"
468
     */
469 3
    protected function prepareRequestPath(array $category)
470
    {
471
472
        // load the product URL suffix to use
473 3
        $urlSuffix = $this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_URL_SUFFIX, '.html');
474
475
        // query whether or not, the category is the root category
476 3
        if ($this->isRootCategory($category)) {
477 3
            return sprintf('%s%s', $this->urlKey, $urlSuffix);
478
        } else {
479
            // query whether or not the category's "url_path" attribute, necessary to create a valid "request_path", is available
480 2
            if (isset($category[MemberNames::URL_PATH]) && $category[MemberNames::URL_PATH]) {
481 2
                return sprintf('%s/%s%s', $category[MemberNames::URL_PATH], $this->urlKey, $urlSuffix);
482
            }
483
        }
484
485
        // throw an exception if the category's "url_path" attribute is NOT available
486
        throw new \RuntimeException(
487
            sprintf(
488
                'Can\'t find mandatory attribute "%s" for category ID "%d", necessary to build a valid "request_path"',
489
                MemberNames::URL_PATH,
490
                $category[MemberNames::ENTITY_ID]
491
            )
492
        );
493
    }
494
495
    /**
496
     * Prepare's the URL rewrite's metadata with the passed category values.
497
     *
498
     * @param array $category The category used for preparation
499
     *
500
     * @return array The metadata
501
     */
502 3
    protected function prepareMetadata(array $category)
503
    {
504
505
        // initialize the metadata
506 3
        $metadata = array();
507
508
        // query whether or not, the passed category IS the root category
509 3
        if ($this->isRootCategory($category)) {
510 3
            return $metadata;
511
        }
512
513
        // if not, set the category ID in the metadata
514 2
        $metadata[UrlRewriteObserver::CATEGORY_ID] = (integer) $category[MemberNames::ENTITY_ID];
515
516
        // return the metadata
517 2
        return $metadata;
518
    }
519
520
    /**
521
     * Query whether or not the actual entity is visible.
522
     *
523
     * @return boolean TRUE if the entity is visible, else FALSE
524
     */
525 3
    protected function isVisible()
526
    {
527 3
        return $this->getEntityIdVisibilityIdMapping() !== VisibilityKeys::VISIBILITY_NOT_VISIBLE;
528
    }
529
530
    /**
531
     * Return's the visibility for the passed entity ID, if it already has been mapped. The mapping will be created
532
     * by calling <code>\TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue</code> which will
533
     * be done by the <code>\TechDivision\Import\Product\Callbacks\VisibilityCallback</code>.
534
     *
535
     * @return integer The visibility ID
536
     * @throws \Exception Is thrown, if the entity ID has not been mapped
537
     * @see \TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue()
538
     */
539 3
    protected function getEntityIdVisibilityIdMapping()
540
    {
541 3
        return $this->getSubject()->getEntityIdVisibilityIdMapping();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getEntityIdVisibilityIdMapping() does only exist in the following implementations of said interface: TechDivision\Import\Product\Subjects\BunchSubject, 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...
542
    }
543
544
    /**
545
     * Return's the root category for the actual view store.
546
     *
547
     * @return array The store's root category
548
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
549
     */
550 3
    protected function getRootCategory()
551
    {
552 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.

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...
553
    }
554
555
    /**
556
     * Return's TRUE if the passed category IS the root category, else FALSE.
557
     *
558
     * @param array $category The category to query
559
     *
560
     * @return boolean TRUE if the passed category IS the root category
561
     */
562 3
    protected function isRootCategory(array $category)
563
    {
564
565
        // load the root category
566 3
        $rootCategory = $this->getRootCategory();
567
568
        // compare the entity IDs and return the result
569 3
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
570
    }
571
572
    /**
573
     * Return's the category with the passed path.
574
     *
575
     * @param string $path The path of the category to return
576
     *
577
     * @return array The category
578
     */
579 2
    protected function getCategoryByPath($path)
580
    {
581 2
        return $this->getSubject()->getCategoryByPath($path);
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...
582
    }
583
584
    /**
585
     * Return's the category with the passed ID.
586
     *
587
     * @param integer $categoryId The ID of the category to return
588
     *
589
     * @return array The category data
590
     */
591 2
    protected function getCategory($categoryId)
592
    {
593 2
        return $this->getSubject()->getCategory($categoryId);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getCategory() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, 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...
594
    }
595
596
    /**
597
     * Persist's the URL rewrite with the passed data.
598
     *
599
     * @param array $row The URL rewrite to persist
600
     *
601
     * @return string The ID of the persisted entity
602
     */
603 3
    protected function persistUrlRewrite($row)
604
    {
605 3
        return $this->getProductUrlRewriteProcessor()->persistUrlRewrite($row);
606
    }
607
608
    /**
609
     * Persist's the URL rewrite product => category relation with the passed data.
610
     *
611
     * @param array $row The URL rewrite product => category relation to persist
612
     *
613
     * @return void
614
     */
615 3
    protected function persistUrlRewriteProductCategory($row)
616
    {
617 3
        return $this->getProductUrlRewriteProcessor()->persistUrlRewriteProductCategory($row);
618
    }
619
620
    /**
621
     * Queries whether or not the passed SKU and store view code has already been processed.
622
     *
623
     * @param string $sku           The SKU to check been processed
624
     * @param string $storeViewCode The store view code to check been processed
625
     *
626
     * @return boolean TRUE if the SKU and store view code has been processed, else FALSE
627
     */
628 3
    protected function storeViewHasBeenProcessed($sku, $storeViewCode)
629
    {
630 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...
631
    }
632
633
    /**
634
     * Add the entity ID => visibility mapping for the actual entity ID.
635
     *
636
     * @param string $visibility The visibility of the actual entity to map
637
     *
638
     * @return void
639
     */
640 3
    protected function addEntityIdVisibilityIdMapping($visibility)
641
    {
642 3
        $this->getSubject()->addEntityIdVisibilityIdMapping($visibility);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method addEntityIdVisibilityIdMapping() does only exist in the following implementations of said interface: TechDivision\Import\Product\Subjects\BunchSubject, 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...
643 3
    }
644
645
    /**
646
     * Set's the ID of the product that has been created recently.
647
     *
648
     * @param string $lastEntityId The entity ID
649
     *
650
     * @return void
651
     */
652 3
    protected function setLastEntityId($lastEntityId)
653
    {
654 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...
655 3
    }
656
657
    /**
658
     * Load's and return's the product with the passed SKU.
659
     *
660
     * @param string $sku The SKU of the product to load
661
     *
662
     * @return array The product
663
     */
664 3
    protected function loadProduct($sku)
665
    {
666 3
        return $this->getProductUrlRewriteProcessor()->loadProduct($sku);
667
    }
668
}
669