Skip to content

Commit ca4d46c

Browse files
committed
Add experimental custom memcached client (no extension required)
1 parent f8aeeea commit ca4d46c

4 files changed

Lines changed: 267 additions & 21 deletions

File tree

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
<?php
2+
/**
3+
* This file is part of phpCacheAdmin.
4+
*
5+
* Copyright (c) Róbert Kelčák (https://kelcak.com/)
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace RobiNN\Pca\Dashboards\Memcached\Compatibility;
14+
15+
use RobiNN\Pca\Dashboards\Memcached\MemcachedException;
16+
use RobiNN\Pca\Helpers;
17+
18+
class PHPMem implements CompatibilityInterface {
19+
use CommandTrait;
20+
21+
/**
22+
* @const string PHPMem version.
23+
*/
24+
public const VERSION = '1.0.0';
25+
26+
private string $host;
27+
28+
private int $port;
29+
30+
/**
31+
* @var array<string, int|string>
32+
*/
33+
protected array $server;
34+
35+
/**
36+
* @param array<string, int|string> $server
37+
*/
38+
public function __construct(array $server = []) {
39+
$this->server = $server;
40+
}
41+
42+
/**
43+
* Add a server to the server pool.
44+
*
45+
* @param string $host
46+
* @param int $port
47+
*
48+
* @return bool
49+
*/
50+
public function addServer(string $host, int $port = 11211): bool {
51+
$this->host = $host;
52+
$this->port = $port;
53+
54+
return true;
55+
}
56+
57+
/**
58+
* Semd data to the server.
59+
*
60+
* @param string $command
61+
*
62+
* @return ?string
63+
*/
64+
private function send(string $command): ?string {
65+
if (isset($this->server['path'])) {
66+
$fp = @stream_socket_client('unix://'.$this->host);
67+
} else {
68+
$fp = @fsockopen($this->host, $this->port, $error_code, $error_message, 3);
69+
}
70+
71+
if ($fp === false) {
72+
return null;
73+
}
74+
75+
fwrite($fp, $command."\r\n");
76+
77+
$buffer = '';
78+
79+
while (!feof($fp)) {
80+
$buffer .= fgets($fp, 256);
81+
82+
foreach (['END', 'DELETED', 'NOT_FOUND', 'OK', 'EXISTS', 'ERROR', 'RESET', 'STORED', 'NOT_STORED', 'VERSION'] as $end) {
83+
if (preg_match('/^'.$end.'/imu', $buffer)) {
84+
break 2;
85+
}
86+
}
87+
}
88+
89+
fclose($fp);
90+
91+
return rtrim($buffer, "\r\n");
92+
}
93+
94+
/**
95+
* Store an item.
96+
*
97+
* @param string $key
98+
* @param mixed $value
99+
* @param int $expiration
100+
*
101+
* @return bool
102+
*/
103+
public function set(string $key, $value, int $expiration = 0): bool {
104+
$type = gettype($value);
105+
106+
if (($type !== 'string' && $type !== 'integer' && $type !== 'double' && $type !== 'boolean') === true) {
107+
$value = serialize($value);
108+
}
109+
110+
$raw = $this->send('set'.' '.$key.' '.'0 '.$expiration.' '.strlen($value)."\r\n".$value."\r\n");
111+
112+
if (Helpers::str_starts_with($raw, 'STORED')) {
113+
return true;
114+
}
115+
116+
return false;
117+
}
118+
119+
/**
120+
* Retrieve an item.
121+
*
122+
* @param string $key
123+
*
124+
* @return string|false
125+
*/
126+
public function get(string $key) {
127+
$raw = $this->send('get '.$key);
128+
$lines = explode("\r\n", $raw);
129+
130+
if (Helpers::str_starts_with($raw, 'VALUE') && Helpers::str_ends_with($raw, 'END')) {
131+
return $lines[1];
132+
}
133+
134+
return false;
135+
}
136+
137+
/**
138+
* Delete item from the server.
139+
*
140+
* @param string $key
141+
*
142+
* @return bool
143+
*/
144+
public function delete(string $key): bool {
145+
$raw = $this->send('delete '.$key);
146+
147+
return $raw === 'DELETED';
148+
}
149+
150+
/**
151+
* Invalidate all items in the cache.
152+
*
153+
* @return bool
154+
*/
155+
public function flush(): bool {
156+
$raw = $this->send('flush_all');
157+
158+
return $raw === 'OK';
159+
}
160+
161+
/**
162+
* Check connection.
163+
*
164+
* @return bool
165+
*/
166+
public function isConnected(): bool {
167+
$stats = $this->getServerStats();
168+
169+
return isset($stats['pid']) && $stats['pid'] > 0;
170+
}
171+
172+
/**
173+
* Get server statistics.
174+
*
175+
* @return array<string, mixed>
176+
*/
177+
public function getServerStats(): array {
178+
$raw = $this->send('stats');
179+
$lines = explode("\r\n", $raw);
180+
$line_n = 0;
181+
$stats = [];
182+
183+
while ($lines[$line_n] !== 'END') {
184+
$line = explode(' ', $lines[$line_n]);
185+
array_shift($line); // remove 'STAT' key
186+
[$name, $value] = $line;
187+
188+
$stats[$name] = $value;
189+
190+
++$line_n;
191+
}
192+
193+
return $stats;
194+
}
195+
196+
/**
197+
* Store item.
198+
*
199+
* @param string $key
200+
* @param string $value
201+
* @param int $expiration
202+
*
203+
* @return bool
204+
*/
205+
public function store(string $key, string $value, int $expiration = 0): bool {
206+
return $this->set($key, $value, $expiration);
207+
}
208+
209+
/**
210+
* SASL authentication.
211+
*
212+
* @return void
213+
* @throws MemcachedException
214+
*/
215+
public function sasl(): void {
216+
throw new MemcachedException('PHPMem does not support SASL authentication.');
217+
}
218+
}

src/Dashboards/Memcached/MemcachedDashboard.php

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ public function __construct(Template $template) {
3939
* @return bool
4040
*/
4141
public static function check(): bool {
42-
return extension_loaded('memcached') || extension_loaded('memcache');
42+
return
43+
extension_loaded('memcached') ||
44+
extension_loaded('memcache') ||
45+
class_exists(Compatibility\PHPMem::class);
4346
}
4447

4548
/**
@@ -60,16 +63,18 @@ public function getDashboardInfo(): array {
6063
*
6164
* @param array<string, int|string> $server
6265
*
63-
* @return Compatibility\Memcache|Compatibility\Memcached
66+
* @return Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem
6467
* @throws DashboardException
6568
*/
6669
private function connect(array $server) {
6770
if (extension_loaded('memcached')) {
6871
$memcached = new Compatibility\Memcached($server);
6972
} elseif (extension_loaded('memcache')) {
7073
$memcached = new Compatibility\Memcache($server);
74+
} elseif (class_exists(Compatibility\PHPMem::class)) {
75+
$memcached = new Compatibility\PHPMem($server);
7176
} else {
72-
throw new DashboardException('Memcache(d) extension is not installed.');
77+
throw new DashboardException('Memcache(d) extension or PHPMem client is not installed.');
7378
}
7479

7580
if (isset($server['path'])) {
@@ -96,7 +101,7 @@ private function connect(array $server) {
96101
}
97102

98103
if (!$memcached->isConnected()) {
99-
throw new DashboardException(sprintf('Failed to connect to Memcache(d) server %s.', $memcached_server));
104+
throw new DashboardException(sprintf('Failed to connect to Memcached server %s.', $memcached_server));
100105
}
101106

102107
return $memcached;
@@ -163,11 +168,18 @@ public function showPanels(): string {
163168
return '';
164169
}
165170

166-
$memcached = extension_loaded('memcached') ? 'd' : '';
171+
if (extension_loaded('memcached') || extension_loaded('memcache')) {
172+
$memcached = extension_loaded('memcached') ? 'd' : '';
173+
$title = 'PHP <span class="font-semibold">Memcache'.$memcached.'</span> extension';
174+
$version = phpversion('memcache'.$memcached);
175+
} elseif (class_exists(Compatibility\PHPMem::class)) {
176+
$title = 'PHPMem';
177+
$version = Compatibility\PHPMem::VERSION;
178+
}
167179

168180
return $this->template->render('partials/info', [
169-
'title' => 'PHP <span class="font-semibold">Memcache'.$memcached.'</span> extension',
170-
'extension_version' => phpversion('memcache'.$memcached),
181+
'title' => $title ?? null,
182+
'extension_version' => $version ?? null,
171183
'info' => $this->info(),
172184
]);
173185
}

src/Dashboards/Memcached/MemcachedTrait.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
use RobiNN\Pca\Config;
1616
use RobiNN\Pca\Dashboards\DashboardException;
17-
use RobiNN\Pca\Dashboards\Memcached\Compatibility\Memcache;
18-
use RobiNN\Pca\Dashboards\Memcached\Compatibility\Memcached;
1917
use RobiNN\Pca\Format;
2018
use RobiNN\Pca\Helpers;
2119
use RobiNN\Pca\Http;
@@ -54,7 +52,7 @@ private function serverInfo(array $servers): array {
5452
/**
5553
* Delete all keys.
5654
*
57-
* @param Memcache|Memcached $memcached
55+
* @param Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem $memcached
5856
*
5957
* @return string
6058
*/
@@ -71,7 +69,7 @@ private function deleteAllKeys($memcached): string {
7169
/**
7270
* Delete key or selected keys.
7371
*
74-
* @param Memcache|Memcached $memcached
72+
* @param Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem $memcached
7573
*
7674
* @return string
7775
* @throws MemcachedException
@@ -107,9 +105,11 @@ private function moreInfo(array $servers): string {
107105
$server_data = $servers[$id];
108106

109107
$info = $this->connect($server_data)->getServerStats();
110-
$memcached = extension_loaded('memcached') ? 'd' : '';
111108

112-
$info += Helpers::getExtIniInfo('memcache'.$memcached);
109+
if (extension_loaded('memcached') || extension_loaded('memcache')) {
110+
$memcached = extension_loaded('memcached') ? 'd' : '';
111+
$info += Helpers::getExtIniInfo('memcache'.$memcached);
112+
}
113113

114114
return $this->template->render('partials/info_table', [
115115
'panel_title' => $server_data['name'] ?? $server_data['host'].':'.$server_data['port'],
@@ -123,7 +123,7 @@ private function moreInfo(array $servers): string {
123123
/**
124124
* Get all keys with data.
125125
*
126-
* @param Memcache|Memcached $memcached
126+
* @param Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem $memcached
127127
*
128128
* @return array<int, array<string, string|int>>
129129
* @throws MemcachedException
@@ -145,7 +145,7 @@ private function getAllKeys($memcached): array {
145145
/**
146146
* Main dashboard content.
147147
*
148-
* @param Memcache|Memcached $memcached
148+
* @param Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem $memcached
149149
*
150150
* @return string
151151
* @throws MemcachedException
@@ -171,7 +171,7 @@ private function mainDashboard($memcached): string {
171171
/**
172172
* View key value.
173173
*
174-
* @param Memcache|Memcached $memcached
174+
* @param Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem $memcached
175175
*
176176
* @return string
177177
* @throws MemcachedException
@@ -219,7 +219,7 @@ private function viewKey($memcached): string {
219219
/**
220220
* Import key.
221221
*
222-
* @param Memcache|Memcached $memcached
222+
* @param Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem $memcached
223223
*
224224
* @return void
225225
* @throws MemcachedException
@@ -241,7 +241,7 @@ private function import($memcached): void {
241241
/**
242242
* Add/edit form.
243243
*
244-
* @param Memcache|Memcached $memcached
244+
* @param Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem $memcached
245245
*
246246
* @return string
247247
* @throws MemcachedException
@@ -276,7 +276,7 @@ private function form($memcached): string {
276276
/**
277277
* Save key.
278278
*
279-
* @param Memcache|Memcached $memcached
279+
* @param Compatibility\Memcache|Compatibility\Memcached|Compatibility\PHPMem $memcached
280280
*
281281
* @return void
282282
*/

0 commit comments

Comments
 (0)