Completed
Push — master ( 2dca3b...5a27f3 )
by
unknown
16:48 queued 02:47
created

ContentHandler   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 468
Duplicated Lines 26.28 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

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

29 Methods

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