Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
StoriesCache
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 9
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRelatedStories
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getStory
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getStories
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
2
 invalidateForArticle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 invalidateStory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 loadAndRenderStory
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 makeRelatedStoriesKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeStoryKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Wikistories;
4
5use MediaWiki\Page\WikiPageFactory;
6use Wikimedia\ObjectCache\WANObjectCache;
7
8class StoriesCache {
9
10    /**
11     * This needs to be incremented every time we change
12     * the structure of the cached stories so they can
13     * be invalidated and re-created with the most recent
14     * structure.
15     */
16    private const CACHE_VERSION = 14;
17
18    /**
19     * This defines how long stories will stay in the cache if they not edited.
20     * For testing use WANObjectCache::TTL_UNCACHEABLE
21     */
22    private const CACHE_TTL = WANObjectCache::TTL_WEEK;
23
24    public function __construct(
25        private readonly WANObjectCache $wanObjectCache,
26        private readonly PageLinksSearch $pageLinksSearch,
27        private readonly WikiPageFactory $wikiPageFactory,
28        private readonly StoryRenderer $storyRenderer,
29    ) {
30    }
31
32    /**
33     * Retrieve the stories related to the give title
34     *
35     * @param string $titleDbKey
36     * @param int $pageId
37     * @return array Stories linked to the given title
38     */
39    public function getRelatedStories( string $titleDbKey, int $pageId ): array {
40        $ids = $this->wanObjectCache->getWithSetCallback(
41            $this->makeRelatedStoriesKey( $pageId ),
42            self::CACHE_TTL,
43            function () use ( $titleDbKey ) {
44                return $this->pageLinksSearch->getPageLinks( $titleDbKey, 10 );
45            }
46        );
47        return array_values( $this->getStories( $ids ) );
48    }
49
50    /**
51     * @param int $id
52     * @return mixed
53     */
54    public function getStory( int $id ) {
55        return $this->wanObjectCache->getWithSetCallback(
56            $this->makeStoryKey( $id ),
57            self::CACHE_TTL,
58            function () use ( $id ) {
59                return $this->loadAndRenderStory( $id );
60            }
61        );
62    }
63
64    /**
65     * @param array $ids
66     * @return mixed[]
67     */
68    private function getStories( array $ids ): array {
69        $keys = $this->wanObjectCache->makeMultiKeys(
70            $ids,
71            function ( $id ) {
72                return $this->makeStoryKey( $id );
73            }
74        );
75        $stories = $this->wanObjectCache->getMultiWithSetCallback(
76            $keys,
77            self::CACHE_TTL,
78            function ( $id ) {
79                return $this->loadAndRenderStory( $id );
80            }
81        );
82        return array_filter( $stories );
83    }
84
85    /**
86     * Clear the cached stories for the given article.
87     */
88    public function invalidateForArticle( int $articleId ): void {
89        $this->wanObjectCache->delete( $this->makeRelatedStoriesKey( $articleId ) );
90    }
91
92    public function invalidateStory( int $storyId ): void {
93        $this->wanObjectCache->delete( $this->makeStoryKey( $storyId ) );
94    }
95
96    private function loadAndRenderStory( int $storyId ): ?array {
97        $page = $this->wikiPageFactory->newFromID( $storyId );
98        $storyContent = $page->getContent();
99        return $storyContent instanceof StoryContent ?
100            $this->storyRenderer->getStoryData( $storyContent, $page->getTitle() ) :
101            null;
102    }
103
104    /**
105     * @param int $articleId
106     * @return string Cache key for the related stories for an article
107     */
108    private function makeRelatedStoriesKey( int $articleId ): string {
109        return $this->wanObjectCache->makeKey( 'wikistories', self::CACHE_VERSION, 'related', $articleId );
110    }
111
112    private function makeStoryKey( int $storyId ): string {
113        return $this->wanObjectCache->makeKey( 'wikistories', self::CACHE_VERSION, 'story', $storyId );
114    }
115}