Completed
Push — spi_log_cache_hits ( 876d84...ef4861 )
by André
39:52
created

ContentHandler   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 443
Duplicated Lines 10.61 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 47
loc 443
rs 9.0399
c 0
b 0
f 0
wmc 42
lcom 1
cbo 11

27 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 7 1
A createDraftFromVersion() 0 8 1
A getCacheTagsForVersion() 0 7 1
A getCacheTagsForContent() 0 10 1
A copy() 0 10 1
A load() 0 21 3
A loadContentList() 0 25 3
A loadContentInfo() 0 17 2
A loadContentInfoList() 18 18 1
A loadContentInfoByRemoteId() 0 17 2
A loadVersionInfo() 0 17 2
A loadDraftsForUser() 0 6 1
A setStatus() 0 13 2
A updateMetadata() 0 8 1
A updateContent() 0 8 1
A deleteContent() 0 27 2
A deleteVersion() 0 8 1
A listVersions() 0 21 4
A addRelation() 0 6 1
A removeRelation() 0 5 1
A loadRelations() 13 13 1
A loadReverseRelations() 0 6 1
A publish() 0 8 1
A removeTranslationFromContent() 0 4 1
A deleteTranslationFromContent() 0 13 1
A deleteTranslationFromDraft() 0 15 1
A getCacheTags() 16 16 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ContentHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ContentHandler, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * File containing the ContentHandler 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\API\Repository\Values\Content\Relation as APIRelation;
12
use eZ\Publish\SPI\Persistence\Content\Handler as ContentHandlerInterface;
13
use eZ\Publish\SPI\Persistence\Content;
14
use eZ\Publish\SPI\Persistence\Content\VersionInfo;
15
use eZ\Publish\SPI\Persistence\Content\ContentInfo;
16
use eZ\Publish\SPI\Persistence\Content\CreateStruct;
17
use eZ\Publish\SPI\Persistence\Content\UpdateStruct;
18
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct;
19
use eZ\Publish\SPI\Persistence\Content\Relation\CreateStruct as RelationCreateStruct;
20
21
/**
22
 * @see \eZ\Publish\SPI\Persistence\Content\Handler
23
 */
24
class ContentHandler extends AbstractHandler implements ContentHandlerInterface
25
{
26
    const ALL_TRANSLATIONS_KEY = '0';
27
28
    /**
29
     * {@inheritdoc}
30
     */
31
    public function create(CreateStruct $struct)
32
    {
33
        // Cached on demand when published or loaded
34
        $this->logger->logCall(__METHOD__, array('struct' => $struct));
35
36
        return $this->persistenceHandler->contentHandler()->create($struct);
37
    }
38
39
    /**
40
     * {@inheritdoc}
41
     */
42
    public function createDraftFromVersion($contentId, $srcVersion, $userId)
43
    {
44
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'version' => $srcVersion, 'user' => $userId));
45
        $draft = $this->persistenceHandler->contentHandler()->createDraftFromVersion($contentId, $srcVersion, $userId);
46
        $this->cache->invalidateTags(["content-{$contentId}-version-list"]);
47
48
        return $draft;
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function copy($contentId, $versionNo = null, $newOwnerId = null)
55
    {
56
        $this->logger->logCall(__METHOD__, array(
57
            'content' => $contentId,
58
            'version' => $versionNo,
59
            'newOwner' => $newOwnerId,
60
        ));
61
62
        return $this->persistenceHandler->contentHandler()->copy($contentId, $versionNo, $newOwnerId);
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     *
68
     * @todo Add support for setting version number to null in order to reuse cache with loadContentList.
69
     */
70
    public function load($contentId, $versionNo, array $translations = null)
71
    {
72
        $translationsKey = empty($translations) ? self::ALL_TRANSLATIONS_KEY : implode('|', $translations);
73
        $cacheItem = $this->cache->getItem("ez-content-${contentId}-${versionNo}-${translationsKey}");
74
        if ($cacheItem->isHit()) {
75
            $this->logger->logCacheHit(
76
                __METHOD__,
77
                ['content' => $contentId, 'version' => $versionNo, 'translations' => $translations]
78
            );
79
80
            return $cacheItem->get();
81
        }
82
83
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'version' => $versionNo, 'translations' => $translations));
84
        $content = $this->persistenceHandler->contentHandler()->load($contentId, $versionNo, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 70 can also be of type array; however, eZ\Publish\SPI\Persistence\Content\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...
85
        $cacheItem->set($content);
86
        $cacheItem->tag($this->getCacheTagsForContent($content));
87
        $this->cache->save($cacheItem);
88
89
        return $content;
90
    }
91
92
    public function loadContentList(array $contentIds, array $translations = null): array
93
    {
94
        // Extract suffix for each one
95
        $keySuffixes = [];
96
        foreach ($contentIds as $contentId) {
97
            $keySuffixes[$contentId] = '-' . (empty($translations) ? self::ALL_TRANSLATIONS_KEY : implode('|', $translations));
98
        }
99
100
        return $this->getMultipleCacheItems(
101
            $contentIds,
102
            'ez-content-',
103
            function (array $cacheMissIds) use ($translations) {
104
                $this->logger->logCall(__CLASS__ . '::loadContentList', ['content' => $cacheMissIds]);
105
106
                return $this->persistenceHandler->contentHandler()->loadContentList($cacheMissIds, $translations);
0 ignored issues
show
Bug introduced by
It seems like $translations defined by parameter $translations on line 92 can also be of type array; however, eZ\Publish\SPI\Persisten...dler::loadContentList() 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...
107
            },
108
            function (Content $content) {
109
                return $this->getCacheTagsForContent($content);
110
            },
111
            function () use ($contentIds) {
112
                $this->logger->logCacheHit(__CLASS__ . '::loadContentList', ['content' => $contentIds]);
113
            },
114
            $keySuffixes
115
        );
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function loadContentInfo($contentId)
122
    {
123
        $cacheItem = $this->cache->getItem("ez-content-info-${contentId}");
124
        if ($cacheItem->isHit()) {
125
            $this->logger->logCacheHit(__METHOD__, ['content' => $contentId]);
126
127
            return $cacheItem->get();
128
        }
129
130
        $this->logger->logCall(__METHOD__, array('content' => $contentId));
131
        $contentInfo = $this->persistenceHandler->contentHandler()->loadContentInfo($contentId);
132
        $cacheItem->set($contentInfo);
133
        $cacheItem->tag($this->getCacheTags($contentInfo));
134
        $this->cache->save($cacheItem);
135
136
        return $contentInfo;
137
    }
138
139 View Code Duplication
    public function loadContentInfoList(array $contentIds)
140
    {
141
        return $this->getMultipleCacheItems(
142
            $contentIds,
143
            'ez-content-info-',
144
            function (array $cacheMissIds) {
145
                $this->logger->logCall(__CLASS__ . '::loadContentInfoList', ['content' => $cacheMissIds]);
146
147
                return $this->persistenceHandler->contentHandler()->loadContentInfoList($cacheMissIds);
148
            },
149
            function (ContentInfo $info) {
150
                return $this->getCacheTags($info);
151
            },
152
            function () use ($contentIds) {
153
                $this->logger->logCacheHit(__CLASS__ . '::loadContentInfoList', ['content' => $contentIds]);
154
            }
155
        );
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function loadContentInfoByRemoteId($remoteId)
162
    {
163
        $cacheItem = $this->cache->getItem("ez-content-info-byRemoteId-${remoteId}");
164
        if ($cacheItem->isHit()) {
165
            $this->logger->logCacheHit(__METHOD__, ['content' => $remoteId]);
166
167
            return $cacheItem->get();
168
        }
169
170
        $this->logger->logCall(__METHOD__, array('content' => $remoteId));
171
        $contentInfo = $this->persistenceHandler->contentHandler()->loadContentInfoByRemoteId($remoteId);
172
        $cacheItem->set($contentInfo);
173
        $cacheItem->tag($this->getCacheTags($contentInfo));
174
        $this->cache->save($cacheItem);
175
176
        return $contentInfo;
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182
    public function loadVersionInfo($contentId, $versionNo)
183
    {
184
        $cacheItem = $this->cache->getItem("ez-content-version-info-${contentId}-${versionNo}");
185
        if ($cacheItem->isHit()) {
186
            $this->logger->logCacheHit(__METHOD__, ['content' => $contentId, 'version' => $versionNo]);
187
188
            return $cacheItem->get();
189
        }
190
191
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'version' => $versionNo]);
192
        $versionInfo = $this->persistenceHandler->contentHandler()->loadVersionInfo($contentId, $versionNo);
193
        $cacheItem->set($versionInfo);
194
        $cacheItem->tag($this->getCacheTagsForVersion($versionInfo));
195
        $this->cache->save($cacheItem);
196
197
        return $versionInfo;
198
    }
199
200
    /**
201
     * {@inheritdoc}
202
     */
203
    public function loadDraftsForUser($userId)
204
    {
205
        $this->logger->logCall(__METHOD__, array('user' => $userId));
206
207
        return $this->persistenceHandler->contentHandler()->loadDraftsForUser($userId);
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213
    public function setStatus($contentId, $status, $versionNo)
214
    {
215
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'status' => $status, 'version' => $versionNo));
216
        $return = $this->persistenceHandler->contentHandler()->setStatus($contentId, $status, $versionNo);
217
218
        if ($status === VersionInfo::STATUS_PUBLISHED) {
219
            $this->cache->invalidateTags(['content-' . $contentId]);
220
        } else {
221
            $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
222
        }
223
224
        return $return;
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function updateMetadata($contentId, MetadataUpdateStruct $struct)
231
    {
232
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'struct' => $struct));
233
        $contentInfo = $this->persistenceHandler->contentHandler()->updateMetadata($contentId, $struct);
234
        $this->cache->invalidateTags(['content-' . $contentId]);
235
236
        return $contentInfo;
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     */
242
    public function updateContent($contentId, $versionNo, UpdateStruct $struct)
243
    {
244
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'version' => $versionNo, 'struct' => $struct));
245
        $content = $this->persistenceHandler->contentHandler()->updateContent($contentId, $versionNo, $struct);
246
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
247
248
        return $content;
249
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254
    public function deleteContent($contentId)
255
    {
256
        $this->logger->logCall(__METHOD__, array('content' => $contentId));
257
258
        // Load reverse field relations first
259
        $reverseRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
260
            $contentId,
261
            APIRelation::FIELD | APIRelation::ASSET
262
        );
263
264
        $return = $this->persistenceHandler->contentHandler()->deleteContent($contentId);
265
266
        $this->cache->invalidateTags(['content-' . $contentId]);
267
        if (!empty($reverseRelations)) {
268
            $this->cache->invalidateTags(
269
                array_map(
270
                    function ($relation) {
271
                        // only the full content object *with* fields is affected by this
272
                        return 'content-fields-' . $relation->sourceContentId;
273
                    },
274
                    $reverseRelations
275
                )
276
            );
277
        }
278
279
        return $return;
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    public function deleteVersion($contentId, $versionNo)
286
    {
287
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'version' => $versionNo));
288
        $return = $this->persistenceHandler->contentHandler()->deleteVersion($contentId, $versionNo);
289
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
290
291
        return $return;
292
    }
293
294
    /**
295
     * {@inheritdoc}
296
     */
297
    public function listVersions($contentId, $status = null, $limit = -1)
298
    {
299
        $cacheItem = $this->cache->getItem("ez-content-${contentId}-version-list" . ($status ? "-byStatus-${status}" : ''));
300
        if ($cacheItem->isHit()) {
301
            $this->logger->logCacheHit(__METHOD__, ['content' => $contentId, 'status' => $status]);
302
303
            return $cacheItem->get();
304
        }
305
306
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'status' => $status));
307
        $versions = $this->persistenceHandler->contentHandler()->listVersions($contentId, $status, $limit);
308
        $cacheItem->set($versions);
309
        $tags = ["content-{$contentId}", "content-{$contentId}-version-list"];
310
        foreach ($versions as $version) {
311
            $tags = $this->getCacheTagsForVersion($version, $tags);
312
        }
313
        $cacheItem->tag($tags);
314
        $this->cache->save($cacheItem);
315
316
        return $versions;
317
    }
318
319
    /**
320
     * {@inheritdoc}
321
     */
322
    public function addRelation(RelationCreateStruct $relation)
323
    {
324
        $this->logger->logCall(__METHOD__, array('struct' => $relation));
325
326
        return $this->persistenceHandler->contentHandler()->addRelation($relation);
327
    }
328
329
    /**
330
     * {@inheritdoc}
331
     */
332
    public function removeRelation($relationId, $type)
333
    {
334
        $this->logger->logCall(__METHOD__, array('relation' => $relationId, 'type' => $type));
335
        $this->persistenceHandler->contentHandler()->removeRelation($relationId, $type);
336
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341 View Code Duplication
    public function loadRelations($sourceContentId, $sourceContentVersionNo = null, $type = null)
342
    {
343
        $this->logger->logCall(
344
            __METHOD__,
345
            array(
346
                'content' => $sourceContentId,
347
                'version' => $sourceContentVersionNo,
348
                'type' => $type,
349
            )
350
        );
351
352
        return $this->persistenceHandler->contentHandler()->loadRelations($sourceContentId, $sourceContentVersionNo, $type);
353
    }
354
355
    /**
356
     * {@inheritdoc}
357
     */
358
    public function loadReverseRelations($destinationContentId, $type = null)
359
    {
360
        $this->logger->logCall(__METHOD__, array('content' => $destinationContentId, 'type' => $type));
361
362
        return $this->persistenceHandler->contentHandler()->loadReverseRelations($destinationContentId, $type);
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368
    public function publish($contentId, $versionNo, MetadataUpdateStruct $struct)
369
    {
370
        $this->logger->logCall(__METHOD__, array('content' => $contentId, 'version' => $versionNo, 'struct' => $struct));
371
        $content = $this->persistenceHandler->contentHandler()->publish($contentId, $versionNo, $struct);
372
        $this->cache->invalidateTags(['content-' . $contentId]);
373
374
        return $content;
375
    }
376
377
    /**
378
     * {@inheritdoc}
379
     */
380
    public function removeTranslationFromContent($contentId, $languageCode)
381
    {
382
        $this->deleteTranslationFromContent($contentId, $languageCode);
383
    }
384
385
    /**
386
     * {@inheritdoc}
387
     */
388
    public function deleteTranslationFromContent($contentId, $languageCode)
389
    {
390
        $this->logger->logCall(
391
            __METHOD__,
392
            [
393
                'contentId' => $contentId,
394
                'languageCode' => $languageCode,
395
            ]
396
        );
397
398
        $this->persistenceHandler->contentHandler()->deleteTranslationFromContent($contentId, $languageCode);
399
        $this->cache->invalidateTags(['content-' . $contentId]);
400
    }
401
402
    /**
403
     * {@inheritdoc}
404
     */
405
    public function deleteTranslationFromDraft($contentId, $versionNo, $languageCode)
406
    {
407
        $this->logger->logCall(
408
            __METHOD__,
409
            ['content' => $contentId, 'version' => $versionNo, 'languageCode' => $languageCode]
410
        );
411
        $content = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
412
            $contentId,
413
            $versionNo,
414
            $languageCode
415
        );
416
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
417
418
        return $content;
419
    }
420
421
    /**
422
     * Return relevant content and location tags so cache can be purged reliably.
423
     *
424
     * For use when generating cache, not on invalidation.
425
     *
426
     * @param \eZ\Publish\SPI\Persistence\Content\ContentInfo $contentInfo
427
     * @param array $tags Optional, can be used to specify other tags.
428
     *
429
     * @return array
430
     */
431 View Code Duplication
    private function getCacheTags(ContentInfo $contentInfo, array $tags = [])
432
    {
433
        $tags[] = 'content-' . $contentInfo->id;
434
435
        if ($contentInfo->mainLocationId) {
436
            $locations = $this->persistenceHandler->locationHandler()->loadLocationsByContent($contentInfo->id);
437
            foreach ($locations as $location) {
438
                $tags[] = 'location-' . $location->id;
439
                foreach (explode('/', trim($location->pathString, '/')) as $pathId) {
440
                    $tags[] = 'location-path-' . $pathId;
441
                }
442
            }
443
        }
444
445
        return array_unique($tags);
446
    }
447
448
    private function getCacheTagsForVersion(VersionInfo $versionInfo, array $tags = [])
449
    {
450
        $contentInfo = $versionInfo->contentInfo;
451
        $tags[] = 'content-' . $contentInfo->id . '-version-' . $versionInfo->versionNo;
452
453
        return $this->getCacheTags($contentInfo, $tags);
454
    }
455
456
    private function getCacheTagsForContent(Content $content)
457
    {
458
        $versionInfo = $content->versionInfo;
459
        $tags = [
460
            'content-fields-' . $versionInfo->contentInfo->id,
461
            'content-fields-type-' . $versionInfo->contentInfo->contentTypeId,
462
        ];
463
464
        return $this->getCacheTagsForVersion($versionInfo, $tags);
465
    }
466
}
467