Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
MemcachedPhpBagOStuff
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 12
870
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 doGet
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 doSet
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 doDelete
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 doAdd
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 doCas
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 doIncrWithInitAsync
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 doIncrWithInitSync
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
42
 doChangeTTL
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 doGetMulti
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 serialize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 unserialize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Object caching using memcached.
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 * A wrapper class for the pure-PHP memcached client, exposing a BagOStuff interface.
26 *
27 * @ingroup Cache
28 */
29class MemcachedPhpBagOStuff extends MemcachedBagOStuff {
30    /** @var MemcachedClient */
31    protected $client;
32
33    /**
34     * Available parameters are:
35     *   - servers:             The list of IP:port combinations holding the memcached servers.
36     *   - persistent:          Whether to use a persistent connection
37     *   - compress_threshold:  The minimum size an object must be before it is compressed
38     *   - timeout:             The read timeout in microseconds
39     *   - connect_timeout:     The connect timeout in seconds
40     *
41     * @param array $params
42     */
43    public function __construct( $params ) {
44        parent::__construct( $params );
45
46        // Default class-specific parameters
47        $params += [
48            'compress_threshold' => 1500,
49            'connect_timeout' => 0.5,
50            'timeout' => 500000,
51        ];
52
53        $this->client = new MemcachedClient( $params );
54        $this->client->set_servers( $params['servers'] );
55        $this->client->set_debug( true );
56    }
57
58    protected function doGet( $key, $flags = 0, &$casToken = null ) {
59        $getToken = ( $casToken === self::PASS_BY_REF );
60        $casToken = null;
61
62        $routeKey = $this->validateKeyAndPrependRoute( $key );
63
64        // T257003: only require "gets" (instead of "get") when a CAS token is needed
65        $res = $getToken
66            // @phan-suppress-next-line PhanTypeMismatchArgument False positive
67            ? $this->client->get( $routeKey, $casToken )
68            : $this->client->get( $routeKey );
69
70        if ( $this->client->_last_cmd_status !== self::ERR_NONE ) {
71            $this->setLastError( $this->client->_last_cmd_status );
72        }
73
74        return $res;
75    }
76
77    protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
78        $routeKey = $this->validateKeyAndPrependRoute( $key );
79
80        $res = $this->client->set( $routeKey, $value, $this->fixExpiry( $exptime ) );
81
82        if ( $this->client->_last_cmd_status !== self::ERR_NONE ) {
83            $this->setLastError( $this->client->_last_cmd_status );
84        }
85
86        return $res;
87    }
88
89    protected function doDelete( $key, $flags = 0 ) {
90        $routeKey = $this->validateKeyAndPrependRoute( $key );
91
92        $res = $this->client->delete( $routeKey );
93
94        if ( $this->client->_last_cmd_status !== self::ERR_NONE ) {
95            $this->setLastError( $this->client->_last_cmd_status );
96        }
97
98        return $res;
99    }
100
101    protected function doAdd( $key, $value, $exptime = 0, $flags = 0 ) {
102        $routeKey = $this->validateKeyAndPrependRoute( $key );
103
104        $res = $this->client->add( $routeKey, $value, $this->fixExpiry( $exptime ) );
105
106        if ( $this->client->_last_cmd_status !== self::ERR_NONE ) {
107            $this->setLastError( $this->client->_last_cmd_status );
108        }
109
110        return $res;
111    }
112
113    protected function doCas( $casToken, $key, $value, $exptime = 0, $flags = 0 ) {
114        $routeKey = $this->validateKeyAndPrependRoute( $key );
115
116        $res = $this->client->cas( $casToken, $routeKey, $value, $this->fixExpiry( $exptime ) );
117
118        if ( $this->client->_last_cmd_status !== self::ERR_NONE ) {
119            $this->setLastError( $this->client->_last_cmd_status );
120        }
121
122        return $res;
123    }
124
125    protected function doIncrWithInitAsync( $key, $exptime, $step, $init ) {
126        $routeKey = $this->validateKeyAndPrependRoute( $key );
127        $watchPoint = $this->watchErrors();
128        $this->client->add( $routeKey, $init - $step, $this->fixExpiry( $exptime ) );
129        $this->client->incr( $routeKey, $step );
130        return !$this->getLastError( $watchPoint );
131    }
132
133    protected function doIncrWithInitSync( $key, $exptime, $step, $init ) {
134        $routeKey = $this->validateKeyAndPrependRoute( $key );
135
136        $watchPoint = $this->watchErrors();
137        $newValue = $this->client->incr( $routeKey, $step ) ?? false;
138        if ( $newValue === false && !$this->getLastError( $watchPoint ) ) {
139            // No key set; initialize
140            $success = $this->client->add( $routeKey, $init, $this->fixExpiry( $exptime ) );
141            $newValue = $success ? $init : false;
142            if ( $newValue === false && !$this->getLastError( $watchPoint ) ) {
143                // Raced out initializing; increment
144                $newValue = $this->client->incr( $routeKey, $step ) ?? false;
145            }
146        }
147
148        return $newValue;
149    }
150
151    protected function doChangeTTL( $key, $exptime, $flags ) {
152        $routeKey = $this->validateKeyAndPrependRoute( $key );
153
154        $res = $this->client->touch( $routeKey, $this->fixExpiry( $exptime ) );
155
156        if ( $this->client->_last_cmd_status !== self::ERR_NONE ) {
157            $this->setLastError( $this->client->_last_cmd_status );
158        }
159
160        return $res;
161    }
162
163    protected function doGetMulti( array $keys, $flags = 0 ) {
164        $routeKeys = [];
165        foreach ( $keys as $key ) {
166            $routeKeys[] = $this->validateKeyAndPrependRoute( $key );
167        }
168
169        $resByRouteKey = $this->client->get_multi( $routeKeys );
170
171        $res = [];
172        foreach ( $resByRouteKey as $routeKey => $value ) {
173            $res[$this->stripRouteFromKey( $routeKey )] = $value;
174        }
175
176        if ( $this->client->_last_cmd_status !== self::ERR_NONE ) {
177            $this->setLastError( $this->client->_last_cmd_status );
178        }
179
180        return $res;
181    }
182
183    protected function serialize( $value ) {
184        return is_int( $value ) ? $value : $this->client->serialize( $value );
185    }
186
187    protected function unserialize( $value ) {
188        return $this->isInteger( $value ) ? (int)$value : $this->client->unserialize( $value );
189    }
190}