Completed
Push — master ( 7bc8ae...9f2037 )
by Łukasz
21:21
created

ContentHandler::init()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 34

Duplication

Lines 9
Ratio 26.47 %

Importance

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