Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
83.33% |
40 / 48 |
|
75.00% |
6 / 8 |
CRAP | |
0.00% |
0 / 1 |
RemoteSchema | |
83.33% |
40 / 48 |
|
75.00% |
6 / 8 |
18.34 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
get | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
jsonSerialize | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
memcGet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
memcSet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
httpGet | |
76.47% |
13 / 17 |
|
0.00% |
0 / 1 |
4.21 | |||
lock | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUri | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\EventLogging; |
4 | |
5 | use BagOStuff; |
6 | use FormatJson; |
7 | use JsonSerializable; |
8 | use MediaWiki\Http\HttpRequestFactory; |
9 | use MediaWiki\MediaWikiServices; |
10 | use ObjectCache; |
11 | use stdClass; |
12 | |
13 | /** |
14 | * Represents a schema revision on a remote wiki. |
15 | * Handles retrieval (via HTTP) and local caching. |
16 | */ |
17 | class RemoteSchema implements JsonSerializable { |
18 | |
19 | public const LOCK_TIMEOUT = 20; |
20 | |
21 | public $title; |
22 | public $revision; |
23 | public $cache; |
24 | public $httpRequestFactory; |
25 | public $key; |
26 | public $content = false; |
27 | |
28 | /** |
29 | * Constructor. |
30 | * @param string $title |
31 | * @param int $revision |
32 | * @param BagOStuff|null $cache (optional) cache client. |
33 | * @param HttpRequestFactory|null $httpRequestFactory (optional) HttpRequestFactory client. |
34 | */ |
35 | public function __construct( $title, $revision, $cache = null, $httpRequestFactory = null ) { |
36 | global $wgEventLoggingSchemaApiUri; |
37 | |
38 | $this->title = $title; |
39 | $this->revision = $revision; |
40 | $this->cache = $cache ?: ObjectCache::getInstance( CACHE_ANYTHING ); |
41 | $this->httpRequestFactory = $httpRequestFactory ?: MediaWikiServices::getInstance()->getHttpRequestFactory(); |
42 | $this->key = $this->cache->makeGlobalKey( |
43 | 'eventlogging-schema', |
44 | $wgEventLoggingSchemaApiUri, |
45 | $revision |
46 | ); |
47 | } |
48 | |
49 | /** |
50 | * Retrieves schema content. |
51 | * @return array|bool Schema or false if irretrievable. |
52 | */ |
53 | public function get() { |
54 | if ( $this->content ) { |
55 | return $this->content; |
56 | } |
57 | |
58 | $this->content = $this->memcGet(); |
59 | if ( $this->content ) { |
60 | return $this->content; |
61 | } |
62 | |
63 | $this->content = $this->httpGet(); |
64 | if ( $this->content ) { |
65 | $this->memcSet(); |
66 | } |
67 | |
68 | return $this->content; |
69 | } |
70 | |
71 | /** |
72 | * Returns an object containing serializable properties. |
73 | * @return array |
74 | */ |
75 | public function jsonSerialize(): array { |
76 | return [ |
77 | 'schema' => $this->get() ?: new stdClass(), |
78 | 'revision' => $this->revision |
79 | ]; |
80 | } |
81 | |
82 | /** |
83 | * Retrieves content from memcached. |
84 | * @return array|bool Schema or false if not in cache. |
85 | */ |
86 | protected function memcGet() { |
87 | return $this->cache->get( $this->key ); |
88 | } |
89 | |
90 | /** |
91 | * Store content in memcached. |
92 | * @return bool |
93 | */ |
94 | protected function memcSet() { |
95 | return $this->cache->set( $this->key, $this->content ); |
96 | } |
97 | |
98 | /** |
99 | * Retrieves the schema using HTTP. |
100 | * Uses a memcached lock to avoid cache stampedes. |
101 | * @return array|bool Schema or false if unable to fetch. |
102 | */ |
103 | protected function httpGet() { |
104 | $uri = $this->getUri(); |
105 | if ( !$this->lock() ) { |
106 | EventLogging::getLogger()->warning( |
107 | 'Failed to get lock for requesting {schema_uri}.', |
108 | [ 'schema_uri' => $uri ] |
109 | ); |
110 | return false; |
111 | } |
112 | $raw = $this->httpRequestFactory->get( $uri, [ |
113 | 'timeout' => self::LOCK_TIMEOUT * 0.8 |
114 | ], __METHOD__ ); |
115 | $content = FormatJson::decode( $raw, true ); |
116 | if ( !$content ) { |
117 | EventLogging::getLogger()->error( |
118 | 'Request to {schema_uri} failed.', |
119 | [ 'schema_uri' => $uri ] |
120 | ); |
121 | } |
122 | return $content ?: false; |
123 | } |
124 | |
125 | /** |
126 | * Acquire a mutex lock for HTTP retrieval. |
127 | * @return bool Whether lock was successfully acquired. |
128 | */ |
129 | protected function lock() { |
130 | return $this->cache->add( $this->key . ':lock', 1, self::LOCK_TIMEOUT ); |
131 | } |
132 | |
133 | /** |
134 | * Constructs URI for retrieving schema from remote wiki. |
135 | * @return string URI. |
136 | */ |
137 | protected function getUri() { |
138 | global $wgEventLoggingSchemaApiUri; |
139 | |
140 | return wfAppendQuery( $wgEventLoggingSchemaApiUri, [ |
141 | 'action' => 'jsonschema', |
142 | 'revid' => $this->revision, |
143 | 'formatversion' => 2, |
144 | 'format' => 'json', |
145 | ] ); |
146 | } |
147 | } |