Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
82.35% |
42 / 51 |
|
42.86% |
3 / 7 |
CRAP | |
0.00% |
0 / 1 |
MessageGroupWANCache | |
82.35% |
42 / 51 |
|
42.86% |
3 / 7 |
18.59 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getValue | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
4.00 | |||
setValue | |
50.00% |
1 / 2 |
|
0.00% |
0 / 1 |
1.12 | |||
delete | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
configure | |
76.19% |
16 / 21 |
|
0.00% |
0 / 1 |
5.34 | |||
checkConfig | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
toClosure | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\MessageGroupProcessing; |
5 | |
6 | use Closure; |
7 | use InvalidArgumentException; |
8 | use TypeError; |
9 | use WANObjectCache; |
10 | |
11 | /** |
12 | * A wrapper around WANObjectCache providing a simpler interface for |
13 | * MessageGroups to use the cache. |
14 | * |
15 | * @author Abijeet Patro |
16 | * @license GPL-2.0-or-later |
17 | */ |
18 | class MessageGroupWANCache { |
19 | private WANObjectCache $cache; |
20 | private string $cacheKey; |
21 | /** |
22 | * To be called when the cache is empty or expired to get the data |
23 | * to repopulate the cache |
24 | */ |
25 | private Closure $regenerator; |
26 | /** @see @https://doc.wikimedia.org/mediawiki-core/master/php/classWANObjectCache.html */ |
27 | private int $lockTSE; |
28 | /** @see @https://doc.wikimedia.org/mediawiki-core/master/php/classWANObjectCache.html */ |
29 | private ?Closure $touchedCallback; |
30 | /** @see @https://doc.wikimedia.org/mediawiki-core/master/php/classWANObjectCache.html */ |
31 | private int $ttl; |
32 | |
33 | /** |
34 | * A prefix for all keys saved by this cache |
35 | * @var string |
36 | */ |
37 | private const KEY_PREFIX = 'translate-mg'; |
38 | |
39 | public function __construct( WANObjectCache $cache ) { |
40 | $this->cache = $cache; |
41 | } |
42 | |
43 | /** |
44 | * Fetches value from cache for a message group. |
45 | * |
46 | * @param string|false $recache Either "recache" or false |
47 | * @return mixed |
48 | */ |
49 | public function getValue( $recache = false ) { |
50 | $this->checkConfig(); |
51 | |
52 | return $this->cache->getWithSetCallback( |
53 | $this->cacheKey, |
54 | $this->ttl, |
55 | $this->regenerator, |
56 | [ |
57 | 'lockTSE' => $this->lockTSE, // avoid stampedes (mutex) |
58 | 'touchedCallback' => function ( $value ) { |
59 | if ( isset( $this->touchedCallback ) && call_user_func( $this->touchedCallback, $value ) ) { |
60 | // treat value as if it just expired (for "lockTSE") |
61 | return time(); |
62 | } |
63 | |
64 | return null; |
65 | }, |
66 | // "miss" on recache |
67 | 'minAsOf' => $recache ? INF : WANObjectCache::MIN_TIMESTAMP_NONE, |
68 | ] |
69 | ); |
70 | } |
71 | |
72 | /** |
73 | * Sets value in the cache for the message group |
74 | * @param mixed $cacheData |
75 | */ |
76 | public function setValue( $cacheData ): void { |
77 | $this->checkConfig(); |
78 | $this->cache->set( $this->cacheKey, $cacheData, $this->ttl ); |
79 | } |
80 | |
81 | /** Deletes the cached value */ |
82 | public function delete(): void { |
83 | $this->checkConfig(); |
84 | $this->cache->delete( $this->cacheKey ); |
85 | } |
86 | |
87 | /** Configure the message group. This must be called before making a call to any other method. */ |
88 | public function configure( array $config ): void { |
89 | if ( !isset( $config['key'] ) ) { |
90 | throw new InvalidArgumentException( '$config[\'key\'] not set' ); |
91 | } |
92 | $cacheKey = $config['key']; |
93 | |
94 | if ( !isset( $config['regenerator'] ) ) { |
95 | throw new InvalidArgumentException( '$config[\'regenerator\'] not set' ); |
96 | } |
97 | $this->regenerator = $this->toClosure( 'regenerator', $config['regenerator'] ); |
98 | |
99 | $cacheVersion = $config['version'] ?? null; |
100 | $this->lockTSE = $config['lockTSE'] ?? 30; |
101 | $this->touchedCallback = isset( $config['touchedCallback'] ) |
102 | ? $this->toClosure( 'touchedCallback', $config['touchedCallback'] ) |
103 | : null; |
104 | |
105 | $this->ttl = $config['ttl'] ?? WANObjectCache::TTL_DAY; |
106 | |
107 | if ( $cacheVersion ) { |
108 | $this->cacheKey = $this->cache->makeKey( |
109 | self::KEY_PREFIX, |
110 | strtolower( $cacheKey ), |
111 | 'v' . $cacheVersion |
112 | ); |
113 | } else { |
114 | $this->cacheKey = $this->cache->makeKey( |
115 | self::KEY_PREFIX, strtolower( $cacheKey ) |
116 | ); |
117 | } |
118 | } |
119 | |
120 | /** Check to see if the instance is configured properly. */ |
121 | private function checkConfig(): void { |
122 | // Attempt to cast $this->regenerator of type \Closure to isset |
123 | // @phan-suppress-next-line PhanRedundantCondition |
124 | if ( !isset( $this->cacheKey ) || !isset( $this->regenerator ) ) { |
125 | throw new InvalidArgumentException( |
126 | "Required data not set." |
127 | . " Ensure you have called the configure function before get / setting values." |
128 | ); |
129 | } |
130 | } |
131 | |
132 | /** |
133 | * @param string $key key of the config |
134 | * @param mixed $potentialCallable The potential callable to convert. |
135 | */ |
136 | private function toClosure( string $key, $potentialCallable ): Closure { |
137 | try { |
138 | return Closure::fromCallable( $potentialCallable ); |
139 | } catch ( TypeError $e ) { |
140 | throw new InvalidArgumentException( |
141 | "\$config['$key'] is not callable: " . $e->getMessage(), 0, $e |
142 | ); |
143 | } |
144 | } |
145 | } |