Completed
Push — 7.5 ( 17c267...9e0292 )
by Łukasz
47:52 queued 28:25
created

ContentHandler::addRelation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
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
    /** @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
        $this->getContentTags = function (Content $content) {
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
    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
    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
    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
    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
        if ($cacheItem->isHit()) {
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
    /**
227
     * {@inheritdoc}
228
     */
229
    public function countDraftsForUser(int $userId): int
230
    {
231
        $this->logger->logCall(__METHOD__, ['user' => $userId]);
232
233
        return $this->persistenceHandler->contentHandler()->countDraftsForUser($userId);
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function loadDraftsForUser($userId)
240
    {
241
        $this->logger->logCall(__METHOD__, ['user' => $userId]);
242
243
        return $this->persistenceHandler->contentHandler()->loadDraftsForUser($userId);
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249
    public function loadDraftListForUser(int $userId, int $offset = 0, int $limit = -1): array
250
    {
251
        $this->logger->logCall(__METHOD__, ['user' => $userId, 'offset' => $offset, 'limit' => $limit]);
252
253
        return $this->persistenceHandler->contentHandler()->loadDraftListForUser($userId, $offset, $limit);
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259
    public function setStatus($contentId, $status, $versionNo)
260
    {
261
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'status' => $status, 'version' => $versionNo]);
262
        $return = $this->persistenceHandler->contentHandler()->setStatus($contentId, $status, $versionNo);
263
264
        if ($status === VersionInfo::STATUS_PUBLISHED) {
265
            $this->cache->invalidateTags(['content-' . $contentId]);
266
        } else {
267
            $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
268
        }
269
270
        return $return;
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    public function updateMetadata($contentId, MetadataUpdateStruct $struct)
277
    {
278
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'struct' => $struct]);
279
        $contentInfo = $this->persistenceHandler->contentHandler()->updateMetadata($contentId, $struct);
280
        $this->cache->invalidateTags(['content-' . $contentId]);
281
282
        return $contentInfo;
283
    }
284
285
    /**
286
     * {@inheritdoc}
287
     */
288
    public function updateContent($contentId, $versionNo, UpdateStruct $struct)
289
    {
290
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'version' => $versionNo, 'struct' => $struct]);
291
        $content = $this->persistenceHandler->contentHandler()->updateContent($contentId, $versionNo, $struct);
292
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
293
294
        return $content;
295
    }
296
297
    /**
298
     * {@inheritdoc}
299
     */
300
    public function deleteContent($contentId)
301
    {
302
        $this->logger->logCall(__METHOD__, ['content' => $contentId]);
303
304
        // Load reverse field relations first
305
        $reverseRelations = $this->persistenceHandler->contentHandler()->loadReverseRelations(
306
            $contentId,
307
            APIRelation::FIELD | APIRelation::ASSET
308
        );
309
310
        $return = $this->persistenceHandler->contentHandler()->deleteContent($contentId);
311
312
        if (!empty($reverseRelations)) {
313
            $tags = \array_map(
314
                static function ($relation) {
315
                    // only the full content object *with* fields is affected by this
316
                    return 'content-fields-' . $relation->sourceContentId;
317
                },
318
                $reverseRelations
319
            );
320
        } else {
321
            $tags = [];
322
        }
323
        $tags[] = 'content-' . $contentId;
324
        $this->cache->invalidateTags($tags);
325
326
        return $return;
327
    }
328
329
    /**
330
     * {@inheritdoc}
331
     */
332
    public function deleteVersion($contentId, $versionNo)
333
    {
334
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'version' => $versionNo]);
335
        $return = $this->persistenceHandler->contentHandler()->deleteVersion($contentId, $versionNo);
336
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
337
338
        return $return;
339
    }
340
341
    /**
342
     * {@inheritdoc}
343
     */
344
    public function listVersions($contentId, $status = null, $limit = -1)
345
    {
346
        $cacheItem = $this->cache->getItem("ez-content-${contentId}-version-list" . ($status ? "-byStatus-${status}" : '') . "-limit-{$limit}");
347
        if ($cacheItem->isHit()) {
348
            $this->logger->logCacheHit(['content' => $contentId, 'status' => $status]);
349
350
            return $cacheItem->get();
351
        }
352
353
        $this->logger->logCacheMiss(['content' => $contentId, 'status' => $status]);
354
        $versions = $this->persistenceHandler->contentHandler()->listVersions($contentId, $status, $limit);
355
        $cacheItem->set($versions);
356
        $tags = ["content-{$contentId}", "content-{$contentId}-version-list"];
357
        foreach ($versions as $version) {
358
            $tags = $this->getCacheTagsForVersion($version, $tags);
359
        }
360
        $cacheItem->tag($tags);
361
        $this->cache->save($cacheItem);
362
363
        return $versions;
364
    }
365
366
    /**
367
     * {@inheritdoc}
368
     */
369
    public function addRelation(RelationCreateStruct $relation)
370
    {
371
        $this->logger->logCall(__METHOD__, ['struct' => $relation]);
372
373
        return $this->persistenceHandler->contentHandler()->addRelation($relation);
374
    }
375
376
    /**
377
     * {@inheritdoc}
378
     */
379
    public function removeRelation($relationId, $type)
380
    {
381
        $this->logger->logCall(__METHOD__, ['relation' => $relationId, 'type' => $type]);
382
        $this->persistenceHandler->contentHandler()->removeRelation($relationId, $type);
383
    }
384
385
    /**
386
     * {@inheritdoc}
387
     */
388
    public function loadRelations($sourceContentId, $sourceContentVersionNo = null, $type = null)
389
    {
390
        $this->logger->logCall(
391
            __METHOD__,
392
            [
393
                'content' => $sourceContentId,
394
                'version' => $sourceContentVersionNo,
395
                'type' => $type,
396
            ]
397
        );
398
399
        return $this->persistenceHandler->contentHandler()->loadRelations($sourceContentId, $sourceContentVersionNo, $type);
400
    }
401
402
    /**
403
     * {@inheritdoc}
404
     */
405
    public function countReverseRelations(int $destinationContentId, ?int $type = null): int
406
    {
407
        $this->logger->logCall(__METHOD__, ['content' => $destinationContentId, 'type' => $type]);
408
409
        return $this->persistenceHandler->contentHandler()->countReverseRelations($destinationContentId, $type);
410
    }
411
412
    /**
413
     * {@inheritdoc}
414
     */
415
    public function loadReverseRelations($destinationContentId, $type = null)
416
    {
417
        $this->logger->logCall(__METHOD__, ['content' => $destinationContentId, 'type' => $type]);
418
419
        return $this->persistenceHandler->contentHandler()->loadReverseRelations($destinationContentId, $type);
420
    }
421
422
    /**
423
     * {@inheritdoc}
424
     */
425
    public function loadReverseRelationList(
426
        int $destinationContentId,
427
        int $offset = 0,
428
        int $limit = -1,
429
        ?int $type = null
430
    ): array {
431
        $this->logger->logCall(__METHOD__, [
432
            'content' => $destinationContentId,
433
            'offset' => $offset,
434
            'limit' => $limit,
435
            'type' => $type,
436
        ]);
437
438
        return $this->persistenceHandler->contentHandler()->loadReverseRelationList(
439
            $destinationContentId,
440
            $offset,
441
            $limit,
442
            $type
443
        );
444
    }
445
446
    /**
447
     * {@inheritdoc}
448
     */
449
    public function publish($contentId, $versionNo, MetadataUpdateStruct $struct)
450
    {
451
        $this->logger->logCall(__METHOD__, ['content' => $contentId, 'version' => $versionNo, 'struct' => $struct]);
452
        $content = $this->persistenceHandler->contentHandler()->publish($contentId, $versionNo, $struct);
453
        $this->cache->invalidateTags(['content-' . $contentId]);
454
455
        return $content;
456
    }
457
458
    /**
459
     * {@inheritdoc}
460
     */
461
    public function removeTranslationFromContent($contentId, $languageCode)
462
    {
463
        $this->deleteTranslationFromContent($contentId, $languageCode);
464
    }
465
466
    /**
467
     * {@inheritdoc}
468
     */
469
    public function deleteTranslationFromContent($contentId, $languageCode)
470
    {
471
        $this->logger->logCall(
472
            __METHOD__,
473
            [
474
                'contentId' => $contentId,
475
                'languageCode' => $languageCode,
476
            ]
477
        );
478
479
        $this->persistenceHandler->contentHandler()->deleteTranslationFromContent($contentId, $languageCode);
480
        $this->cache->invalidateTags(['content-' . $contentId]);
481
    }
482
483
    /**
484
     * {@inheritdoc}
485
     */
486
    public function deleteTranslationFromDraft($contentId, $versionNo, $languageCode)
487
    {
488
        $this->logger->logCall(
489
            __METHOD__,
490
            ['content' => $contentId, 'version' => $versionNo, 'languageCode' => $languageCode]
491
        );
492
        $content = $this->persistenceHandler->contentHandler()->deleteTranslationFromDraft(
493
            $contentId,
494
            $versionNo,
495
            $languageCode
496
        );
497
        $this->cache->invalidateTags(["content-{$contentId}-version-{$versionNo}"]);
498
499
        return $content;
500
    }
501
502
    /**
503
     * Return relevant content and location tags so cache can be purged reliably.
504
     *
505
     * For use when generating cache, not on invalidation.
506
     *
507
     * @param \eZ\Publish\SPI\Persistence\Content\VersionInfo $versionInfo
508
     * @param array $tags Optional, can be used to specify other tags.
509
     *
510
     * @return array
511
     */
512
    private function getCacheTagsForVersion(VersionInfo $versionInfo, array $tags = []): array
513
    {
514
        $contentInfo = $versionInfo->contentInfo;
515
        $tags[] = 'content-' . $contentInfo->id . '-version-' . $versionInfo->versionNo;
516
        $getContentInfoTagsFn = $this->getContentInfoTags;
517
518
        return $getContentInfoTagsFn($contentInfo, $tags);
519
    }
520
521
    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...
522
    {
523
        $versionInfo = $content->versionInfo;
524
        $tags = [
525
            'content-fields-' . $versionInfo->contentInfo->id,
526
            'content-fields-type-' . $versionInfo->contentInfo->contentTypeId,
527
        ];
528
529
        return $this->getCacheTagsForVersion($versionInfo, $tags);
530
    }
531
}
532