Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
52.27% |
23 / 44 |
|
37.50% |
3 / 8 |
CRAP | |
0.00% |
0 / 1 |
MemcachedBagOStuff | |
52.27% |
23 / 44 |
|
37.50% |
3 / 8 |
63.49 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
makeKeyInternal | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
5 | |||
requireConvertGenericKey | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
validateKeyEncoding | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
validateKeyAndPrependRoute | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
stripRouteFromKey | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
fixExpiry | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
doIncrWithInit | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
doIncrWithInitAsync | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
doIncrWithInitSync | n/a |
0 / 0 |
n/a |
0 / 0 |
0 |
1 | <?php |
2 | /** |
3 | * Base class for memcached clients. |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup Cache |
22 | */ |
23 | |
24 | /** |
25 | * Base class for memcached clients. |
26 | * |
27 | * @ingroup Cache |
28 | */ |
29 | abstract class MemcachedBagOStuff extends MediumSpecificBagOStuff { |
30 | /** @var string Routing prefix appended to keys during operations */ |
31 | protected $routingPrefix; |
32 | |
33 | /** |
34 | * @param array $params Additional parameters include: |
35 | * - routingPrefix: a routing prefix of the form "<datacenter>/<cluster>/" used to convey |
36 | * the location/strategy to use for handling keys accessed from this instance. The prefix |
37 | * is prepended to keys during cache operations. The memcached proxy must preserve these |
38 | * prefixes in any responses that include requested keys (e.g. get/gets). The proxy is |
39 | * also assumed to strip the routing prefix from the stored key name, which allows for |
40 | * unprefixed access. This can be used with mcrouter. [optional] |
41 | */ |
42 | public function __construct( array $params ) { |
43 | $params['segmentationSize'] ??= 917_504; // < 1MiB |
44 | parent::__construct( $params ); |
45 | |
46 | $this->routingPrefix = $params['routingPrefix'] ?? ''; |
47 | |
48 | // ...and does not use special disk-cache plugins |
49 | $this->attrMap[self::ATTR_DURABILITY] = self::QOS_DURABILITY_SERVICE; |
50 | } |
51 | |
52 | /** |
53 | * Format a cache key. |
54 | * |
55 | * @since 1.27 |
56 | * @see BagOStuff::makeKeyInternal |
57 | * @param string $keyspace |
58 | * @param string[]|int[] $components |
59 | * @return string |
60 | */ |
61 | protected function makeKeyInternal( $keyspace, $components ) { |
62 | // Memcached keys have a maximum length of 255 characters. From that, |
63 | // subtract the number of characters we need for the keyspace and for |
64 | // the separator character needed for each argument. To handle some |
65 | // custom prefixes used by thing like WANObjectCache, limit to 205. |
66 | $charsLeft = 205 - strlen( $keyspace ) - count( $components ); |
67 | |
68 | foreach ( $components as &$component ) { |
69 | $component = strtr( $component, ' ', '_' ); |
70 | |
71 | // Make sure %, #, and non-ASCII chars are escaped |
72 | $component = preg_replace_callback( |
73 | '/[^\x21-\x22\x24\x26-\x39\x3b-\x7e]+/', |
74 | static function ( $m ) { |
75 | return rawurlencode( $m[0] ); |
76 | }, |
77 | $component |
78 | ); |
79 | |
80 | // 33 = 32 characters for the MD5 + 1 for the '#' prefix. |
81 | if ( $charsLeft > 33 && strlen( $component ) > $charsLeft ) { |
82 | $component = '#' . md5( $component ); |
83 | } |
84 | |
85 | $charsLeft -= strlen( $component ); |
86 | } |
87 | |
88 | if ( $charsLeft < 0 ) { |
89 | return $keyspace . ':BagOStuff-long-key:##' . md5( implode( ':', $components ) ); |
90 | } |
91 | |
92 | return $keyspace . ':' . implode( ':', $components ); |
93 | } |
94 | |
95 | protected function requireConvertGenericKey(): bool { |
96 | return true; |
97 | } |
98 | |
99 | /** |
100 | * Ensure that a key is safe to use (contains no control characters and no |
101 | * characters above the ASCII range.) |
102 | * |
103 | * @param string $key |
104 | * @return string |
105 | * @throws Exception |
106 | */ |
107 | public function validateKeyEncoding( $key ) { |
108 | if ( preg_match( '/[^\x21-\x7e]+/', $key ) ) { |
109 | throw new InvalidArgumentException( "Key contains invalid characters: $key" ); |
110 | } |
111 | |
112 | return $key; |
113 | } |
114 | |
115 | /** |
116 | * @param string $key |
117 | * @return string |
118 | */ |
119 | protected function validateKeyAndPrependRoute( $key ) { |
120 | $this->validateKeyEncoding( $key ); |
121 | |
122 | if ( $this->routingPrefix === '' ) { |
123 | return $key; |
124 | } |
125 | |
126 | if ( $key[0] === '/' ) { |
127 | throw new RuntimeException( "Key '$key' already contains a route." ); |
128 | } |
129 | |
130 | return $this->routingPrefix . $key; |
131 | } |
132 | |
133 | /** |
134 | * @param string $key |
135 | * @return string |
136 | */ |
137 | protected function stripRouteFromKey( $key ) { |
138 | if ( $this->routingPrefix === '' ) { |
139 | return $key; |
140 | } |
141 | |
142 | if ( str_starts_with( $key, $this->routingPrefix ) ) { |
143 | return substr( $key, strlen( $this->routingPrefix ) ); |
144 | } |
145 | |
146 | return $key; |
147 | } |
148 | |
149 | /** |
150 | * @param int|float $exptime |
151 | * @return int |
152 | */ |
153 | protected function fixExpiry( $exptime ) { |
154 | if ( $exptime < 0 ) { |
155 | // The PECL driver does not seem to like negative relative values |
156 | $expiresAt = $this->getCurrentTime() + $exptime; |
157 | } elseif ( $this->isRelativeExpiration( $exptime ) ) { |
158 | // TTLs higher than 30 days will be detected as absolute TTLs |
159 | // (UNIX timestamps), and will result in the cache entry being |
160 | // discarded immediately because the expiry is in the past. |
161 | // Clamp expires >30d at 30d, unless they're >=1e9 in which |
162 | // case they are likely to really be absolute (1e9 = 2011-09-09) |
163 | $expiresAt = min( $exptime, self::TTL_MONTH ); |
164 | } else { |
165 | $expiresAt = $exptime; |
166 | } |
167 | |
168 | return (int)$expiresAt; |
169 | } |
170 | |
171 | protected function doIncrWithInit( $key, $exptime, $step, $init, $flags ) { |
172 | if ( $flags & self::WRITE_BACKGROUND ) { |
173 | return $this->doIncrWithInitAsync( $key, $exptime, $step, $init ); |
174 | } else { |
175 | return $this->doIncrWithInitSync( $key, $exptime, $step, $init ); |
176 | } |
177 | } |
178 | |
179 | /** |
180 | * @param string $key |
181 | * @param int $exptime |
182 | * @param int $step |
183 | * @param int $init |
184 | * @return bool True on success, false on failure |
185 | */ |
186 | abstract protected function doIncrWithInitAsync( $key, $exptime, $step, $init ); |
187 | |
188 | /** |
189 | * @param string $key |
190 | * @param int $exptime |
191 | * @param int $step |
192 | * @param int $init |
193 | * @return int|bool New value or false on failure |
194 | */ |
195 | abstract protected function doIncrWithInitSync( $key, $exptime, $step, $init ); |
196 | } |