Completed
Push — location_multi_load ( e5e305 )
by André
32:49 queued 12:49
created

LocationHandler::loadList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 8
Ratio 42.11 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 8
loc 19
rs 9.6333
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing the LocationHandler implementation.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Persistence\Cache;
10
11
use eZ\Publish\SPI\Persistence\Content\Location\Handler as LocationHandlerInterface;
12
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct;
13
use eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct;
14
use eZ\Publish\SPI\Persistence\Content\Location;
15
16
/**
17
 * @see \eZ\Publish\SPI\Persistence\Content\Location\Handler
18
 */
19
class LocationHandler extends AbstractHandler implements LocationHandlerInterface
20
{
21
    /**
22
     * {@inheritdoc}
23
     */
24 View Code Duplication
    public function load($locationId, array $translations = null, bool $useAlwaysAvailable = true)
25
    {
26
        $translationsKey = $this->getCacheTranslationKey($translations, $useAlwaysAvailable);
27
        $cacheItem = $this->cache->getItem("ez-location-${locationId}-${translationsKey}");
28
        if ($cacheItem->isHit()) {
29
            return $cacheItem->get();
30
        }
31
32
        $this->logger->logCall(
33
            __METHOD__,
34
            ['location' => $locationId, 'translations' => $translations, 'always-available' => $useAlwaysAvailable]
35
        );
36
        $location = $this->persistenceHandler->locationHandler()->load($locationId, $translations, $useAlwaysAvailable);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 24 can also be of type array; however, eZ\Publish\SPI\Persisten...ocation\Handler::load() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
37
38
        $cacheItem->set($location);
39
        $cacheItem->tag($this->getCacheTags($location));
40
        $this->cache->save($cacheItem);
41
42
        return $location;
43
    }
44
45
    public function loadList(array $locationIds, array $translations = null, bool $useAlwaysAvailable = true): iterable
46
    {
47
        return $this->getMultipleCacheItems(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getMultipl...$useAlwaysAvailable))); (array) is incompatible with the return type declared by the interface eZ\Publish\SPI\Persisten...ation\Handler::loadList of type eZ\Publish\SPI\Persisten...ntent\Location\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
48
            $locationIds,
49
            'ez-location-',
50 View Code Duplication
            function (array $cacheMissIds) use ($translations, $useAlwaysAvailable) {
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...
51
                $this->logger->logCall(
52
                    __CLASS__ . '::loadList',
53
                    ['location' => $cacheMissIds, 'translations' => $translations, 'always-available' => $useAlwaysAvailable]
54
                );
55
56
                return $this->persistenceHandler->locationHandler()->loadList($cacheMissIds, $translations, $useAlwaysAvailable);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 45 can also be of type array; however, eZ\Publish\SPI\Persisten...ion\Handler::loadList() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
57
            },
58
            function (Location $location) {
59
                return $this->getCacheTags($location);
60
            },
61
            array_fill_keys($locationIds, '-' . $this->getCacheTranslationKey($translations, $useAlwaysAvailable))
62
        );
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function loadSubtreeIds($locationId)
69
    {
70
        $cacheItem = $this->cache->getItem("ez-location-subtree-${locationId}");
71
        if ($cacheItem->isHit()) {
72
            return $cacheItem->get();
73
        }
74
75
        $this->logger->logCall(__METHOD__, array('location' => $locationId));
76
        $locationIds = $this->persistenceHandler->locationHandler()->loadSubtreeIds($locationId);
77
78
        $cacheItem->set($locationIds);
79
        $cacheTags = ['location-' . $locationId, 'location-path-' . $locationId];
80
        foreach ($locationIds as $id) {
81
            $cacheTags[] = 'location-' . $id;
82
            $cacheTags[] = 'location-path-' . $id;
83
        }
84
        $cacheItem->tag($cacheTags);
85
        $this->cache->save($cacheItem);
86
87
        return $locationIds;
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function loadLocationsByContent($contentId, $rootLocationId = null)
94
    {
95
        if ($rootLocationId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rootLocationId of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
96
            $cacheItem = $this->cache->getItem("ez-content-locations-${contentId}-root-${rootLocationId}");
97
            $cacheTags = ['content-' . $contentId, 'location-' . $rootLocationId, 'location-path-' . $rootLocationId];
98
        } else {
99
            $cacheItem = $this->cache->getItem("ez-content-locations-${contentId}");
100
            $cacheTags = ['content-' . $contentId];
101
        }
102
103
        if ($cacheItem->isHit()) {
104
            return $cacheItem->get();
105
        }
106
107
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'root' => $rootLocationId));
108
        $locations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentId, $rootLocationId);
109
110
        $cacheItem->set($locations);
111
        foreach ($locations as $location) {
112
            $cacheTags = $this->getCacheTags($location, $cacheTags);
113
        }
114
        $cacheItem->tag($cacheTags);
115
        $this->cache->save($cacheItem);
116
117
        return $locations;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123 View Code Duplication
    public function loadParentLocationsForDraftContent($contentId)
124
    {
125
        $cacheItem = $this->cache->getItem("ez-content-locations-${contentId}-parentForDraft");
126
        if ($cacheItem->isHit()) {
127
            return $cacheItem->get();
128
        }
129
130
        $this->logger->logCall(__METHOD__, array('content' => $contentId));
131
        $locations = $this->persistenceHandler->locationHandler()->loadParentLocationsForDraftContent($contentId);
132
133
        $cacheItem->set($locations);
134
        $cacheTags = ['content-' . $contentId];
135
        foreach ($locations as $location) {
136
            $cacheTags = $this->getCacheTags($location, $cacheTags);
137
        }
138
        $cacheItem->tag($cacheTags);
139
        $this->cache->save($cacheItem);
140
141
        return $locations;
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147 View Code Duplication
    public function loadByRemoteId($remoteId, array $translations = null, bool $useAlwaysAvailable = true)
148
    {
149
        $translationsKey = $this->getCacheTranslationKey($translations, $useAlwaysAvailable);
150
        $cacheItem = $this->cache->getItem("ez-location-remoteid-${remoteId}-${translationsKey}");
151
        if ($cacheItem->isHit()) {
152
            return $cacheItem->get();
153
        }
154
155
        $this->logger->logCall(
156
            __METHOD__,
157
            ['location' => $remoteId, 'translations' => $translations, 'always-available' => $useAlwaysAvailable]
158
        );
159
        $location = $this->persistenceHandler->locationHandler()->loadByRemoteId($remoteId, $translations, $useAlwaysAvailable);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 147 can also be of type array; however, eZ\Publish\SPI\Persisten...ndler::loadByRemoteId() does only seem to accept null|array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
160
161
        $cacheItem->set($location);
162
        $cacheItem->tag($this->getCacheTags($location));
163
        $this->cache->save($cacheItem);
164
165
        return $location;
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 View Code Duplication
    public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null)
172
    {
173
        $this->logger->logCall(__METHOD__, array(
174
            'source' => $sourceId,
175
            'destination' => $destinationParentId,
176
            'newOwner' => $newOwnerId,
177
        ));
178
179
        return $this->persistenceHandler->locationHandler()->copySubtree($sourceId, $destinationParentId, $newOwnerId);
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185
    public function move($sourceId, $destinationParentId)
186
    {
187
        $this->logger->logCall(__METHOD__, array('source' => $sourceId, 'destination' => $destinationParentId));
188
        $return = $this->persistenceHandler->locationHandler()->move($sourceId, $destinationParentId);
189
190
        $this->cache->invalidateTags(['location-path-' . $sourceId]);
191
192
        return $return;
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function markSubtreeModified($locationId, $timestamp = null)
199
    {
200
        $this->logger->logCall(__METHOD__, array('location' => $locationId, 'time' => $timestamp));
201
        $this->persistenceHandler->locationHandler()->markSubtreeModified($locationId, $timestamp);
0 ignored issues
show
Deprecated Code introduced by
The method eZ\Publish\SPI\Persisten...::markSubtreeModified() has been deprecated with message: As of 6.8, not been used by repository since 5.x.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
202
    }
203
204
    /**
205
     * {@inheritdoc}
206
     */
207
    public function hide($locationId)
208
    {
209
        $this->logger->logCall(__METHOD__, array('location' => $locationId));
210
        $return = $this->persistenceHandler->locationHandler()->hide($locationId);
211
212
        $this->cache->invalidateTags(['location-path-data-' . $locationId]);
213
214
        return $return;
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function unHide($locationId)
221
    {
222
        $this->logger->logCall(__METHOD__, array('location' => $locationId));
223
        $return = $this->persistenceHandler->locationHandler()->unHide($locationId);
224
225
        $this->cache->invalidateTags(['location-path-data-' . $locationId]);
226
227
        return $return;
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233
    public function swap($locationId1, $locationId2)
234
    {
235
        $this->logger->logCall(__METHOD__, array('location1' => $locationId1, 'location2' => $locationId2));
236
        $locationHandler = $this->persistenceHandler->locationHandler();
237
238
        $return = $locationHandler->swap($locationId1, $locationId2);
239
240
        $this->cache->invalidateTags(
241
            [
242
                'location-' . $locationId1,
243
                'location-' . $locationId2,
244
            ]
245
        );
246
247
        return $return;
248
    }
249
250
    /**
251
     * {@inheritdoc}
252
     */
253
    public function update(UpdateStruct $struct, $locationId)
254
    {
255
        $this->logger->logCall(__METHOD__, array('location' => $locationId, 'struct' => $struct));
256
        $this->persistenceHandler->locationHandler()->update($struct, $locationId);
257
258
        $this->cache->invalidateTags(['location-data-' . $locationId]);
259
    }
260
261
    /**
262
     * {@inheritdoc}
263
     */
264
    public function create(CreateStruct $locationStruct)
265
    {
266
        $this->logger->logCall(__METHOD__, array('struct' => $locationStruct));
267
        $location = $this->persistenceHandler->locationHandler()->create($locationStruct);
268
269
        // need to clear loadLocationsByContent and similar collections involving locations data
270
        // also need to clear content info on main location changes
271
        $this->cache->invalidateTags(['content-' . $locationStruct->contentId, 'role-assignment-group-list-' . $locationStruct->contentId]);
272
273
        return $location;
274
    }
275
276
    /**
277
     * {@inheritdoc}
278
     */
279
    public function removeSubtree($locationId)
280
    {
281
        $this->logger->logCall(__METHOD__, array('location' => $locationId));
282
        $return = $this->persistenceHandler->locationHandler()->removeSubtree($locationId);
283
284
        $this->cache->invalidateTags(['location-path-' . $locationId]);
285
286
        return $return;
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292
    public function setSectionForSubtree($locationId, $sectionId)
293
    {
294
        $this->logger->logCall(__METHOD__, array('location' => $locationId, 'section' => $sectionId));
295
        $this->persistenceHandler->locationHandler()->setSectionForSubtree($locationId, $sectionId);
296
297
        $this->cache->invalidateTags(['location-path-' . $locationId]);
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303
    public function changeMainLocation($contentId, $locationId)
304
    {
305
        $this->logger->logCall(__METHOD__, array('location' => $locationId, 'content' => $contentId));
306
        $this->persistenceHandler->locationHandler()->changeMainLocation($contentId, $locationId);
307
308
        $this->cache->invalidateTags(['content-' . $contentId]);
309
    }
310
311
    /**
312
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
313
     *
314
     * @return int
315
     */
316
    public function countAllLocations()
317
    {
318
        $this->logger->logCall(__METHOD__);
319
320
        return $this->persistenceHandler->locationHandler()->countAllLocations();
321
    }
322
323
    /**
324
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
325
     *
326
     * @param int $offset
327
     * @param int $limit
328
     *
329
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
330
     */
331
    public function loadAllLocations($offset, $limit)
332
    {
333
        $this->logger->logCall(__METHOD__, array('offset' => $offset, 'limit' => $limit));
334
335
        return $this->persistenceHandler->locationHandler()->loadAllLocations($offset, $limit);
336
    }
337
338
    /**
339
     * Return relevant content and location tags so cache can be purged reliably.
340
     *
341
     * @param \eZ\Publish\SPI\Persistence\Content\Location $location
342
     * @param array $tags Optional, can be used to specify additional tags.
343
     *
344
     * @return array
345
     */
346
    private function getCacheTags(Location $location, $tags = [])
347
    {
348
        $tags[] = 'content-' . $location->contentId;
349
        $tags[] = 'location-' . $location->id;
350
        $tags[] = 'location-data-' . $location->id;
351
        foreach (explode('/', trim($location->pathString, '/')) as $pathId) {
352
            $tags[] = 'location-path-' . $pathId;
353
            $tags[] = 'location-path-data-' . $pathId;
354
        }
355
356
        return $tags;
357
    }
358
359
    private function getCacheTranslationKey(array $translations = null, bool $useAlwaysAvailable = true): string
360
    {
361
        if (empty($translations)) {
362
            return (int)$useAlwaysAvailable;
363
        }
364
365
        // Sort array as we don't care about order in location handler usage & want to optimize for cache hits.
366
        sort($translations);
367
368
        return implode('|', $translations) . '|' . (int)$useAlwaysAvailable;
369
    }
370
}
371