adad
This commit is contained in:
132
src/App/RedisClient.php
Normal file
132
src/App/RedisClient.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user