Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
81.82% |
45 / 55 |
|
50.00% |
4 / 8 |
CRAP | |
0.00% |
0 / 1 |
LCStoreStaticArray | |
81.82% |
45 / 55 |
|
50.00% |
4 / 8 |
35.41 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
startWrite | |
62.50% |
5 / 8 |
|
0.00% |
0 / 1 |
4.84 | |||
set | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isValueArray | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
6 | |||
encode | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
7.14 | |||
decode | |
63.64% |
7 / 11 |
|
0.00% |
0 / 1 |
7.73 | |||
finishWrite | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
get | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
4.18 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | use Wikimedia\StaticArrayWriter; |
22 | |
23 | /** |
24 | * Localisation cache storage based on PHP files and static arrays. |
25 | * |
26 | * @since 1.26 |
27 | * @ingroup Language |
28 | */ |
29 | class LCStoreStaticArray implements LCStore { |
30 | /** @var string|null Current language code. */ |
31 | private $currentLang = null; |
32 | |
33 | /** @var array Localisation data. */ |
34 | private $data = []; |
35 | |
36 | /** @var string|null File name. */ |
37 | private $fname = null; |
38 | |
39 | /** @var string Directory for cache files. */ |
40 | private $directory; |
41 | |
42 | public function __construct( $conf = [] ) { |
43 | $this->directory = $conf['directory']; |
44 | } |
45 | |
46 | public function startWrite( $code ) { |
47 | if ( !is_dir( $this->directory ) && !wfMkdirParents( $this->directory, null, __METHOD__ ) ) { |
48 | throw new RuntimeException( "Unable to create the localisation store " . |
49 | "directory \"{$this->directory}\"" ); |
50 | } |
51 | |
52 | $this->currentLang = $code; |
53 | $this->fname = $this->directory . '/' . $code . '.l10n.php'; |
54 | $this->data[$code] = []; |
55 | if ( is_file( $this->fname ) ) { |
56 | $this->data[$code] = require $this->fname; |
57 | } |
58 | } |
59 | |
60 | public function set( $key, $value ) { |
61 | $this->data[$this->currentLang][$key] = self::encode( $value ); |
62 | } |
63 | |
64 | /** |
65 | * Determine whether this array contains only scalar values. |
66 | * |
67 | * @param array $arr |
68 | * @return bool |
69 | */ |
70 | private static function isValueArray( array $arr ) { |
71 | foreach ( $arr as $value ) { |
72 | if ( is_scalar( $value ) |
73 | || $value === null |
74 | || ( is_array( $value ) && self::isValueArray( $value ) ) |
75 | ) { |
76 | continue; |
77 | } |
78 | return false; |
79 | } |
80 | return true; |
81 | } |
82 | |
83 | /** |
84 | * Encodes a value into an array format |
85 | * |
86 | * @param mixed $value |
87 | * @return array|mixed |
88 | * @throws RuntimeException |
89 | */ |
90 | public static function encode( $value ) { |
91 | if ( is_array( $value ) && self::isValueArray( $value ) ) { |
92 | // Type: scalar [v]alue. |
93 | // Optimization: Write large arrays as one value to avoid recursive decoding cost. |
94 | return [ 'v', $value ]; |
95 | } |
96 | if ( is_array( $value ) || is_object( $value ) ) { |
97 | // Type: [s]serialized. |
98 | // Optimization: Avoid recursive decoding cost. Write arrays with an objects |
99 | // as one serialised value. |
100 | return [ 's', serialize( $value ) ]; |
101 | } |
102 | if ( is_scalar( $value ) || $value === null ) { |
103 | // Optimization: Reduce file size by not wrapping scalar values. |
104 | return $value; |
105 | } |
106 | |
107 | throw new RuntimeException( 'Cannot encode ' . var_export( $value, true ) ); |
108 | } |
109 | |
110 | /** |
111 | * Decode something that was encoded with 'encode' |
112 | * |
113 | * @param mixed $encoded |
114 | * @return array|mixed |
115 | * @throws RuntimeException |
116 | */ |
117 | public static function decode( $encoded ) { |
118 | if ( !is_array( $encoded ) ) { |
119 | // Unwrapped scalar value |
120 | return $encoded; |
121 | } |
122 | |
123 | [ $type, $data ] = $encoded; |
124 | |
125 | switch ( $type ) { |
126 | case 'v': |
127 | // Value array (1.35+) or unwrapped scalar value (1.32 and earlier) |
128 | return $data; |
129 | case 's': |
130 | return unserialize( $data ); |
131 | case 'a': |
132 | // Support: MediaWiki 1.34 and earlier (older file format) |
133 | return array_map( [ __CLASS__, 'decode' ], $data ); |
134 | default: |
135 | throw new RuntimeException( |
136 | 'Unable to decode ' . var_export( $encoded, true ) ); |
137 | } |
138 | } |
139 | |
140 | public function finishWrite() { |
141 | $writer = new StaticArrayWriter(); |
142 | $out = $writer->create( |
143 | $this->data[$this->currentLang], |
144 | 'Generated by LCStoreStaticArray.php -- do not edit!' |
145 | ); |
146 | // Don't just write to the file, since concurrent requests may see a partial file (T304515). |
147 | // Write to a file in the same filesystem so that it can be atomically moved. |
148 | $tmpFileName = "{$this->fname}.tmp." . getmypid() . '.' . mt_rand(); |
149 | file_put_contents( $tmpFileName, $out ); |
150 | rename( $tmpFileName, $this->fname ); |
151 | // Release the data to manage the memory in rebuildLocalisationCache |
152 | unset( $this->data[$this->currentLang] ); |
153 | $this->currentLang = null; |
154 | $this->fname = null; |
155 | } |
156 | |
157 | public function get( $code, $key ) { |
158 | if ( !array_key_exists( $code, $this->data ) ) { |
159 | $fname = $this->directory . '/' . $code . '.l10n.php'; |
160 | if ( !is_file( $fname ) ) { |
161 | return null; |
162 | } |
163 | $this->data[$code] = require $fname; |
164 | } |
165 | $data = $this->data[$code]; |
166 | if ( array_key_exists( $key, $data ) ) { |
167 | return self::decode( $data[$key] ); |
168 | } |
169 | return null; |
170 | } |
171 | } |