Completed
Push — master ( 3e0b85...9d0d49 )
by André
63:27 queued 41:30
created

ContentHandler   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 423
Duplicated Lines 9.22 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 11
dl 39
loc 423
rs 8.96
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 7 1
A createDraftFromVersion() 0 8 1
A copy() 0 10 1
A load() 0 17 4
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 deleteVersion() 0 8 1
A listVersions() 0 19 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
A getCacheTagsForVersion() 0 7 1
A getCacheTagsForContent() 0 10 1
A deleteContent() 0 27 2

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