This commit is contained in:
2026-03-05 23:16:49 +01:00
parent d76abde68d
commit 2ccf71915f
7 changed files with 461 additions and 19 deletions

132
src/App/RedisClient.php Normal file
View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace App;
final class RedisClient
{
private string $host;
private int $port;
private ?string $password;
private int $db;
private ?\Socket $socket = null;
private $stream = null;
public function __construct(string $host, int $port = 6379, ?string $password = null, int $db = 0)
{
$this->host = $host;
$this->port = $port;
$this->password = $password;
$this->db = $db;
}
private function connect(): void
{
if ($this->stream) {
return;
}
$errNo = 0;
$errStr = '';
$this->stream = stream_socket_client(
"tcp://{$this->host}:{$this->port}",
$errNo,
$errStr,
3.0,
STREAM_CLIENT_CONNECT
);
if (!$this->stream) {
throw new \RuntimeException("Redis connect failed: {$errStr}");
}
stream_set_timeout($this->stream, 3);
if ($this->password !== null && $this->password !== '') {
$this->command(['AUTH', $this->password]);
}
if ($this->db > 0) {
$this->command(['SELECT', (string)$this->db]);
}
}
public function command(array $args)
{
$this->connect();
$payload = '*' . count($args) . "\r\n";
foreach ($args as $arg) {
$arg = (string)$arg;
$payload .= '$' . strlen($arg) . "\r\n" . $arg . "\r\n";
}
fwrite($this->stream, $payload);
return $this->readResponse();
}
private function readResponse()
{
$line = $this->readLine();
if ($line === '') {
throw new \RuntimeException('Redis empty response');
}
$type = $line[0];
$payload = substr($line, 1);
switch ($type) {
case '+':
return $payload;
case '-':
throw new \RuntimeException('Redis error: ' . $payload);
case ':':
return (int)$payload;
case '$':
$len = (int)$payload;
if ($len === -1) {
return null;
}
$data = $this->readBytes($len);
$this->readLine();
return $data;
case '*':
$count = (int)$payload;
if ($count === -1) {
return null;
}
$items = [];
for ($i = 0; $i < $count; $i++) {
$items[] = $this->readResponse();
}
return $items;
default:
throw new \RuntimeException('Redis protocol error');
}
}
private function readLine(): string
{
$line = '';
while (!feof($this->stream)) {
$chunk = fgets($this->stream);
if ($chunk === false) {
break;
}
$line .= $chunk;
if (str_ends_with($line, "\r\n")) {
break;
}
}
return rtrim($line, "\r\n");
}
private function readBytes(int $length): string
{
$data = '';
$remaining = $length;
while ($remaining > 0 && !feof($this->stream)) {
$chunk = fread($this->stream, $remaining);
if ($chunk === false || $chunk === '') {
break;
}
$data .= $chunk;
$remaining -= strlen($chunk);
}
return $data;
}
}