Completed
Push — master ( dc4a22...2425ba )
by Tim
14s
created

UrlRewriteObserver::persistUrlRewrite()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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 3 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 3 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 3
        }
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 3
                    );
262
263
                    // persist the URL rewrite product category relation
264 3
                    $this->persistUrlRewriteProductCategory($urlRewriteProductCategory);
265
266 3
                } catch (\Exception $e) {
267
                    // query whether or not debug mode has been enabled
268 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...
269
                        $this->getSubject()
270
                             ->getSystemLogger()
271
                             ->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...
272
                    } else {
273
                        throw $e;
274
                    }
275
                }
276 3
            }
277 3
        }
278 3
    }
279
280
    /**
281
     * Initialize the category product with the passed attributes and returns an instance.
282
     *
283
     * @param array $attr The category product attributes
284
     *
285
     * @return array The initialized category product
286
     */
287 2
    protected function initializeUrlRewrite(array $attr)
288
    {
289 2
        return $attr;
290
    }
291
292
    /**
293
     * Initialize the URL rewrite product => category relation with the passed attributes
294
     * and returns an instance.
295
     *
296
     * @param array $attr The URL rewrite product => category relation attributes
297
     *
298
     * @return array The initialized URL rewrite product => category relation
299
     */
300 2
    protected function initializeUrlRewriteProductCategory($attr)
301
    {
302 2
        return $attr;
303
    }
304
305
    /**
306
     * Prepare's the URL rewrites that has to be created/updated.
307
     *
308
     * @return void
309
     */
310 3
    protected function prepareUrlRewrites()
311
    {
312
313
        // (re-)initialize the array for the URL rewrites and the product category IDs
314 3
        $this->urlRewrites = array();
315 3
        $this->productCategoryIds = array();
316
317
        // load the root category, because we need that to create the default product URL rewrite
318 3
        $rootCategory = $this->getRootCategory();
319
320
        // at least, add the root category ID to the category => product relations
321 3
        $this->productCategoryIds[] = $rootCategory[MemberNames::ENTITY_ID];
322
323
        // query whether or not categories has to be used as product URL suffix
324 3
        if ($this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_USE_CATEGORIES, false)) {
325
            // append the category => product relations found
326 3
            foreach ($this->getValue(ColumnKeys::CATEGORIES, array(), array($this, 'explode')) as $path) {
327
                try {
328
                    // try to load the category for the given path
329 2
                    $category = $this->getCategoryByPath(trim($path));
330
                    // resolve the product's categories recursively
331 2
                    $this->resolveCategoryIds($category[MemberNames::ENTITY_ID], true);
332
333 2
                } catch (\Exception $e) {
334
                    // query whether or not debug mode has been enabled
335 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...
336
                        $this->getSubject()
337
                             ->getSystemLogger()
338
                             ->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...
339
                    } else {
340
                        throw $e;
341
                    }
342
                }
343 3
            }
344 3
        }
345
346
        // prepare the URL rewrites
347 3
        foreach ($this->productCategoryIds as $categoryId) {
348
            // set the category ID
349 3
            $this->categoryId = $categoryId;
350
351
            // prepare the attributes for each URL rewrite
352 3
            $this->urlRewrites[$categoryId] = $this->prepareAttributes();
353 3
        }
354 3
    }
355
356
    /**
357
     * Resolve's the parent categories of the category with the passed ID and relate's
358
     * it with the product with the passed ID, if the category is top level OR has the
359
     * anchor flag set.
360
     *
361
     * @param integer $categoryId The ID of the category to resolve the parents
362
     * @param boolean $topLevel   TRUE if the passed category has top level, else FALSE
363
     *
364
     * @return void
365
     */
366 2
    protected function resolveCategoryIds($categoryId, $topLevel = false)
367
    {
368
369
        // return immediately if this is the absolute root node
370 2
        if ((integer) $categoryId === 1) {
371 2
            return;
372
        }
373
374
        // load the data of the category with the passed ID
375 2
        $category = $this->getCategory($categoryId);
376
377
        // query whether or not the product has already been related
378 2
        if (!in_array($categoryId, $this->productCategoryIds)) {
379 2
            if ($topLevel) {
380
                // relate it, if the category is top level
381 2
                $this->productCategoryIds[] = $categoryId;
382 2
            } elseif ((integer) $category[MemberNames::IS_ANCHOR] === 1) {
383
                // also relate it, if the category is not top level, but has the anchor flag set
384
                $this->productCategoryIds[] = $categoryId;
385
            } else {
386 2
                $this->getSubject()
387 2
                     ->getSystemLogger()
388 2
                     ->debug(sprintf('Don\'t create URL rewrite for category "%s" because of missing anchor flag', $category[MemberNames::PATH]));
389
            }
390 2
        }
391
392
        // load the root category
393 2
        $rootCategory = $this->getRootCategory();
394
395
        // try to resolve the parent category IDs
396 2
        if ($rootCategory[MemberNames::ENTITY_ID] !== ($parentId = $category[MemberNames::PARENT_ID])) {
397 2
            $this->resolveCategoryIds($parentId, false);
398 2
        }
399 2
    }
400
401
    /**
402
     * Prepare the attributes of the entity that has to be persisted.
403
     *
404
     * @return array The prepared attributes
405
     */
406 3
    protected function prepareAttributes()
407
    {
408
409
        // load the store ID to use
410 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...
411
412
        // load the category to create the URL rewrite for
413 3
        $category = $this->getCategory($this->categoryId);
414
415
        // initialize the values
416 3
        $requestPath = $this->prepareRequestPath($category);
417 3
        $targetPath = $this->prepareTargetPath($category);
418 3
        $metadata = serialize($this->prepareMetadata($category));
419
420
        // return the prepared URL rewrite
421 3
        return $this->initializeEntity(
422
            array(
423 3
                MemberNames::ENTITY_TYPE      => UrlRewriteObserver::ENTITY_TYPE,
424 3
                MemberNames::ENTITY_ID        => $this->entityId,
425 3
                MemberNames::REQUEST_PATH     => $requestPath,
426 3
                MemberNames::TARGET_PATH      => $targetPath,
427 3
                MemberNames::REDIRECT_TYPE    => 0,
428 3
                MemberNames::STORE_ID         => $storeId,
429 3
                MemberNames::DESCRIPTION      => null,
430 3
                MemberNames::IS_AUTOGENERATED => 1,
431 3
                MemberNames::METADATA         => $metadata
432 3
            )
433 3
        );
434
    }
435
436
    /**
437
     * Prepare's the URL rewrite product => category relation attributes.
438
     *
439
     * @return array The prepared attributes
440
     */
441 3
    protected function prepareUrlRewriteProductCategoryAttributes()
442
    {
443
444
        // return the prepared product
445 3
        return $this->initializeEntity(
446
            array(
447 3
                MemberNames::PRODUCT_ID => $this->entityId,
448 3
                MemberNames::CATEGORY_ID => $this->categoryId,
449 3
                MemberNames::URL_REWRITE_ID => $this->urlRewriteId
450 3
            )
451 3
        );
452
    }
453
454
    /**
455
     * Prepare's the target path for a URL rewrite.
456
     *
457
     * @param array $category The categroy with the URL path
458
     *
459
     * @return string The target path
460
     */
461 3
    protected function prepareTargetPath(array $category)
462
    {
463
464
        // query whether or not, the category is the root category
465 3
        if ($this->isRootCategory($category)) {
466 3
            $targetPath = sprintf('catalog/product/view/id/%d', $this->entityId);
467 3
        } else {
468 2
            $targetPath = sprintf('catalog/product/view/id/%d/category/%d', $this->entityId, $category[MemberNames::ENTITY_ID]);
469
        }
470
471
        // return the target path
472 3
        return $targetPath;
473
    }
474
475
    /**
476
     * Prepare's the request path for a URL rewrite or the target path for a 301 redirect.
477
     *
478
     * @param array $category The categroy with the URL path
479
     *
480
     * @return string The request path
481
     * @throws \RuntimeException Is thrown, if the passed category has no or an empty value for attribute "url_path"
482
     */
483 3
    protected function prepareRequestPath(array $category)
484
    {
485
486
        // load the product URL suffix to use
487 3
        $urlSuffix = $this->getSubject()->getCoreConfigData(CoreConfigDataKeys::CATALOG_SEO_PRODUCT_URL_SUFFIX, '.html');
488
489
        // query whether or not, the category is the root category
490 3
        if ($this->isRootCategory($category)) {
491 3
            return sprintf('%s%s', $this->urlKey, $urlSuffix);
492
        } else {
493
            // query whether or not the category's "url_path" attribute, necessary to create a valid "request_path", is available
494 2
            if (isset($category[MemberNames::URL_PATH]) && $category[MemberNames::URL_PATH]) {
495 2
                return sprintf('%s/%s%s', $category[MemberNames::URL_PATH], $this->urlKey, $urlSuffix);
496
            }
497
        }
498
499
        // throw an exception if the category's "url_path" attribute is NOT available
500
        throw new \RuntimeException(
501
            sprintf(
502
                'Can\'t find mandatory attribute "%s" for category ID "%d", necessary to build a valid "request_path"',
503
                MemberNames::URL_PATH,
504
                $category[MemberNames::ENTITY_ID]
505
            )
506
        );
507
    }
508
509
    /**
510
     * Prepare's the URL rewrite's metadata with the passed category values.
511
     *
512
     * @param array $category The category used for preparation
513
     *
514
     * @return array The metadata
515
     */
516 3
    protected function prepareMetadata(array $category)
517
    {
518
519
        // initialize the metadata
520 3
        $metadata = array();
521
522
        // query whether or not, the passed category IS the root category
523 3
        if ($this->isRootCategory($category)) {
524 3
            return $metadata;
525
        }
526
527
        // if not, set the category ID in the metadata
528 2
        $metadata[UrlRewriteObserver::CATEGORY_ID] = (integer) $category[MemberNames::ENTITY_ID];
529
530
        // return the metadata
531 2
        return $metadata;
532
    }
533
534
    /**
535
     * Query whether or not the actual entity is visible.
536
     *
537
     * @return boolean TRUE if the entity is visible, else FALSE
538
     */
539 3
    protected function isVisible()
540
    {
541 3
        return $this->getEntityIdVisibilityIdMapping() !== VisibilityKeys::VISIBILITY_NOT_VISIBLE;
542
    }
543
544
    /**
545
     * Return's the visibility for the passed entity ID, if it already has been mapped. The mapping will be created
546
     * by calling <code>\TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue</code> which will
547
     * be done by the <code>\TechDivision\Import\Product\Callbacks\VisibilityCallback</code>.
548
     *
549
     * @return integer The visibility ID
550
     * @throws \Exception Is thrown, if the entity ID has not been mapped
551
     * @see \TechDivision\Import\Product\Subjects\BunchSubject::getVisibilityIdByValue()
552
     */
553 3
    protected function getEntityIdVisibilityIdMapping()
554
    {
555 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...
556
    }
557
558
    /**
559
     * Return's the root category for the actual view store.
560
     *
561
     * @return array The store's root category
562
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
563
     */
564 3
    protected function getRootCategory()
565
    {
566 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...
567
    }
568
569
    /**
570
     * Return's TRUE if the passed category IS the root category, else FALSE.
571
     *
572
     * @param array $category The category to query
573
     *
574
     * @return boolean TRUE if the passed category IS the root category
575
     */
576 3
    protected function isRootCategory(array $category)
577
    {
578
579
        // load the root category
580 3
        $rootCategory = $this->getRootCategory();
581
582
        // compare the entity IDs and return the result
583 3
        return $rootCategory[MemberNames::ENTITY_ID] === $category[MemberNames::ENTITY_ID];
584
    }
585
586
    /**
587
     * Return's the category with the passed path.
588
     *
589
     * @param string $path The path of the category to return
590
     *
591
     * @return array The category
592
     */
593 2
    protected function getCategoryByPath($path)
594
    {
595 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...
596
    }
597
598
    /**
599
     * Return's the category with the passed ID.
600
     *
601
     * @param integer $categoryId The ID of the category to return
602
     *
603
     * @return array The category data
604
     */
605 2
    protected function getCategory($categoryId)
606
    {
607 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...
608
    }
609
610
    /**
611
     * Persist's the URL rewrite with the passed data.
612
     *
613
     * @param array $row The URL rewrite to persist
614
     *
615
     * @return string The ID of the persisted entity
616
     */
617 3
    protected function persistUrlRewrite($row)
618
    {
619 3
        return $this->getProductUrlRewriteProcessor()->persistUrlRewrite($row);
620
    }
621
622
    /**
623
     * Persist's the URL rewrite product => category relation with the passed data.
624
     *
625
     * @param array $row The URL rewrite product => category relation to persist
626
     *
627
     * @return void
628
     */
629 3
    protected function persistUrlRewriteProductCategory($row)
630
    {
631 3
        return $this->getProductUrlRewriteProcessor()->persistUrlRewriteProductCategory($row);
632
    }
633
634
    /**
635
     * Queries whether or not the passed SKU and store view code has already been processed.
636
     *
637
     * @param string $sku           The SKU to check been processed
638
     * @param string $storeViewCode The store view code to check been processed
639
     *
640
     * @return boolean TRUE if the SKU and store view code has been processed, else FALSE
641
     */
642 3
    protected function storeViewHasBeenProcessed($sku, $storeViewCode)
643
    {
644 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...
645
    }
646
647
    /**
648
     * Add the entity ID => visibility mapping for the actual entity ID.
649
     *
650
     * @param string $visibility The visibility of the actual entity to map
651
     *
652
     * @return void
653
     */
654 3
    protected function addEntityIdVisibilityIdMapping($visibility)
655
    {
656 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...
657 3
    }
658
659
    /**
660
     * Set's the ID of the product that has been created recently.
661
     *
662
     * @param string $lastEntityId The entity ID
663
     *
664
     * @return void
665
     */
666 3
    protected function setLastEntityId($lastEntityId)
667
    {
668 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...
669 3
    }
670
671
    /**
672
     * Load's and return's the product with the passed SKU.
673
     *
674
     * @param string $sku The SKU of the product to load
675
     *
676
     * @return array The product
677
     */
678 3
    protected function loadProduct($sku)
679
    {
680 3
        return $this->getProductUrlRewriteProcessor()->loadProduct($sku);
681
    }
682
}
683