Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.05% covered (warning)
86.05%
37 / 43
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Memcache
86.05% covered (warning)
86.05%
37 / 43
80.00% covered (warning)
80.00%
4 / 5
14.53
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 connect
73.91% covered (warning)
73.91%
17 / 23
0.00% covered (danger)
0.00%
0 / 1
7.87
 read
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 destroy
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 saveSession
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * MemCache session handler
5 *
6 * Note: This relies on PHP's Memcache extension
7 * (see http://us.php.net/manual/en/book.memcache.php)
8 *
9 * PHP version 8
10 *
11 * Copyright (C) Villanova University 2010.
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License version 2,
15 * as published by the Free Software Foundation.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
25 *
26 * @category VuFind
27 * @package  Session_Handlers
28 * @author   Demian Katz <demian.katz@villanova.edu>
29 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
30 * @link     https://vufind.org/wiki/development:plugins:session_handlers Wiki
31 */
32
33namespace VuFind\Session;
34
35use Laminas\Config\Config;
36
37use function get_class;
38use function in_array;
39
40/**
41 * Memcache session handler
42 *
43 * @category VuFind
44 * @package  Session_Handlers
45 * @author   Demian Katz <demian.katz@villanova.edu>
46 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
47 * @link     https://vufind.org/wiki/development:plugins:session_handlers Wiki
48 */
49class Memcache extends AbstractBase
50{
51    /**
52     * Memcache connection
53     *
54     * @var \Memcache
55     */
56    protected $connection;
57
58    /**
59     * Constructor
60     *
61     * @param Config                    $config Session configuration ([Session] section of config.ini)
62     * @param \Memcache|\Memcached|null $client Optional Memcache client object
63     */
64    public function __construct(Config $config = null, object $client = null)
65    {
66        parent::__construct($config);
67        $this->connect($config, $client);
68    }
69
70    /**
71     * Set up the connection to Memcache.
72     *
73     * @param ?Config                   $config Session configuration ([Session] section of config.ini)
74     * @param \Memcache|\Memcached|null $client Optional Memcache client object
75     *
76     * @return void
77     */
78    protected function connect(?Config $config, ?object $client): void
79    {
80        // Set defaults if nothing set in config file.
81        $host = $config->memcache_host ?? 'localhost';
82        $port = $config->memcache_port ?? 11211;
83        $timeout = $config->memcache_connection_timeout ?? 1;
84        $clientClass = $config->memcache_client ?? 'Memcache';
85
86        // Create/validate client object:
87        if (!in_array($clientClass, ['Memcache', 'Memcached'])) {
88            throw new \Exception("Unsupported Memcache client: $clientClass");
89        }
90        $this->connection = $client ?? new $clientClass();
91        if (!($this->connection instanceof $clientClass)) {
92            throw new \Exception(
93                'Unexpected Memcache client class: ' . get_class($this->connection)
94            );
95        }
96
97        // Prepare a connection exception, just in case we need it:
98        $connectionException = new \Exception(
99            "Could not connect to $clientClass (host = {$host}, port = {$port})."
100        );
101
102        // Establish connection:
103        switch ($clientClass) {
104            case 'Memcache':
105                if (!$this->connection->connect($host, $port, $timeout)) {
106                    throw $connectionException;
107                }
108                break;
109            case 'Memcached':
110                $this->connection
111                    ->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $timeout);
112                if (!$this->connection->addServer($host, $port)) {
113                    throw $connectionException;
114                }
115                break;
116        }
117    }
118
119    /**
120     * Read function must return string value always to make save handler work as
121     * expected. Return empty string if there is no data to read.
122     *
123     * @param string $sessId The session ID to read
124     *
125     * @return string
126     */
127    public function read($sessId): string
128    {
129        // For some reason, Memcache tests fail if we do not pass exactly three
130        // parameters to the get method, even though this seems inconsistent with
131        // the documentation. This mechanism makes the tests pass, but may be worth
132        // revisiting in the future.
133        $extraParams = $this->connection instanceof \Memcache ? [null, null] : [];
134        $value = $this->connection
135            ->get("vufind_sessions/{$sessId}", ...$extraParams);
136        return empty($value) ? '' : $value;
137    }
138
139    /**
140     * The destroy handler, this is executed when a session is destroyed with
141     * session_destroy() and takes the session id as its only parameter.
142     *
143     * @param string $sessId The session ID to destroy
144     *
145     * @return bool
146     */
147    public function destroy($sessId): bool
148    {
149        // Perform standard actions required by all session methods:
150        parent::destroy($sessId);
151
152        // Perform Memcache-specific cleanup:
153        return $this->connection->delete("vufind_sessions/{$sessId}");
154    }
155
156    /**
157     * A function that is called internally when session data is to be saved.
158     *
159     * @param string $sessId The current session ID
160     * @param string $data   The session data to write
161     *
162     * @return bool
163     */
164    protected function saveSession($sessId, $data): bool
165    {
166        // Memcached and Memcache have different set() signatures, so we need to
167        // behave differently depending on the class of the connection.
168        if ($this->connection instanceof \Memcached) {
169            return $this->connection->set(
170                "vufind_sessions/{$sessId}",
171                $data,
172                $this->lifetime
173            );
174        }
175        // The third parameter (0) here is $flags, which we do not use.
176        return $this->connection->set(
177            "vufind_sessions/{$sessId}",
178            $data,
179            0,
180            $this->lifetime
181        );
182    }
183}