From 85e2ad74f8e0c55844a165b94de643cc99742329 Mon Sep 17 00:00:00 2001 From: JanHuang <384099566@qq.com> Date: Tue, 27 Oct 2026 17:33:13 +0800 Subject: [PATCH] =?UTF-8?q?refactor(session):=20RedisSessionHandler=20?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E4=BE=9D=E8=B5=96=20\Redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 RedisClientInterface 与 PhpRedisSessionClient 内部抽象 - 构造器强制 \Redis 对象 - 更新测试为 \Redis mock 并清理 notice - CI 增加 redis 扩展 --- .github/workflows/ci.yml | 2 +- README.md | 3 +- src/Session/Handler/PhpRedisSessionClient.php | 40 --------- src/Session/Handler/RedisClientInterface.php | 21 ----- src/Session/Handler/RedisSessionHandler.php | 22 +++-- tests/session/RedisSessionHandlerTest.php | 88 +++++++++---------- 6 files changed, 57 insertions(+), 119 deletions(-) delete mode 100644 src/Session/Handler/PhpRedisSessionClient.php delete mode 100644 src/Session/Handler/RedisClientInterface.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23cfa69..c941d57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: curl, zlib, swoole + extensions: curl, zlib, swoole, redis coverage: pcov tools: composer:v2 - name: Validate composer.json diff --git a/README.md b/README.md index 9209b22..29badf1 100755 --- a/README.md +++ b/README.md @@ -380,7 +380,6 @@ use FastD\Http\Session\SessionMiddleware; use FastD\Http\Session\Handler\NativeFileSessionHandler; use FastD\Http\Session\Handler\SwooleTableSessionHandler; use FastD\Http\Session\Handler\RedisSessionHandler; -use FastD\Http\Session\Handler\PhpRedisSessionClient; // CGI 运行时:文件存储 $middleware = new SessionMiddleware(new NativeFileSessionHandler()); @@ -392,7 +391,7 @@ $middleware = new SessionMiddleware(new SwooleTableSessionHandler(1440, 4096)); $redis = new \Redis(); $redis->connect('127.0.0.1', 6379); $middleware = new SessionMiddleware( - new RedisSessionHandler(new PhpRedisSessionClient($redis), 1440, 'fastd:session:') + new RedisSessionHandler($redis, 1440, 'fastd:session:') ); // 将 $middleware 注册进 PSR-15 管道后,即可在后续处理器中读取会话 diff --git a/src/Session/Handler/PhpRedisSessionClient.php b/src/Session/Handler/PhpRedisSessionClient.php deleted file mode 100644 index 4c32140..0000000 --- a/src/Session/Handler/PhpRedisSessionClient.php +++ /dev/null @@ -1,40 +0,0 @@ -redis->get($key); - - return is_string($value) || $value === false ? $value : false; - } - - public function setex(string $key, int $ttl, string $value): bool - { - return (bool) $this->redis->setex($key, $ttl, $value); - } - - public function del(string $key): int - { - return (int) $this->redis->del($key); - } - - public function exists(string $key): int - { - return (int) $this->redis->exists($key); - } -} diff --git a/src/Session/Handler/RedisClientInterface.php b/src/Session/Handler/RedisClientInterface.php deleted file mode 100644 index 1f38e8c..0000000 --- a/src/Session/Handler/RedisClientInterface.php +++ /dev/null @@ -1,21 +0,0 @@ -client->get($this->key($sessionId)); + $content = $this->redis->get($this->key($sessionId)); - if ($content === false) { + if (!is_string($content)) { return []; } @@ -45,17 +49,17 @@ public function write(string $sessionId, array $data): bool return false; } - return $this->client->setex($this->key($sessionId), $this->ttl, $content); + return (bool) $this->redis->setex($this->key($sessionId), $this->ttl, $content); } public function destroy(string $sessionId): bool { - return $this->client->del($this->key($sessionId)) > 0; + return (int) $this->redis->del($this->key($sessionId)) > 0; } public function exists(string $sessionId): bool { - return $this->client->exists($this->key($sessionId)) > 0; + return (bool) $this->redis->exists($this->key($sessionId)); } /** diff --git a/tests/session/RedisSessionHandlerTest.php b/tests/session/RedisSessionHandlerTest.php index 13b2784..52101ee 100644 --- a/tests/session/RedisSessionHandlerTest.php +++ b/tests/session/RedisSessionHandlerTest.php @@ -4,89 +4,85 @@ namespace FastD\Http\Tests; -use FastD\Http\Session\Handler\RedisClientInterface; use FastD\Http\Session\Handler\RedisSessionHandler; +use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations; use PHPUnit\Framework\TestCase; +use Redis; -final class FakeRedisClient implements RedisClientInterface +#[AllowMockObjectsWithoutExpectations] +final class RedisSessionHandlerTest extends TestCase { - /** @var array */ - public array $rows = []; - - public function get(string $key): string|false + protected function setUp(): void { - $row = $this->rows[$key] ?? null; - if ($row === null || $row['expires'] <= time()) { - unset($this->rows[$key]); - - return false; + if (!extension_loaded('redis')) { + $this->markTestSkipped('ext-redis is required.'); } - - return $row['value']; } - public function setex(string $key, int $ttl, string $value): bool + public function testConstructorRequiresRedisObject(): void { - $this->rows[$key] = ['value' => $value, 'expires' => time() + $ttl]; + $parameter = (new \ReflectionMethod(RedisSessionHandler::class, '__construct'))->getParameters()[0]; - return true; + $this->assertInstanceOf(\ReflectionNamedType::class, $parameter->getType()); + $this->assertSame(Redis::class, $parameter->getType()->getName()); } - public function del(string $key): int + public function testReadDecodesJson(): void { - $exists = isset($this->rows[$key]); - unset($this->rows[$key]); + $redis = $this->createMock(Redis::class); + $redis->expects($this->once()) + ->method('get') + ->with('fastd:session:abc') + ->willReturn(json_encode(['user' => 'jan'])); - return $exists ? 1 : 0; + $this->assertSame(['user' => 'jan'], (new RedisSessionHandler($redis))->read('abc')); } - public function exists(string $key): int + public function testWriteUsesSetexWithTtlAndJson(): void { - return $this->get($key) === false ? 0 : 1; - } -} + $redis = $this->createMock(Redis::class); + $redis->expects($this->once()) + ->method('setex') + ->with('fastd:session:abc', 60, json_encode(['user' => 'jan'])) + ->willReturn(true); -final class RedisSessionHandlerTest extends TestCase -{ - public function testWriteReadRoundtrip(): void - { - $client = new FakeRedisClient(); - $handler = new RedisSessionHandler($client, 60); + $handler = new RedisSessionHandler($redis, 60); - $this->assertTrue($handler->write('abc', ['user' => 'jan', 'nested' => ['a' => 1]])); - $this->assertTrue($handler->exists('abc')); - $this->assertSame(['user' => 'jan', 'nested' => ['a' => 1]], $handler->read('abc')); - $this->assertArrayHasKey('fastd:session:abc', $client->rows); + $this->assertTrue($handler->write('abc', ['user' => 'jan'])); } - public function testDestroyRemovesSession(): void + public function testDestroyAndExistsUsePrefixedKey(): void { - $handler = new RedisSessionHandler(new FakeRedisClient(), 60); - $handler->write('abc', ['user' => 'jan']); + $redis = $this->createMock(Redis::class); + $redis->expects($this->once())->method('del')->with('fastd:session:abc')->willReturn(1); + $redis->expects($this->once())->method('exists')->with('fastd:session:abc')->willReturn(1); + + $handler = new RedisSessionHandler($redis); $this->assertTrue($handler->destroy('abc')); - $this->assertFalse($handler->exists('abc')); - $this->assertFalse($handler->destroy('abc')); + $this->assertTrue($handler->exists('abc')); } public function testInvalidJsonReturnsEmptyArray(): void { - $client = new FakeRedisClient(); - $client->setex('fastd:session:bad', 60, '{invalid'); + $redis = $this->createMock(Redis::class); + $redis->method('get')->willReturn('{invalid'); - $this->assertSame([], (new RedisSessionHandler($client))->read('bad')); + $this->assertSame([], (new RedisSessionHandler($redis))->read('bad')); } - public function testOversizedPayloadIsRejected(): void + public function testOversizedPayloadIsRejectedBeforeRedisWrite(): void { - $handler = new RedisSessionHandler(new FakeRedisClient(), 60, 'fastd:session:', 32); + $redis = $this->createMock(Redis::class); + $redis->expects($this->never())->method('setex'); + + $handler = new RedisSessionHandler($redis, 60, 'fastd:session:', 32); $this->assertFalse($handler->write('abc', ['blob' => str_repeat('x', 64)])); - $this->assertFalse($handler->exists('abc')); } public function testGcIsNoopBecauseRedisExpiresKeys(): void { - $this->assertSame(0, (new RedisSessionHandler(new FakeRedisClient()))->gc()); + $this->assertSame(0, (new RedisSessionHandler($this->createMock(Redis::class)))->gc()); } }