Completed
Push — master ( 5acaba...f6e111 )
by Łukasz
31:51
created

LocationHandler::update()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 7
rs 10
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
     * Sets a location + all children to invisible.
232
     *
233
     * @param int $id Location ID
234
     */
235 View Code Duplication
    public function setInvisible(int $id): void
236
    {
237
        $this->logger->logCall(__METHOD__, ['location' => $id]);
238
        $this->persistenceHandler->locationHandler()->setInvisible($id);
239
240
        $this->cache->invalidateTags(['location-path-data-' . $id]);
241
    }
242
243
    /**
244
     * Sets a location + all children to visible.
245
     *
246
     * @param int $id Location ID
247
     */
248 View Code Duplication
    public function setVisible(int $id): void
249
    {
250
        $this->logger->logCall(__METHOD__, ['location' => $id]);
251
        $this->persistenceHandler->locationHandler()->setVisible($id);
252
253
        $this->cache->invalidateTags(['location-path-data-' . $id]);
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259
    public function swap($locationId1, $locationId2)
260
    {
261
        $this->logger->logCall(__METHOD__, array('location1' => $locationId1, 'location2' => $locationId2));
262
        $locationHandler = $this->persistenceHandler->locationHandler();
263
264
        $return = $locationHandler->swap($locationId1, $locationId2);
265
266
        $this->cache->invalidateTags(
267
            [
268
                'location-' . $locationId1,
269
                'location-' . $locationId2,
270
            ]
271
        );
272
273
        return $return;
274
    }
275
276
    /**
277
     * {@inheritdoc}
278
     */
279
    public function update(UpdateStruct $struct, $locationId)
280
    {
281
        $this->logger->logCall(__METHOD__, array('location' => $locationId, 'struct' => $struct));
282
        $this->persistenceHandler->locationHandler()->update($struct, $locationId);
283
284
        $this->cache->invalidateTags(['location-data-' . $locationId]);
285
    }
286
287
    /**
288
     * {@inheritdoc}
289
     */
290
    public function create(CreateStruct $locationStruct)
291
    {
292
        $this->logger->logCall(__METHOD__, array('struct' => $locationStruct));
293
        $location = $this->persistenceHandler->locationHandler()->create($locationStruct);
294
295
        // need to clear loadLocationsByContent and similar collections involving locations data
296
        // also need to clear content info on main location changes
297
        $this->cache->invalidateTags(['content-' . $locationStruct->contentId, 'role-assignment-group-list-' . $locationStruct->contentId]);
298
299
        return $location;
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    public function removeSubtree($locationId)
306
    {
307
        $this->logger->logCall(__METHOD__, array('location' => $locationId));
308
        $return = $this->persistenceHandler->locationHandler()->removeSubtree($locationId);
309
310
        $this->cache->invalidateTags(['location-path-' . $locationId]);
311
312
        return $return;
313
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318
    public function setSectionForSubtree($locationId, $sectionId)
319
    {
320
        $this->logger->logCall(__METHOD__, array('location' => $locationId, 'section' => $sectionId));
321
        $this->persistenceHandler->locationHandler()->setSectionForSubtree($locationId, $sectionId);
322
323
        $this->cache->invalidateTags(['location-path-' . $locationId]);
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     */
329
    public function changeMainLocation($contentId, $locationId)
330
    {
331
        $this->logger->logCall(__METHOD__, array('location' => $locationId, 'content' => $contentId));
332
        $this->persistenceHandler->locationHandler()->changeMainLocation($contentId, $locationId);
333
334
        $this->cache->invalidateTags(['content-' . $contentId]);
335
    }
336
337
    /**
338
     * Get the total number of all existing Locations. Can be combined with loadAllLocations.
339
     *
340
     * @return int
341
     */
342
    public function countAllLocations()
343
    {
344
        $this->logger->logCall(__METHOD__);
345
346
        return $this->persistenceHandler->locationHandler()->countAllLocations();
347
    }
348
349
    /**
350
     * Bulk-load all existing Locations, constrained by $limit and $offset to paginate results.
351
     *
352
     * @param int $offset
353
     * @param int $limit
354
     *
355
     * @return \eZ\Publish\SPI\Persistence\Content\Location[]
356
     */
357
    public function loadAllLocations($offset, $limit)
358
    {
359
        $this->logger->logCall(__METHOD__, array('offset' => $offset, 'limit' => $limit));
360
361
        return $this->persistenceHandler->locationHandler()->loadAllLocations($offset, $limit);
362
    }
363
364
    /**
365
     * Return relevant content and location tags so cache can be purged reliably.
366
     *
367
     * @param \eZ\Publish\SPI\Persistence\Content\Location $location
368
     * @param array $tags Optional, can be used to specify additional tags.
369
     *
370
     * @return array
371
     */
372
    private function getCacheTags(Location $location, $tags = [])
373
    {
374
        $tags[] = 'content-' . $location->contentId;
375
        $tags[] = 'location-' . $location->id;
376
        $tags[] = 'location-data-' . $location->id;
377
        foreach (explode('/', trim($location->pathString, '/')) as $pathId) {
378
            $tags[] = 'location-path-' . $pathId;
379
            $tags[] = 'location-path-data-' . $pathId;
380
        }
381
382
        return $tags;
383
    }
384
385
    private function getCacheTranslationKey(array $translations = null, bool $useAlwaysAvailable = true): string
386
    {
387
        if (empty($translations)) {
388
            return (int)$useAlwaysAvailable;
389
        }
390
391
        // Sort array as we don't care about order in location handler usage & want to optimize for cache hits.
392
        sort($translations);
393
394
        return implode('|', $translations) . '|' . (int)$useAlwaysAvailable;
395
    }
396
}
397