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