GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

MemcachedHandler::write()   B
last analyzed

Complexity

Conditions 8
Paths 10

Size

Total Lines 36
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 19
c 1
b 0
f 0
nc 10
nop 2
dl 0
loc 36
rs 8.4444
1
<?php
2
/**
3
 * This file is part of the O2System Framework package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @author         Steeve Andrian Salim
9
 * @copyright      Copyright (c) Steeve Andrian Salim
10
 */
11
12
// ------------------------------------------------------------------------
13
14
namespace O2System\Session\Handlers;
15
16
// ------------------------------------------------------------------------
17
18
use Psr\Log\LoggerInterface;
19
use O2System\Session\Abstracts\AbstractHandler;
20
use O2System\Session\DataStructures\Config;
21
22
/**
23
 * Class MemcachedHandler
24
 *
25
 * @package O2System\Session\Handlers
26
 */
27
class MemcachedHandler extends AbstractHandler
28
{
29
    /**
30
     * Platform Name
31
     *
32
     * @access  protected
33
     * @var string
34
     */
35
    protected $platform = 'memcached';
36
37
    /**
38
     * Memcached Object
39
     *
40
     * @var \Memcache|\Memcached
41
     */
42
    protected $memcached;
43
44
    // ------------------------------------------------------------------------
45
46
    /**
47
     * MemcachedHandler::__construct
48
     *
49
     * @param Config $config
50
     */
51
    public function __construct(Config $config)
52
    {
53
        $config->merge(
54
            [
55
                'host'   => '127.0.0.1',
56
                'port'   => 11211,
57
                'weight' => 1,
58
            ]
59
        );
60
61
        parent::__construct($config);
62
    }
63
64
    /**
65
     * MemcachedHandler::open
66
     *
67
     * Initialize session
68
     *
69
     * @link  http://php.net/manual/en/sessionhandlerinterface.open.php
70
     *
71
     * @param string $save_path The path where to store/retrieve the session.
72
     * @param string $name      The session name.
73
     *
74
     * @return bool <p>
75
     * The return value (usually TRUE on success, FALSE on failure).
76
     * Note this value is returned internally to PHP for processing.
77
     * </p>
78
     * @since 5.4.0
79
     */
80
    public function open($save_path, $name)
81
    {
82
        if (class_exists('Memcached', false)) {
83
            $this->memcached = new \Memcached();
84
            $this->memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); // required for touch() usage
85
        } else {
86
            if ($this->logger instanceof LoggerInterface) {
0 ignored issues
show
introduced by
$this->logger is always a sub-type of Psr\Log\LoggerInterface.
Loading history...
87
                $this->logger->error('SESSION_E_PLATFORM_UNSUPPORTED', ['Memcache']);
88
            }
89
90
            return false;
91
        }
92
93
        if (isset($this->config[ 'servers' ])) {
94
            foreach ($this->config[ 'servers' ] as $server => $setup) {
95
                isset($setup[ 'port' ]) OR $setup[ 'port' ] = 11211;
96
                isset($setup[ 'weight' ]) OR $setup[ 'weight' ] = 1;
97
98
                $this->memcached->addServer(
99
                    $setup[ 'host' ],
100
                    $setup[ 'port' ],
101
                    $setup[ 'weight' ]
102
                );
103
            }
104
        } else {
105
            $this->memcached->addServer(
106
                $this->config[ 'host' ],
107
                $this->config[ 'port' ],
108
                $this->config[ 'weight' ]
109
            );
110
        }
111
112
        if ($this->memcached->getVersion() === false) {
113
            if ($this->logger instanceof LoggerInterface) {
0 ignored issues
show
introduced by
$this->logger is always a sub-type of Psr\Log\LoggerInterface.
Loading history...
114
                $this->logger->error('SESSION_E_MEMCACHED_CONNECTION_REFUSED');
115
            }
116
117
            return false;
118
        }
119
120
        return true;
121
    }
122
123
    // ------------------------------------------------------------------------
124
125
    /**
126
     * MemcachedHandler::close
127
     *
128
     * Close the session
129
     *
130
     * @link  http://php.net/manual/en/sessionhandlerinterface.close.php
131
     * @return bool <p>
132
     *        The return value (usually TRUE on success, FALSE on failure).
133
     *        Note this value is returned internally to PHP for processing.
134
     *        </p>
135
     * @since 5.4.0
136
     */
137
    public function close()
138
    {
139
        if (isset($this->memcached)) {
140
            isset($this->lockKey) AND $this->memcached->delete($this->lockKey);
141
142
            if ($this->memcached instanceof \Memcached) {
143
                $this->memcached->quit();
144
            } elseif ($this->memcached instanceof \MemcachePool) {
0 ignored issues
show
introduced by
$this->memcached is always a sub-type of MemcachePool.
Loading history...
145
                $this->memcached->close();
146
            }
147
148
            $this->memcached = null;
149
150
            return true;
151
        }
152
153
        return false;
154
    }
155
156
    // ------------------------------------------------------------------------
157
158
    /**
159
     * MemcachedHandler::destroy
160
     *
161
     * Destroy a session
162
     *
163
     * @link  http://php.net/manual/en/sessionhandlerinterface.destroy.php
164
     *
165
     * @param string $session_id The session ID being destroyed.
166
     *
167
     * @return bool <p>
168
     * The return value (usually TRUE on success, FALSE on failure).
169
     * Note this value is returned internally to PHP for processing.
170
     * </p>
171
     * @since 5.4.0
172
     */
173
    public function destroy($session_id)
174
    {
175
        if (isset($this->memcached, $this->lockKey)) {
176
            $this->memcached->delete($this->prefixKey . $session_id);
177
178
            return $this->destroyCookie();
179
        }
180
181
        return false;
182
    }
183
184
    // ------------------------------------------------------------------------
185
186
    /**
187
     * MemcachedHandler::gc
188
     *
189
     * Cleanup old sessions
190
     *
191
     * @link  http://php.net/manual/en/sessionhandlerinterface.gc.php
192
     *
193
     * @param int $maxlifetime <p>
194
     *                         Sessions that have not updated for
195
     *                         the last maxlifetime seconds will be removed.
196
     *                         </p>
197
     *
198
     * @return bool <p>
199
     * The return value (usually TRUE on success, FALSE on failure).
200
     * Note this value is returned internally to PHP for processing.
201
     * </p>
202
     * @since 5.4.0
203
     */
204
    public function gc($maxlifetime)
205
    {
206
        // Not necessary, Memcached takes care of that.
207
        return true;
208
    }
209
210
    // ------------------------------------------------------------------------
211
212
    /**
213
     * MemcachedHandler::read
214
     *
215
     * Read session data
216
     *
217
     * @link  http://php.net/manual/en/sessionhandlerinterface.read.php
218
     *
219
     * @param string $session_id The session id to read data for.
220
     *
221
     * @return string <p>
222
     * Returns an encoded string of the read data.
223
     * If nothing was read, it must return an empty string.
224
     * Note this value is returned internally to PHP for processing.
225
     * </p>
226
     * @since 5.4.0
227
     */
228
    public function read($session_id)
229
    {
230
        if (isset($this->memcached) AND $this->lockSession($session_id)) {
231
            // Needed by write() to detect session_regenerate_id() calls
232
            $this->sessionId = $session_id;
233
234
            $sessionData = (string)$this->memcached->get($this->prefixKey . $session_id);
235
            $this->fingerprint = md5($sessionData);
0 ignored issues
show
Documentation Bug introduced by
The property $fingerprint was declared of type boolean, but md5($sessionData) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
236
237
            return $sessionData;
238
        }
239
240
        return '';
241
    }
242
243
    // ------------------------------------------------------------------------
244
245
    /**
246
     * MemcachedHandler::_lockSession
247
     *
248
     * Acquires an (emulated) lock.
249
     *
250
     * @param   string $session_id Session ID
251
     *
252
     * @return  bool
253
     */
254
    protected function lockSession($session_id)
255
    {
256
        if (isset($this->lockKey)) {
257
            return $this->memcached->replace($this->lockKey, time(), 300);
258
        }
259
260
        // 30 attempts to obtain a lock, in case another request already has it
261
        $lockKey = $this->prefixKey . $session_id . ':lock';
262
        $attempt = 0;
263
264
        do {
265
            if ($this->memcached->get($lockKey)) {
266
                sleep(1);
267
                continue;
268
            }
269
270
            if ( ! @$this->memcached->set($lockKey, time(), 300)) {
271
                if ($this->logger instanceof LoggerInterface) {
272
                    $this->logger->error('SESSION_E_OBTAIN_LOCK', [$this->prefixKey . $session_id]);
273
                }
274
275
                return false;
276
            }
277
278
            $this->lockKey = $lockKey;
279
            break;
280
        } while (++$attempt < 30);
281
282
        if ($attempt === 30) {
283
            if ($this->logger instanceof LoggerInterface) {
0 ignored issues
show
introduced by
$this->logger is always a sub-type of Psr\Log\LoggerInterface.
Loading history...
284
                $this->logger->error('SESSION_E_OBTAIN_LOCK_30', [$this->prefixKey . $session_id]);
285
            }
286
287
            return false;
288
        }
289
290
        $this->isLocked = true;
291
292
        return true;
293
    }
294
295
    // ------------------------------------------------------------------------
296
297
    /**
298
     * MemcachedHandler::write
299
     *
300
     * Write session data
301
     *
302
     * @link  http://php.net/manual/en/sessionhandlerinterface.write.php
303
     *
304
     * @param string $session_id   The session id.
305
     * @param string $session_data <p>
306
     *                             The encoded session data. This data is the
307
     *                             result of the PHP internally encoding
308
     *                             the $_SESSION superglobal to a serialized
309
     *                             string and passing it as this parameter.
310
     *                             Please note sessions use an alternative serialization method.
311
     *                             </p>
312
     *
313
     * @return bool <p>
314
     * The return value (usually TRUE on success, FALSE on failure).
315
     * Note this value is returned internally to PHP for processing.
316
     * </p>
317
     * @since 5.4.0
318
     */
319
    public function write($session_id, $session_data)
320
    {
321
        if ( ! isset($this->memcached)) {
322
            return false;
323
        } // Was the ID regenerated?
324
        elseif ($session_id !== $this->sessionId) {
325
            if ( ! $this->lockRelease() OR ! $this->lockSession($session_id)) {
326
                return false;
327
            }
328
329
            $this->fingerprint = md5('');
0 ignored issues
show
Documentation Bug introduced by
The property $fingerprint was declared of type boolean, but md5('') is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
330
            $this->sessionId = $session_id;
331
        }
332
333
        if (isset($this->lockKey)) {
334
            $this->memcached->replace($this->lockKey, time(), 300);
335
336
            if ($this->fingerprint !== ($fingerprint = md5($session_data))) {
337
                if ($this->memcached->set(
338
                    $this->prefixKey . $session_id,
339
                    $session_data,
340
                    $this->config[ 'lifetime' ]
341
                )
342
                ) {
343
                    $this->fingerprint = $fingerprint;
344
345
                    return true;
346
                }
347
348
                return false;
349
            }
350
351
            return $this->memcached->touch($this->prefixKey . $session_id, $this->config[ 'lifetime' ]);
0 ignored issues
show
Bug introduced by
The method touch() does not exist on Memcache. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

351
            return $this->memcached->/** @scrutinizer ignore-call */ touch($this->prefixKey . $session_id, $this->config[ 'lifetime' ]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
352
        }
353
354
        return false;
355
    }
356
357
    //--------------------------------------------------------------------
358
359
    /**
360
     * MemcachedHandler::_lockRelease
361
     *
362
     * Releases a previously acquired lock
363
     *
364
     * @return    bool
365
     */
366
    protected function lockRelease()
367
    {
368
        if (isset($this->memcached, $this->lockKey) AND $this->isLocked) {
369
            if ( ! $this->memcached->delete($this->lockKey) AND
370
                $this->memcached->getResultCode() !== \Memcached::RES_NOTFOUND
0 ignored issues
show
Bug introduced by
The method getResultCode() does not exist on Memcache. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

370
                $this->memcached->/** @scrutinizer ignore-call */ getResultCode() !== \Memcached::RES_NOTFOUND

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
371
            ) {
372
                if ($this->logger instanceof LoggerInterface) {
0 ignored issues
show
introduced by
$this->logger is always a sub-type of Psr\Log\LoggerInterface.
Loading history...
373
                    $this->logger->error('SESSION_E_FREE_LOCK', [$this->lockKey]);
374
                }
375
376
                return false;
377
            }
378
379
            $this->lockKey = null;
380
            $this->isLocked = false;
381
        }
382
383
        return true;
384
    }
385
386
    //--------------------------------------------------------------------
387
388
    /**
389
     * MemcachedHandler::isSupported
390
     *
391
     * Checks if this platform is supported on this system.
392
     *
393
     * @return bool Returns FALSE if unsupported.
394
     */
395
    public function isSupported()
396
    {
397
        return (bool)extension_loaded('memcached');
398
    }
399
}