Completed
Push — pac-307--update-url-key-from-n... ( dbd0e4...a05889 )
by
unknown
01:34
created

UrlKeyAndPathObserver   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 23
lcom 1
cbo 7
dl 0
loc 240
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getPkMemberName() 0 4 1
A getCategoryBunchProcessor() 0 4 1
A getUrlKeyUtil() 0 4 1
A getCategoryByPath() 0 4 1
A getCategoryByPkAndStoreId() 0 4 1
A setIds() 0 4 2
A setLastEntityId() 0 4 1
A __construct() 0 11 1
C process() 0 75 11
A getPrimaryKey() 0 4 1
A loadUrlKey() 0 4 1
A makeUnique() 0 4 1
1
<?php
2
3
/**
4
 * TechDivision\Import\Category\Observers\UrlKeyAndPathObserver
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 2019 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-category
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Category\Observers;
22
23
use Zend\Filter\FilterInterface;
24
use TechDivision\Import\Utils\StoreViewCodes;
25
use TechDivision\Import\Utils\UrlKeyUtilInterface;
26
use TechDivision\Import\Utils\Filter\UrlKeyFilterTrait;
27
use TechDivision\Import\Category\Utils\ColumnKeys;
28
use TechDivision\Import\Category\Utils\MemberNames;
29
use TechDivision\Import\Category\Utils\ConfigurationKeys;
30
use TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface;
31
use TechDivision\Import\Category\Services\CategoryBunchProcessorInterface;
32
33
/**
34
 * Observer that extracts the URL key/path from the category path
35
 * and adds them as two new columns with the their values.
36
 *
37
 * @author    Tim Wagner <[email protected]>
38
 * @copyright 2019 TechDivision GmbH <[email protected]>
39
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
40
 * @link      https://github.com/techdivision/import-category
41
 * @link      http://www.techdivision.com
42
 */
43
class UrlKeyAndPathObserver extends AbstractCategoryImportObserver
44
{
45
46
    /**
47
     * The trait that provides string => URL key conversion functionality.
48
     *
49
     * @var \TechDivision\Import\Utils\Filter\UrlKeyFilterTrait
50
     */
51
    use UrlKeyFilterTrait;
52
53
    /**
54
     * The URL key utility instance.
55
     *
56
     * @var \TechDivision\Import\Utils\UrlKeyUtilInterface
57
     */
58
    protected $urlKeyUtil;
59
60
    /**
61
     * The category bunch processor instance.
62
     *
63
     * @var \TechDivision\Import\Category\Services\CategoryBunchProcessorInterface
64
     */
65
    protected $categoryBunchProcessor;
66
67
    /**
68
     * Initialize the observer with the passed product bunch processor instance.
69
     *
70
     * @param \TechDivision\Import\Category\Services\CategoryBunchProcessorInterface $categoryBunchProcessor  The category bunch processor instance
71
     * @param \Zend\Filter\FilterInterface                                           $convertLiteralUrlFilter The URL filter instance
72
     * @param \TechDivision\Import\Utils\UrlKeyUtilInterface                         $urlKeyUtil              The URL key utility instance
73
     */
74
    public function __construct(
75
        CategoryBunchProcessorInterface $categoryBunchProcessor,
76
        FilterInterface $convertLiteralUrlFilter,
77
        UrlKeyUtilInterface $urlKeyUtil
78
    ) {
79
80
        // set the processor and the URL filter instance
81
        $this->categoryBunchProcessor = $categoryBunchProcessor;
82
        $this->convertLiteralUrlFilter = $convertLiteralUrlFilter;
83
        $this->urlKeyUtil = $urlKeyUtil;
84
    }
85
86
    /**
87
     * Process the observer's business logic.
88
     *
89
     * @return void
90
     */
91
    protected function process()
92
    {
93
94
        // initialize the URL key and array for the categories
95
        $urlKey = null;
96
        $category = array();
97
98
        // set the entity ID for the category with the passed path
99
        try {
100
            $this->setIds($category = $this->getCategoryByPath($this->getValue(ColumnKeys::PATH)));
101
        } catch (\Exception $e) {
102
            $this->setIds(array());
103
        }
104
105
        // query whether or not the URL key column has a value
106
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
107
            $urlKey = $this->makeUnique($this->getSubject(), $this->getValue(ColumnKeys::URL_KEY));
0 ignored issues
show
Documentation introduced by
$this->getSubject() is of type object<TechDivision\Impo...jects\SubjectInterface>, but the function expects a object<TechDivision\Impo...yAwareSubjectInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
108
        } else {
109
            // query whether or not the column `url_key` has a value
110
            if ($category &&
0 ignored issues
show
Bug Best Practice introduced by
The expression $category of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
111
                !$this->getSubject()->getConfiguration()->getParam(ConfigurationKeys::UPDATE_URL_KEY_FROM_NAME, true)
112
            ) {
113
                // product already exists and NO recalc from product key,
114
                // so we search origin url_key from product
115
                $urlKey = $this->loadUrlKey(
116
                    $this->getSubject(),
0 ignored issues
show
Documentation introduced by
$this->getSubject() is of type object<TechDivision\Impo...jects\SubjectInterface>, but the function expects a object<TechDivision\Impo...yAwareSubjectInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
117
                    $this->getPrimaryKey()
118
                );
119
            }
120
            if (!$urlKey) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $urlKey of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
121
                $urlKey = $this->makeUnique($this->getSubject(), $this->convertNameToUrlKey($this->getValue(ColumnKeys::NAME)));
0 ignored issues
show
Documentation introduced by
$this->getSubject() is of type object<TechDivision\Impo...jects\SubjectInterface>, but the function expects a object<TechDivision\Impo...yAwareSubjectInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
122
            }
123
            // let the `url_key` has a value
124
            $this->setValue(
125
                ColumnKeys::URL_KEY,
126
                $urlKey
127
            );
128
        }
129
130
        // explode the path into the category names
131
        if ($categories = $this->explode($this->getValue(ColumnKeys::PATH), '/')) {
132
            // initialize the category with the actual category's URL key
133
            $categoryPaths = array($urlKey);
134
            // prepare the store view code
135
            $this->prepareStoreViewCode();
136
            // load ID of the actual store view
137
            $storeId = $this->getRowStoreId(StoreViewCodes::ADMIN);
138
139
            // iterate over the category names and try to load the category therefore
140
            for ($i = sizeof($categories) - 1; $i > 1; $i--) {
141
                try {
142
                    // prepare the expected category name
143
                    $categoryPath = implode('/', array_slice($categories, 0, $i));
144
                    // load the existing category and prepend the URL key the array with the category URL keys
145
                    $existingCategory = $this->getCategoryByPkAndStoreId($this->mapPath($categoryPath), $storeId);
146
                    // query whether or not an URL key is available or not
147
                    if (isset($existingCategory[MemberNames::URL_KEY])) {
148
                        array_unshift($categoryPaths, $existingCategory[MemberNames::URL_KEY]);
149
                    } else {
150
                        $this->getSystemLogger()->debug(sprintf('Can\'t find URL key for category %s', $categoryPath));
151
                    }
152
                } catch (\Exception $e) {
153
                    $this->getSystemLogger()->debug(sprintf('Can\'t load parent category %s', $categoryPath));
154
                }
155
            }
156
157
            // create the header for the URL path
158
            if (!$this->hasHeader(ColumnKeys::URL_PATH)) {
159
                $this->addHeader(ColumnKeys::URL_PATH);
160
            }
161
162
            // set the URL path
163
            $this->setValue(ColumnKeys::URL_PATH, implode('/', $categoryPaths));
164
        }
165
    }
166
167
    /**
168
     * Return the primary key member name.
169
     *
170
     * @return string The primary key member name
171
     */
172
    protected function getPkMemberName()
173
    {
174
        return MemberNames::ENTITY_ID;
175
    }
176
177
    /**
178
     * Returns the category bunch processor instance.
179
     *
180
     * @return \TechDivision\Import\Category\Services\CategoryBunchProcessorInterface The category bunch processor instance
181
     */
182
    protected function getCategoryBunchProcessor()
183
    {
184
        return $this->categoryBunchProcessor;
185
    }
186
187
    /**
188
     * Returns the URL key utility instance.
189
     *
190
     * @return \TechDivision\Import\Utils\UrlKeyUtilInterface The URL key utility instance
191
     */
192
    protected function getUrlKeyUtil()
193
    {
194
        return $this->urlKeyUtil;
195
    }
196
197
    /**
198
     * Return's the category with the passed path.
199
     *
200
     * @param string $path The path of the category to return
201
     *
202
     * @return array The category
203
     * @throws \Exception Is thrown, if the requested category is not available
204
     */
205
    protected function getCategoryByPath($path)
206
    {
207
        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\Cate...AbstractCategorySubject, TechDivision\Import\Category\Subjects\BunchSubject, TechDivision\Import\Cate...ts\SortableBunchSubject.

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...
208
    }
209
210
    /**
211
     * Returns the category with the passed primary key and the attribute values for the passed store ID.
212
     *
213
     * @param string  $pk      The primary key of the category to return
214
     * @param integer $storeId The store ID of the category values
215
     *
216
     * @return array|null The category data
217
     */
218
    protected function getCategoryByPkAndStoreId($pk, $storeId)
219
    {
220
        return $this->getCategoryBunchProcessor()->getCategoryByPkAndStoreId($pk, $storeId);
221
    }
222
223
    /**
224
     * Temporarily persist's the IDs of the passed category.
225
     *
226
     * @param array $category The category to temporarily persist the IDs for
227
     *
228
     * @return void
229
     */
230
    protected function setIds(array $category)
231
    {
232
        $this->setLastEntityId(isset($category[$this->getPkMemberName()]) ? $category[$this->getPkMemberName()] : null);
233
    }
234
235
    /**
236
     * Set's the ID of the category that has been created recently.
237
     *
238
     * @param string $lastEntityId The entity ID
239
     *
240
     * @return void
241
     */
242
    protected function setLastEntityId($lastEntityId)
243
    {
244
        $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\Cate...AbstractCategorySubject, TechDivision\Import\Category\Subjects\BunchSubject, TechDivision\Import\Cate...ts\SortableBunchSubject, TechDivision\Import\Plugins\ExportableSubjectImpl, 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...
245
    }
246
247
    /**
248
     * Return's the PK to of the product.
249
     *
250
     * @return integer The PK to create the relation with
251
     */
252
    protected function getPrimaryKey()
253
    {
254
        $this->getSubject()->getLastEntityId();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getLastEntityId() does only exist in the following implementations of said interface: TechDivision\Import\Cate...AbstractCategorySubject, TechDivision\Import\Category\Subjects\BunchSubject, TechDivision\Import\Cate...ts\SortableBunchSubject, TechDivision\Import\Observers\EntitySubjectImpl, TechDivision\Import\Plugins\ExportableSubjectImpl, 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...
255
    }
256
257
    /**
258
     * Load's and return's the url_key with the passed primary ID.
259
     *
260
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject      The subject to load the URL key
261
     * @param int                                                       $primaryKeyId The ID from category
262
     *
263
     * @return string|null url_key or null
264
     */
265
    protected function loadUrlKey(UrlKeyAwareSubjectInterface $subject, $primaryKeyId)
0 ignored issues
show
Unused Code introduced by
The parameter $primaryKeyId is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
266
    {
267
        return $this->getUrlKeyUtil()->loadUrlKey($subject, $primaryKeyFromSku);
0 ignored issues
show
Bug introduced by
The variable $primaryKeyFromSku does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The method loadUrlKey() does not seem to exist on object<TechDivision\Impo...ls\UrlKeyUtilInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
268
    }
269
270
    /**
271
     * Make's the passed URL key unique by adding the next number to the end.
272
     *
273
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject The subject to make the URL key unique for
274
     * @param string                                                    $urlKey  The URL key to make unique
275
     *
276
     * @return string The unique URL key
277
     */
278
    protected function makeUnique(UrlKeyAwareSubjectInterface $subject, $urlKey)
279
    {
280
        return $this->getUrlKeyUtil()->makeUnique($subject, $urlKey);
281
    }
282
}
283