Completed
Push — master ( 48855d...06b3fd )
by
unknown
37:10 queued 16:09
created

ContentHandler   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 420
Duplicated Lines 12.62 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 53
loc 420
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 copy() 0 6 1
A load() 0 16 3
A loadContentList() 5 22 3
A loadContentInfo() 0 15 2
A loadContentInfoList() 5 15 1
A loadContentInfoByRemoteId() 0 15 2
A loadVersionInfo() 0 15 2
A loadDraftsForUser() 0 6 1
A setStatus() 0 13 2
A updateMetadata() 0 8 1
A updateContent() 0 8 1
A deleteContent() 27 27 2
A deleteVersion() 0 8 1
A listVersions() 0 19 4
A addRelation() 0 6 1
A removeRelation() 0 5 1
A loadRelations() 0 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
A getCacheTagsForVersion() 0 7 1
A getCacheTagsForContent() 0 10 1

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