Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.66% covered (warning)
89.66%
26 / 29
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractJsonStore
89.66% covered (warning)
89.66%
26 / 29
40.00% covered (danger)
40.00%
2 / 5
8.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 makeCacheKey
n/a
0 / 0
n/a
0 / 0
0
 fetchJsonBlob
n/a
0 / 0
n/a
0 / 0
0
 invalidate
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 loadConfiguration
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 loadConfigurationUncached
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 loadFromWanCache
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
1<?php
2
3namespace MediaWiki\Extension\CommunityConfiguration\Store;
4
5use HashBagOStuff;
6use MediaWiki\Json\FormatJson;
7use StatusValue;
8use WANObjectCache;
9use Wikimedia\LightweightObjectStore\ExpirationAwareness;
10
11/**
12 * Implements caching for the store; assumes stored values are JSON blobs
13 */
14abstract class AbstractJsonStore extends AbstractStore {
15
16    protected WANObjectCache $cache;
17    protected HashBagOStuff $inProcessCache;
18
19    public function __construct( WANObjectCache $cache ) {
20        $this->cache = $cache;
21        $this->inProcessCache = new HashBagOStuff();
22    }
23
24    /**
25     * Create a key to cache the stored blobs under
26     *
27     * @return string
28     */
29    abstract protected function makeCacheKey(): string;
30
31    /**
32     * Fetch JSON blob itself
33     *
34     * @return StatusValue When OK, must contain the JSON blob (represented as string) as the value.
35     */
36    abstract protected function fetchJsonBlob(): StatusValue;
37
38    /**
39     * @inheritDoc
40     */
41    public function invalidate(): void {
42        $cacheKey = $this->makeCacheKey();
43        $this->cache->delete( $cacheKey );
44        $this->inProcessCache->delete( $cacheKey );
45    }
46
47    /**
48     * @inheritDoc
49     */
50    public function loadConfiguration(): StatusValue {
51        // WANObjectCache has an in-process cache (pcTTL), but it is not subject
52        // to invalidation.
53        $result = $this->inProcessCache->getWithSetCallback(
54            $this->makeCacheKey(),
55            ExpirationAwareness::TTL_INDEFINITE,
56            function () {
57                return $this->loadFromWanCache();
58            }
59        );
60
61        if ( $result->isOK() ) {
62            // Deserialize the data at the very last step, to ensure each caller gets their own
63            // copy of the data. This is to avoid cache pollution; see LoaderIntegrationTest and
64            // T364101 for more details.
65            return FormatJson::parse( $result->getValue() );
66        }
67
68        // Return a (shallow) clone of the (cached) StatusValue. This is necessary, as
69        // StatusValue objects are mutable and cached in the in-process cache. Omitting this is
70        // likely to result in a cache pollution problem similar to T364101.
71        return clone $result;
72    }
73
74    /**
75     * @inheritDoc
76     */
77    public function loadConfigurationUncached(): StatusValue {
78        $result = $this->fetchJsonBlob();
79        if ( $result->isOK() ) {
80            // Deserialize the data at the very last step, to ensure each caller gets their own
81            // copy of the data. This is to avoid cache pollution; see LoaderIntegrationTest and
82            // T364101 for more details.
83            return FormatJson::parse( $result->getValue() );
84        }
85
86        // Return a (shallow) clone of the (cached) StatusValue. This is necessary, as
87        // StatusValue objects are mutable and cached in the in-process cache. Omitting this is
88        // likely to result in a cache pollution problem similar to T364101.
89        return clone $result;
90    }
91
92    private function loadFromWanCache() {
93        return $this->cache->getWithSetCallback(
94            $this->makeCacheKey(),
95            ExpirationAwareness::TTL_DAY,
96            function ( $oldValue, &$ttl ) {
97                $result = $this->fetchJsonBlob();
98                if ( !$result->isOK() ) {
99                    // error should not be cached
100                    $ttl = ExpirationAwareness::TTL_UNCACHEABLE;
101                }
102                return $result;
103            }
104        );
105    }
106}