Completed
Push — inmemory-content-meta ( b4781d )
by André
20:22
created

ContentHandler::init()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

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