Completed
Push — master ( 3a08fe...aee93b )
by Tim
18:05 queued 16:13
created

UrlKeyUtil::doMakeUnique()   C

Complexity

Conditions 10
Paths 24

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 84
ccs 35
cts 35
cp 1
rs 6.4824
c 0
b 0
f 0
cc 10
nc 24
nop 4
crap 10

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * TechDivision\Import\Utils\UrlKeyUtil
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 2021 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
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Utils;
22
23
use TechDivision\Import\Loaders\LoaderInterface;
24
use TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface;
25
use TechDivision\Import\Services\UrlKeyAwareProcessorInterface;
26
27
/**
28
 * Utility class that provides functionality to make URL keys unique.
29
 *
30
 * @author    Tim Wagner <[email protected]>
31
 * @copyright 2021 TechDivision GmbH <[email protected]>
32
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
33
 * @link      https://github.com/techdivision/import
34
 * @link      http://www.techdivision.com
35
 */
36
class UrlKeyUtil implements UrlKeyUtilInterface
37
{
38
39
    /**
40
     * The URL key aware processor instance.
41
     *
42
     * \TechDivision\Import\Services\UrlKeyAwareProcessorInterface
43
     */
44
    protected $urlKeyAwareProcessor;
45
46
    /**
47
     * The array with the entity type and store view specific suffixes.
48
     *
49
     * @var array
50
     */
51
    protected $suffixes = array();
52
53
    /**
54
     * The array with the entity type code > configuration key mapping.
55
     *
56
     * @var array
57
     */
58
    protected $entityTypeCodeToConfigKeyMapping = array(
59
        EntityTypeCodes::CATALOG_PRODUCT  => CoreConfigDataKeys::CATALOG_SEO_PRODUCT_URL_SUFFIX,
60
        EntityTypeCodes::CATALOG_CATEGORY => CoreConfigDataKeys::CATALOG_SEO_CATEGORY_URL_SUFFIX
61
    );
62
63
    /**
64
     * Construct a new instance.
65
     *
66
     * @param \TechDivision\Import\Services\UrlKeyAwareProcessorInterface $urlKeyAwareProcessor The URL key aware processor instance
67
     * @param \TechDivision\Import\Loaders\LoaderInterface                $coreConfigDataLoader The core config data loader instance
68
     * @param \TechDivision\Import\Loaders\LoaderInterface                $storeIdLoader        The core config data loader instance
69
     */
70 8
    public function __construct(
71
        UrlKeyAwareProcessorInterface $urlKeyAwareProcessor,
72
        LoaderInterface $coreConfigDataLoader,
73
        LoaderInterface $storeIdLoader
74
    ) {
75
76
        // initialize the URL kew aware processor instance
77 8
        $this->urlKeyAwareProcessor = $urlKeyAwareProcessor;
78
79
        // load the available stores
80 8
        $storeIds = $storeIdLoader->load();
81
82
        // initialize the URL suffixs from the Magento core configuration
83 8
        foreach ($storeIds as $storeId) {
84
            // prepare the array with the entity type and store ID specific suffixes
85 8
            foreach ($this->entityTypeCodeToConfigKeyMapping as $entityTypeCode => $configKey) {
86
                // load the suffix for the given entity type => configuration key and store ID
87 8
                $suffix = $coreConfigDataLoader->load($configKey, '.html', ScopeKeys::SCOPE_DEFAULT, $storeId);
0 ignored issues
show
Unused Code introduced by
The call to LoaderInterface::load() has too many arguments starting with $configKey.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
88
                // register the suffux in the array
89 8
                $this->suffixes[$entityTypeCode][$storeId] = $suffix;
90
            }
91
        }
92 8
    }
93
94
    /**
95
     * Returns the URL key aware processor instance.
96
     *
97
     * @return \TechDivision\Import\Services\UrlKeyAwareProcessorInterface The processor instance
98
     */
99 5
    protected function getUrlKeyAwareProcessor()
100
    {
101 5
        return $this->urlKeyAwareProcessor;
102
    }
103
104
    /**
105
     * Load's and return's the URL rewrite for the given request path and store ID
106
     *
107
     * @param string $requestPath The request path to load the URL rewrite for
108
     * @param int    $storeId     The store ID to load the URL rewrite for
109
     *
110
     * @return string|null The URL rewrite found for the given request path and store ID
111
     */
112
    protected function loadUrlRewriteByRequestPathAndStoreId(string $requestPath, int $storeId)
113
    {
114
        return $this->getUrlKeyAwareProcessor()->loadUrlRewriteByRequestPathAndStoreId($requestPath, $storeId);
115
    }
116
117
    /**
118
     * Make's the passed URL key unique by adding/raising a number to the end.
119
     *
120
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject The subject to make the URL key unique for
121
     * @param array                                                     $entity  The entity to make the URL key unique for
122
     * @param string                                                    $urlKey  The URL key to make unique
123
     * @param string|null                                               $urlPath The URL path to make unique (only used for categories)
124
     *
125
     * @return string The unique URL key
126
     */
127 8
    protected function doMakeUnique(UrlKeyAwareSubjectInterface $subject, array $entity, string $urlKey, string $urlPath = null) : string
128
    {
129
130
        // initialize the store view ID, use the default store view if no store view has
131
        // been set, because the default url_key value has been set in default store view
132 8
        $storeId = (int) $subject->getRowStoreId();
133 8
        $entityTypeCode = $subject->getEntityTypeCode();
134
135
        // initialize the entity ID from the passed entity
136 8
        $entityId = (int) $entity[MemberNames::ENTITY_ID];
137
138
        // initialize the counter
139 8
        $counter = 0;
140
141
        // initialize the counters
142 8
        $matchingCounters = array();
143 8
        $notMatchingCounters = array();
144
145
        // pre-initialze the URL by concatenating path and/or key to query for
146 8
        $url = $urlPath ? sprintf('%s/%s', $urlPath, $urlKey) : $urlKey;
147
148
        do {
149
            // prepare the request path to load an existing URL rewrite
150 8
            $requestPath = sprintf('%s%s', $url, $this->suffixes[$entityTypeCode][$storeId]);
151
            // try to load an existing URL rewrite
152 8
            $urlRewrite = $this->loadUrlRewriteByRequestPathAndStoreId($requestPath, $storeId);
153
154
            // try to load the entity's URL key
155 8
            if ($urlRewrite) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $urlRewrite 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...
156
                // this IS the URL key of the passed entity
157 6
                if (((int) $urlRewrite[MemberNames::ENTITY_ID]     === $entityId) &&
158 6
                    ((int) $urlRewrite[MemberNames::STORE_ID]      === $storeId) &&
159 6
                    ((int) $urlRewrite[MemberNames::REDIRECT_TYPE] === 0)
160
                ) {
161
                    // add the matching counter
162 3
                    $matchingCounters[] = $counter;
163
                    // stop further processing here, because we've a matching
164
                    // URL key and that's all we want for the moment
165 3
                    break;
166
                } else {
167 3
                    $notMatchingCounters[] = $counter;
168
                }
169
170
                // prepare the next URL key to query for
171 3
                $url = sprintf('%s-%d', $urlKey, ++$counter);
172
            } else {
173
                // we've temporary persist a dummy URL rewrite to keep track of the new URL key, e. g. for
174
                // the case the import contains another product or category that wants to use the same one
175 5
                $this->getUrlKeyAwareProcessor()->persistUrlRewrite(
176
                    array(
177 5
                        MemberNames::URL_REWRITE_ID => md5(sprintf('%d-%s', $storeId, $requestPath)),
178 5
                        MemberNames::REDIRECT_TYPE  => 0,
179 5
                        MemberNames::STORE_ID       => $storeId,
180 5
                        MemberNames::ENTITY_ID      => $entityId,
181 5
                        MemberNames::REQUEST_PATH   => $requestPath,
182
                        EntityStatus::MEMBER_NAME   => EntityStatus::STATUS_CREATE
183
                    )
184
                );
185
            }
186 5
        } while ($urlRewrite);
0 ignored issues
show
Bug Best Practice introduced by
The expression $urlRewrite 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...
187
188
        // sort the array ascending according to the counter
189 8
        asort($matchingCounters);
190 8
        asort($notMatchingCounters);
191
192
        // this IS the URL key of the passed entity => we've an UPDATE
193 8
        if (sizeof($matchingCounters) > 0) {
194
            // load highest counter
195 3
            $counter = end($matchingCounters);
196
            // if the counter is > 0, we've to append it to the new URL key
197 3
            if ($counter > 0) {
198 3
                $urlKey = sprintf('%s-%d', $urlKey, $counter);
199
            }
200 5
        } elseif (sizeof($notMatchingCounters) > 0) {
201
            // load the last entry that contains the
202
            // the last NOT matching counter
203 3
            $newCounter = end($notMatchingCounters);
204
            // create a new URL key by raising the counter
205 3
            $urlKey = sprintf('%s-%d', $urlKey, ++$newCounter);
206
        }
207
208
        // return the passed URL key, if NOT
209 8
        return $urlKey;
210
    }
211
212
    /**
213
     * Make's the passed URL key unique by adding the next number to the end.
214
     *
215
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject  The subject to make the URL key unique for
216
     * @param array                                                     $entity   The entity to make the URL key unique for
217
     * @param string                                                    $urlKey   The URL key to make unique
218
     * @param array                                                     $urlPaths The URL paths to make unique
219
     *
220
     * @return string The unique URL key
221
     */
222 8
    public function makeUnique(UrlKeyAwareSubjectInterface $subject, array $entity, string $urlKey, array $urlPaths = array()) : string
223
    {
224
225
        // iterate over the passed URL paths
226
        // and try to find a unique URL key
227 8
        for ($i = sizeof($urlPaths) > 0 ? 0 : -1; $i < sizeof($urlPaths); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
228
            // try to make the URL key unique for the given URL path
229 8
            $proposedUrlKey = $this->doMakeUnique($subject, $entity, $urlKey, isset($urlPaths[$i]) ? $urlPaths[$i] : null);
230
231
            // if the URL key is NOT the same as the passed one or with the parent URL path
232
            // it can NOT be used, so we've to persist it temporarily and try it again for
233
            // all the other URL paths until we found one that works with every URL path
234 8
            if ($urlKey !== $proposedUrlKey) {
235
                // temporarily persist the URL key
236 3
                $urlKey = $proposedUrlKey;
237
                // reset the counter and restart the
238
                // iteration with the first URL path
239 3
                $i = -2;
240
            }
241
        }
242
243
        // return the unique URL key
244 8
        return $urlKey;
245
    }
246
247
    /**
248
     * Load the url_key if exists
249
     *
250
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject      The subject to make the URL key unique for
251
     * @param int                                                       $primaryKeyId The ID from category or product
252
     *
253
     * @return string|null The URL key
254
     */
255
    public function loadUrlKey(UrlKeyAwareSubjectInterface $subject, $primaryKeyId)
256
    {
257
258
        // initialize the entity type ID
259
        $entityType = $subject->getEntityType();
0 ignored issues
show
Bug introduced by
The method getEntityType() does not exist on TechDivision\Import\Subj...eyAwareSubjectInterface. Did you maybe mean getEntityTypeCode()?

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...
260
        $entityTypeId = (integer) $entityType[MemberNames::ENTITY_TYPE_ID];
261
262
        // initialize the store view ID, use the admin store view if no store view has
263
        // been set, because the default url_key value has been set in admin store view
264
        $storeId = $subject->getRowStoreId(StoreViewCodes::ADMIN);
265
266
        // try to load the attribute
267
        $attribute = $this->getUrlKeyAwareProcessor()
268
            ->loadVarcharAttributeByAttributeCodeAndEntityTypeIdAndStoreIdAndPrimaryKey(
269
                MemberNames::URL_KEY,
270
                $entityTypeId,
271
                $storeId,
272
                $primaryKeyId
273
            );
274
275
        // return the attribute value or null, if not available
276
        return $attribute ? $attribute['value'] : null;
277
    }
278
}
279