Completed
Push — EZEE-3159 ( 9c86bd )
by
unknown
19:29
created

LocationHandler::countAllLocations()   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 0
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 loadLocationsByTrashContent($contentId, $rootLocationId = null)
152
    {
153
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'root' => $rootLocationId]);
154
155
        return $this->persistenceHandler->locationHandler()->loadLocationsByTrashContent($contentId, $rootLocationId);
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function loadParentLocationsForDraftContent($contentId)
162
    {
163
        $cacheItem = $this->cache->getItem("ez-content-locations-${contentId}-parentForDraft");
164
        if ($cacheItem->isHit()) {
165
            $this->logger->logCacheHit(['content' => $contentId]);
166
167
            return $cacheItem->get();
168
        }
169
170
        $this->logger->logCacheMiss(['content' => $contentId]);
171
        $locations = $this->persistenceHandler->locationHandler()->loadParentLocationsForDraftContent($contentId);
172
173
        $cacheItem->set($locations);
174
        $cacheTags = ['content-' . $contentId];
175
        foreach ($locations as $location) {
176
            $cacheTags = $this->getCacheTags($location, $cacheTags);
177
        }
178
        $cacheItem->tag($cacheTags);
179
        $this->cache->save($cacheItem);
180
181
        return $locations;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 View Code Duplication
    public function loadByRemoteId($remoteId, array $translations = null, bool $useAlwaysAvailable = true)
188
    {
189
        $keySuffix = '-' . $this->getCacheTranslationKey($translations, $useAlwaysAvailable);
190
        $getLocationKeysFn = $this->getLocationKeys;
191
192
        return $this->getCacheValue(
193
            $this->escapeForCacheKey($remoteId),
194
            'ez-location-remoteid-',
195
            function () use ($remoteId, $translations, $useAlwaysAvailable) {
196
                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 187 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...
197
            },
198
            $this->getLocationTags,
199
            static function (Location $location) use ($keySuffix, $getLocationKeysFn) {
200
                return $getLocationKeysFn($location, $keySuffix);
201
            },
202
            $keySuffix,
203
            ['location' => $remoteId, 'translations' => $translations, 'alwaysAvailable' => $useAlwaysAvailable]
204
        );
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210 View Code Duplication
    public function copySubtree($sourceId, $destinationParentId, $newOwnerId = null)
211
    {
212
        $this->logger->logCall(__METHOD__, [
213
            'source' => $sourceId,
214
            'destination' => $destinationParentId,
215
            'newOwner' => $newOwnerId,
216
        ]);
217
218
        return $this->persistenceHandler->locationHandler()->copySubtree($sourceId, $destinationParentId, $newOwnerId);
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224
    public function move($sourceId, $destinationParentId)
225
    {
226
        $this->logger->logCall(__METHOD__, ['source' => $sourceId, 'destination' => $destinationParentId]);
227
        $return = $this->persistenceHandler->locationHandler()->move($sourceId, $destinationParentId);
228
229
        $this->cache->invalidateTags(['location-path-' . $sourceId]);
230
231
        return $return;
232
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237
    public function markSubtreeModified($locationId, $timestamp = null)
238
    {
239
        $this->logger->logCall(__METHOD__, ['location' => $locationId, 'time' => $timestamp]);
240
        $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...
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246
    public function hide($locationId)
247
    {
248
        $this->logger->logCall(__METHOD__, ['location' => $locationId]);
249
        $return = $this->persistenceHandler->locationHandler()->hide($locationId);
250
251
        $this->cache->invalidateTags(['location-path-' . $locationId]);
252
253
        return $return;
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259
    public function unHide($locationId)
260
    {
261
        $this->logger->logCall(__METHOD__, ['location' => $locationId]);
262
        $return = $this->persistenceHandler->locationHandler()->unHide($locationId);
263
264
        $this->cache->invalidateTags(['location-path-' . $locationId]);
265
266
        return $return;
267
    }
268
269
    /**
270
     * Sets a location + all children to invisible.
271
     *
272
     * @param int $id Location ID
273
     */
274
    public function setInvisible(int $id): void
275
    {
276
        $this->logger->logCall(__METHOD__, ['location' => $id]);
277
        $this->persistenceHandler->locationHandler()->setInvisible($id);
278
279
        $this->cache->invalidateTags(['location-path-' . $id]);
280
    }
281
282
    /**
283
     * Sets a location + all children to visible.
284
     *
285
     * @param int $id Location ID
286
     */
287
    public function setVisible(int $id): void
288
    {
289
        $this->logger->logCall(__METHOD__, ['location' => $id]);
290
        $this->persistenceHandler->locationHandler()->setVisible($id);
291
292
        $this->cache->invalidateTags(['location-path-' . $id]);
293
    }
294
295
    /**
296
     * {@inheritdoc}
297
     */
298 View Code Duplication
    public function swap($locationId1, $locationId2)
299
    {
300
        $this->logger->logCall(__METHOD__, ['location1' => $locationId1, 'location2' => $locationId2]);
301
        $locationHandler = $this->persistenceHandler->locationHandler();
302
303
        $return = $locationHandler->swap($locationId1, $locationId2);
304
305
        $this->cache->invalidateTags(
306
            [
307
                'location-' . $locationId1,
308
                'location-' . $locationId2,
309
            ]
310
        );
311
312
        return $return;
313
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318 View Code Duplication
    public function update(UpdateStruct $struct, $locationId)
319
    {
320
        $this->logger->logCall(__METHOD__, ['location' => $locationId, 'struct' => $struct]);
321
        $this->persistenceHandler->locationHandler()->update($struct, $locationId);
322
323
        $this->cache->invalidateTags(['location-' . $locationId]);
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     */
329 View Code Duplication
    public function create(CreateStruct $locationStruct)
330
    {
331
        $this->logger->logCall(__METHOD__, ['struct' => $locationStruct]);
332
        $location = $this->persistenceHandler->locationHandler()->create($locationStruct);
333
334
        // need to clear loadLocationsByContent and similar collections involving locations data
335
        // also need to clear content info on main location changes
336
        $this->cache->invalidateTags(['content-' . $locationStruct->contentId, 'role-assignment-group-list-' . $locationStruct->contentId]);
337
338
        return $location;
339
    }
340
341
    /**
342
     * {@inheritdoc}
343
     */
344
    public function removeSubtree($locationId)
345
    {
346
        $this->logger->logCall(__METHOD__, ['location' => $locationId]);
347
        $return = $this->persistenceHandler->locationHandler()->removeSubtree($locationId);
348
349
        $this->cache->invalidateTags(['location-path-' . $locationId]);
350
351
        return $return;
352
    }
353
354
    /**
355
     * {@inheritdoc}
356
     */
357 View Code Duplication
    public function setSectionForSubtree($locationId, $sectionId)
358
    {
359
        $this->logger->logCall(__METHOD__, ['location' => $locationId, 'section' => $sectionId]);
360
        $this->persistenceHandler->locationHandler()->setSectionForSubtree($locationId, $sectionId);
361
362
        $this->cache->invalidateTags(['location-path-' . $locationId]);
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368 View Code Duplication
    public function changeMainLocation($contentId, $locationId)
369
    {
370
        $this->logger->logCall(__METHOD__, ['location' => $locationId, 'content' => $contentId]);
371
        $this->persistenceHandler->locationHandler()->changeMainLocation($contentId, $locationId);
372
373
        $this->cache->invalidateTags(['content-' . $contentId]);
374
    }
375
376
    /**
377
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
378
     *
379
     * @return int
380
     */
381
    public function countAllLocations()
382
    {
383
        $this->logger->logCall(__METHOD__);
384
385
        return $this->persistenceHandler->locationHandler()->countAllLocations();
386
    }
387
388
    /**
389
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
390
     *
391
     * @param int $offset
392
     * @param int $limit
393
     *
394
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
395
     */
396
    public function loadAllLocations($offset, $limit)
397
    {
398
        $this->logger->logCall(__METHOD__, ['offset' => $offset, 'limit' => $limit]);
399
400
        return $this->persistenceHandler->locationHandler()->loadAllLocations($offset, $limit);
401
    }
402
403
    /**
404
     * Return relevant content and location tags so cache can be purged reliably.
405
     *
406
     * @param \eZ\Publish\SPI\Persistence\Content\Location $location
407
     * @param array $tags Optional, can be used to specify additional tags.
408
     *
409
     * @return array
410
     */
411
    private function getCacheTags(Location $location, $tags = [])
412
    {
413
        $tags[] = 'content-' . $location->contentId;
414
        $tags[] = 'location-' . $location->id;
415
        foreach (explode('/', trim($location->pathString, '/')) as $pathId) {
416
            $tags[] = 'location-path-' . $pathId;
417
        }
418
419
        return $tags;
420
    }
421
422
    private function getCacheTranslationKey(array $translations = null, bool $useAlwaysAvailable = true): string
423
    {
424
        if (empty($translations)) {
425
            return (int)$useAlwaysAvailable;
426
        }
427
428
        // Sort array as we don't care about order in location handler usage & want to optimize for cache hits.
429
        sort($translations);
430
431
        return implode('|', $translations) . '|' . (int)$useAlwaysAvailable;
432
    }
433
}
434