Completed
Push — EZEE-3159 ( 56bba0...79049c )
by
unknown
18:48
created

LocationHandler::loadLocationsByTrashContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
namespace eZ\Publish\Core\Persistence\Cache;
8
9
use eZ\Publish\SPI\Persistence\Content\Location\Handler as LocationHandlerInterface;
10
use eZ\Publish\SPI\Persistence\Content\Location\CreateStruct;
11
use eZ\Publish\SPI\Persistence\Content\Location\UpdateStruct;
12
use eZ\Publish\SPI\Persistence\Content\Location;
13
14
/**
15
 * @see \eZ\Publish\SPI\Persistence\Content\Location\Handler
16
 */
17
class LocationHandler extends AbstractInMemoryPersistenceHandler implements LocationHandlerInterface
18
{
19
    /** @var callable */
20
    private $getLocationTags;
21
22
    /** @var callable */
23
    private $getLocationKeys;
24
25
    protected function init(): void
26
    {
27
        $this->getLocationTags = static function (Location $location) {
28
            $tags = [
29
                 'content-' . $location->contentId,
30
                 'location-' . $location->id,
31
             ];
32
            foreach (\explode('/', \trim($location->pathString, '/')) as $pathId) {
33
                $tags[] = 'location-path-' . $pathId;
34
            }
35
36
            return $tags;
37
        };
38
        $this->getLocationKeys = function (Location $location, $keySuffix = '-1') {
39
            return [
40
                 'ez-location-' . $location->id . $keySuffix,
41
                 'ez-location-remoteid-' . $this->escapeForCacheKey($location->remoteId) . $keySuffix,
42
             ];
43
        };
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49 View Code Duplication
    public function load($locationId, array $translations = null, bool $useAlwaysAvailable = true)
50
    {
51
        $keySuffix = '-' . $this->getCacheTranslationKey($translations, $useAlwaysAvailable);
52
        $getLocationKeysFn = $this->getLocationKeys;
53
54
        return $this->getCacheValue(
55
            (int) $locationId,
56
            'ez-location-',
57
            function ($id) use ($translations, $useAlwaysAvailable) {
58
                return $this->persistenceHandler->locationHandler()->load($id, $translations, $useAlwaysAvailable);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 49 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...
59
            },
60
            $this->getLocationTags,
61
            static function (Location $location) use ($keySuffix, $getLocationKeysFn) {
62
                return $getLocationKeysFn($location, $keySuffix);
63
            },
64
            $keySuffix,
65
            ['location' => $locationId, 'translations' => $translations, 'alwaysAvailable' => $useAlwaysAvailable]
66
        );
67
    }
68
69 View Code Duplication
    public function loadList(array $locationIds, array $translations = null, bool $useAlwaysAvailable = true): iterable
70
    {
71
        $keySuffix = '-' . $this->getCacheTranslationKey($translations, $useAlwaysAvailable);
72
        $getLocationKeysFn = $this->getLocationKeys;
73
74
        return $this->getMultipleCacheValues(
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...
75
            $locationIds,
76
            'ez-location-',
77
            function (array $ids) use ($translations, $useAlwaysAvailable) {
78
                return $this->persistenceHandler->locationHandler()->loadList($ids, $translations, $useAlwaysAvailable);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 69 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...
79
            },
80
            $this->getLocationTags,
81
            static function (Location $location) use ($keySuffix, $getLocationKeysFn) {
82
                return $getLocationKeysFn($location, $keySuffix);
83
            },
84
            $keySuffix,
85
            ['location' => $locationIds, 'translations' => $translations, 'alwaysAvailable' => $useAlwaysAvailable]
86
        );
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function loadSubtreeIds($locationId)
93
    {
94
        $cacheItem = $this->cache->getItem("ez-location-subtree-${locationId}");
95
        if ($cacheItem->isHit()) {
96
            $this->logger->logCacheHit(['location' => $locationId]);
97
98
            return $cacheItem->get();
99
        }
100
101
        $this->logger->logCacheMiss(['location' => $locationId]);
102
        $locationIds = $this->persistenceHandler->locationHandler()->loadSubtreeIds($locationId);
103
104
        $cacheItem->set($locationIds);
105
        $cacheTags = ['location-' . $locationId, 'location-path-' . $locationId];
106
        foreach ($locationIds as $id) {
107
            $cacheTags[] = 'location-' . $id;
108
            $cacheTags[] = 'location-path-' . $id;
109
        }
110
        $cacheItem->tag($cacheTags);
111
        $this->cache->save($cacheItem);
112
113
        return $locationIds;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function loadLocationsByContent($contentId, $rootLocationId = null)
120
    {
121
        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...
122
            $cacheItem = $this->cache->getItem("ez-content-locations-${contentId}-root-${rootLocationId}");
123
            $cacheTags = ['content-' . $contentId, 'location-' . $rootLocationId, 'location-path-' . $rootLocationId];
124
        } else {
125
            $cacheItem = $this->cache->getItem("ez-content-locations-${contentId}");
126
            $cacheTags = ['content-' . $contentId];
127
        }
128
129
        if ($cacheItem->isHit()) {
130
            $this->logger->logCacheHit(['content' => $contentId, 'root' => $rootLocationId]);
131
132
            return $cacheItem->get();
133
        }
134
135
        $this->logger->logCacheMiss(['content' => $contentId, 'root' => $rootLocationId]);
136
        $locations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentId, $rootLocationId);
137
138
        $cacheItem->set($locations);
139
        foreach ($locations as $location) {
140
            $cacheTags = $this->getCacheTags($location, $cacheTags);
141
        }
142
        $cacheItem->tag($cacheTags);
143
        $this->cache->save($cacheItem);
144
145
        return $locations;
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function loadParentLocationsForDraftContent($contentId)
152
    {
153
        $cacheItem = $this->cache->getItem("ez-content-locations-${contentId}-parentForDraft");
154
        if ($cacheItem->isHit()) {
155
            $this->logger->logCacheHit(['content' => $contentId]);
156
157
            return $cacheItem->get();
158
        }
159
160
        $this->logger->logCacheMiss(['content' => $contentId]);
161
        $locations = $this->persistenceHandler->locationHandler()->loadParentLocationsForDraftContent($contentId);
162
163
        $cacheItem->set($locations);
164
        $cacheTags = ['content-' . $contentId];
165
        foreach ($locations as $location) {
166
            $cacheTags = $this->getCacheTags($location, $cacheTags);
167
        }
168
        $cacheItem->tag($cacheTags);
169
        $this->cache->save($cacheItem);
170
171
        return $locations;
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177 View Code Duplication
    public function loadByRemoteId($remoteId, array $translations = null, bool $useAlwaysAvailable = true)
178
    {
179
        $keySuffix = '-' . $this->getCacheTranslationKey($translations, $useAlwaysAvailable);
180
        $getLocationKeysFn = $this->getLocationKeys;
181
182
        return $this->getCacheValue(
183
            $this->escapeForCacheKey($remoteId),
184
            'ez-location-remoteid-',
185
            function () use ($remoteId, $translations, $useAlwaysAvailable) {
186
                return $this->persistenceHandler->locationHandler()->loadByRemoteId($remoteId, $translations, $useAlwaysAvailable);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 177 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...
187
            },
188
            $this->getLocationTags,
189
            static function (Location $location) use ($keySuffix, $getLocationKeysFn) {
190
                return $getLocationKeysFn($location, $keySuffix);
191
            },
192
            $keySuffix,
193
            ['location' => $remoteId, 'translations' => $translations, 'alwaysAvailable' => $useAlwaysAvailable]
194
        );
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 View Code Duplication
    public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null)
201
    {
202
        $this->logger->logCall(__METHOD__, [
203
            'source' => $sourceId,
204
            'destination' => $destinationParentId,
205
            'newOwner' => $newOwnerId,
206
        ]);
207
208
        return $this->persistenceHandler->locationHandler()->copySubtree($sourceId, $destinationParentId, $newOwnerId);
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function move($sourceId, $destinationParentId)
215
    {
216
        $this->logger->logCall(__METHOD__, ['source' => $sourceId, 'destination' => $destinationParentId]);
217
        $return = $this->persistenceHandler->locationHandler()->move($sourceId, $destinationParentId);
218
219
        $this->cache->invalidateTags(['location-path-' . $sourceId]);
220
221
        return $return;
222
    }
223
224
    /**
225
     * {@inheritdoc}
226
     */
227
    public function markSubtreeModified($locationId, $timestamp = null)
228
    {
229
        $this->logger->logCall(__METHOD__, ['location' => $locationId, 'time' => $timestamp]);
230
        $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...
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    public function hide($locationId)
237
    {
238
        $this->logger->logCall(__METHOD__, ['location' => $locationId]);
239
        $return = $this->persistenceHandler->locationHandler()->hide($locationId);
240
241
        $this->cache->invalidateTags(['location-path-' . $locationId]);
242
243
        return $return;
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function unHide($locationId)
250
    {
251
        $this->logger->logCall(__METHOD__, ['location' => $locationId]);
252
        $return = $this->persistenceHandler->locationHandler()->unHide($locationId);
253
254
        $this->cache->invalidateTags(['location-path-' . $locationId]);
255
256
        return $return;
257
    }
258
259
    /**
260
     * Sets a location + all children to invisible.
261
     *
262
     * @param int $id Location ID
263
     */
264
    public function setInvisible(int $id): void
265
    {
266
        $this->logger->logCall(__METHOD__, ['location' => $id]);
267
        $this->persistenceHandler->locationHandler()->setInvisible($id);
268
269
        $this->cache->invalidateTags(['location-path-' . $id]);
270
    }
271
272
    /**
273
     * Sets a location + all children to visible.
274
     *
275
     * @param int $id Location ID
276
     */
277
    public function setVisible(int $id): void
278
    {
279
        $this->logger->logCall(__METHOD__, ['location' => $id]);
280
        $this->persistenceHandler->locationHandler()->setVisible($id);
281
282
        $this->cache->invalidateTags(['location-path-' . $id]);
283
    }
284
285
    /**
286
     * {@inheritdoc}
287
     */
288 View Code Duplication
    public function swap($locationId1, $locationId2)
289
    {
290
        $this->logger->logCall(__METHOD__, ['location1' => $locationId1, 'location2' => $locationId2]);
291
        $locationHandler = $this->persistenceHandler->locationHandler();
292
293
        $return = $locationHandler->swap($locationId1, $locationId2);
294
295
        $this->cache->invalidateTags(
296
            [
297
                'location-' . $locationId1,
298
                'location-' . $locationId2,
299
            ]
300
        );
301
302
        return $return;
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308 View Code Duplication
    public function update(UpdateStruct $struct, $locationId)
309
    {
310
        $this->logger->logCall(__METHOD__, ['location' => $locationId, 'struct' => $struct]);
311
        $this->persistenceHandler->locationHandler()->update($struct, $locationId);
312
313
        $this->cache->invalidateTags(['location-' . $locationId]);
314
    }
315
316
    /**
317
     * {@inheritdoc}
318
     */
319 View Code Duplication
    public function create(CreateStruct $locationStruct)
320
    {
321
        $this->logger->logCall(__METHOD__, ['struct' => $locationStruct]);
322
        $location = $this->persistenceHandler->locationHandler()->create($locationStruct);
323
324
        // need to clear loadLocationsByContent and similar collections involving locations data
325
        // also need to clear content info on main location changes
326
        $this->cache->invalidateTags(['content-' . $locationStruct->contentId, 'role-assignment-group-list-' . $locationStruct->contentId]);
327
328
        return $location;
329
    }
330
331
    /**
332
     * {@inheritdoc}
333
     */
334
    public function removeSubtree($locationId)
335
    {
336
        $this->logger->logCall(__METHOD__, ['location' => $locationId]);
337
        $return = $this->persistenceHandler->locationHandler()->removeSubtree($locationId);
338
339
        $this->cache->invalidateTags(['location-path-' . $locationId]);
340
341
        return $return;
342
    }
343
344
    /**
345
     * {@inheritdoc}
346
     */
347 View Code Duplication
    public function setSectionForSubtree($locationId, $sectionId)
348
    {
349
        $this->logger->logCall(__METHOD__, ['location' => $locationId, 'section' => $sectionId]);
350
        $this->persistenceHandler->locationHandler()->setSectionForSubtree($locationId, $sectionId);
351
352
        $this->cache->invalidateTags(['location-path-' . $locationId]);
353
    }
354
355
    /**
356
     * {@inheritdoc}
357
     */
358 View Code Duplication
    public function changeMainLocation($contentId, $locationId)
359
    {
360
        $this->logger->logCall(__METHOD__, ['location' => $locationId, 'content' => $contentId]);
361
        $this->persistenceHandler->locationHandler()->changeMainLocation($contentId, $locationId);
362
363
        $this->cache->invalidateTags(['content-' . $contentId]);
364
    }
365
366
    /**
367
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
368
     *
369
     * @return int
370
     */
371
    public function countAllLocations()
372
    {
373
        $this->logger->logCall(__METHOD__);
374
375
        return $this->persistenceHandler->locationHandler()->countAllLocations();
376
    }
377
378
    /**
379
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
380
     *
381
     * @param int $offset
382
     * @param int $limit
383
     *
384
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
385
     */
386
    public function loadAllLocations($offset, $limit)
387
    {
388
        $this->logger->logCall(__METHOD__, ['offset' => $offset, 'limit' => $limit]);
389
390
        return $this->persistenceHandler->locationHandler()->loadAllLocations($offset, $limit);
391
    }
392
393
    /**
394
     * Return relevant content and location tags so cache can be purged reliably.
395
     *
396
     * @param \eZ\Publish\SPI\Persistence\Content\Location $location
397
     * @param array $tags Optional, can be used to specify additional tags.
398
     *
399
     * @return array
400
     */
401
    private function getCacheTags(Location $location, $tags = [])
402
    {
403
        $tags[] = 'content-' . $location->contentId;
404
        $tags[] = 'location-' . $location->id;
405
        foreach (explode('/', trim($location->pathString, '/')) as $pathId) {
406
            $tags[] = 'location-path-' . $pathId;
407
        }
408
409
        return $tags;
410
    }
411
412
    private function getCacheTranslationKey(array $translations = null, bool $useAlwaysAvailable = true): string
413
    {
414
        if (empty($translations)) {
415
            return (int)$useAlwaysAvailable;
416
        }
417
418
        // Sort array as we don't care about order in location handler usage & want to optimize for cache hits.
419
        sort($translations);
420
421
        return implode('|', $translations) . '|' . (int)$useAlwaysAvailable;
422
    }
423
}
424