From 2e22d06aaabcb15a3fad1d7deef6ec08eae972aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Jensen?= Date: Thu, 20 Feb 2025 14:37:34 +0100 Subject: [PATCH] phpstan level 7 --- Makefile | 2 +- phpstan.neon | 3 ++- src/Connection.php | 11 +++++++--- src/Frame/FrameHandler.php | 16 +++++++++++--- src/Http/Message.php | 3 +-- src/Http/ServerRequest.php | 2 +- src/Message/Close.php | 4 ++-- src/Message/Message.php | 1 + src/Message/MessageHandler.php | 24 +++++++++++++-------- src/Middleware/MiddlewareHandler.php | 5 +++-- src/Middleware/ProcessHttpStack.php | 2 ++ src/Middleware/ProcessOutgoingInterface.php | 5 +++++ src/Middleware/ProcessStack.php | 7 ++++-- tests/mock/EchoLog.php | 3 ++- tests/mock/MockStreamTrait.php | 24 +++++++++++++++++++-- tests/suites/client/ClientTest.php | 8 ++++--- tests/suites/client/ConfigTest.php | 2 +- tests/suites/connection/ConnectionTest.php | 2 +- tests/suites/frame/FrameHandlerTest.php | 12 ++++++----- tests/suites/http/RequestTest.php | 1 + tests/suites/http/ServerRequestTest.php | 2 +- tests/suites/middleware/CallbackTest.php | 7 +++++- tests/suites/server/ConfigTest.php | 4 ++-- 23 files changed, 107 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index d6f6f61..c9e4b03 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ cs: composer.lock ./vendor/bin/phpcs stan: composer.lock - ./vendor/bin/phpstan analyse + ./vendor/bin/phpstan analyse --memory-limit 256M coverage: composer.lock build XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml diff --git a/phpstan.neon b/phpstan.neon index d7f8277..3a1f859 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,5 @@ parameters: - level: 6 + level: 7 paths: - src + - tests diff --git a/src/Connection.php b/src/Connection.php index 1f4f63a..6083b4d 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -143,11 +143,11 @@ public function setFrameSize(int $frameSize): self /** * Get frame size. - * @return int Frame size in bytes + * @return int<1, max> Frame size in bytes */ public function getFrameSize(): int { - return $this->frameSize; + return max(1, $this->frameSize); } /** @@ -289,7 +289,12 @@ public function send(Message $message): Message return $this->pushMessage($message); } - // Push a message to stream + /** + * Push a message to stream. + * @template T of Message + * @param T $message + * @return T + */ public function pushMessage(Message $message): Message { try { diff --git a/src/Frame/FrameHandler.php b/src/Frame/FrameHandler.php index ab2727a..318e569 100644 --- a/src/Frame/FrameHandler.php +++ b/src/Frame/FrameHandler.php @@ -53,7 +53,7 @@ public function pull(): Frame { // Read the frame "header" first, two bytes. $data = $this->read(2); - list ($byte_1, $byte_2) = array_values(unpack('C*', $data)); + list ($byte_1, $byte_2) = array_values($this->unpack('C*', $data)); $final = (bool)($byte_1 & 0b10000000); // Final fragment marker. $rsv1 = (bool)($byte_1 & 0b01000000); $rsv2 = (bool)($byte_1 & 0b00100000); @@ -75,10 +75,10 @@ public function pull(): Frame if ($payload_length > 125) { if ($payload_length === 126) { $data = $this->read(2); // 126: Payload length is a 16-bit unsigned int - $payload_length = current(unpack('n', $data)); + $payload_length = current($this->unpack('n', $data)); } else { $data = $this->read(8); // 127: Payload length is a 64-bit unsigned int - $payload_length = current(unpack('J', $data)); + $payload_length = current($this->unpack('J', $data)); } } @@ -197,4 +197,14 @@ private function write(string $data): int } return $written; } + + /** @return array */ + private function unpack(string $format, string $string): array + { + $result = unpack($format, $string); + if ($result === false) { + throw new RuntimeException('Could not parse message header'); + } + return $result; + } } diff --git a/src/Http/Message.php b/src/Http/Message.php index dac76e9..1f062a5 100644 --- a/src/Http/Message.php +++ b/src/Http/Message.php @@ -174,8 +174,7 @@ private function handleHeader(string $name, mixed $value): void if (!is_string($content) && !is_numeric($content)) { throw new InvalidArgumentException("Invalid header value(s) provided."); } - $content = trim($content); - $this->headers[strtolower($name)][$name][] = $content; + $this->headers[strtolower($name)][$name][] = trim((string)$content); } } } diff --git a/src/Http/ServerRequest.php b/src/Http/ServerRequest.php index 10e8fed..7478c61 100644 --- a/src/Http/ServerRequest.php +++ b/src/Http/ServerRequest.php @@ -49,7 +49,7 @@ public function withCookieParams(array $cookies): self /** * Retrieves the deserialized query string arguments, if any. - * @return array + * @return array */ public function getQueryParams(): array { diff --git a/src/Message/Close.php b/src/Message/Close.php index b5ebe6a..7cb65fb 100644 --- a/src/Message/Close.php +++ b/src/Message/Close.php @@ -37,7 +37,7 @@ public function getPayload(): string $status_binstr = sprintf('%016b', $this->status); $status_str = ''; foreach (str_split($status_binstr, 8) as $binstr) { - $status_str .= chr(bindec($binstr)); + $status_str .= chr((int)bindec($binstr)); } return $status_str . $this->content; } @@ -47,7 +47,7 @@ public function setPayload(string $payload = ''): void $this->status = 0; $this->content = ''; if (strlen($payload) > 0) { - $this->status = current(unpack('n', $payload)); + $this->status = current(unpack('n', $payload) ?: []); } if (strlen($payload) > 2) { $this->content = substr($payload, 2); diff --git a/src/Message/Message.php b/src/Message/Message.php index 042d69a..bb96259 100644 --- a/src/Message/Message.php +++ b/src/Message/Message.php @@ -73,6 +73,7 @@ public function setPayload(string $payload = ''): void /** * Split messages into frames + * @param int<1, max> $frameSize * @return array */ public function getFrames(int $frameSize = 4096): array diff --git a/src/Message/MessageHandler.php b/src/Message/MessageHandler.php index 6366839..1fdc916 100644 --- a/src/Message/MessageHandler.php +++ b/src/Message/MessageHandler.php @@ -29,8 +29,8 @@ class MessageHandler implements LoggerAwareInterface, Stringable private FrameHandler $frameHandler; private LoggerInterface $logger; - /** @var array{opcode: string, payload: string, frames: int}|null $readBuffer */ - private array|null $readBuffer = null; + /** @var object{opcode: string, payload: string, frames: int}|null $readBuffer */ + private object|null $readBuffer = null; public function __construct(FrameHandler $frameHandler) { @@ -44,7 +44,13 @@ public function setLogger(LoggerInterface $logger): void $this->frameHandler->setLogger($logger); } - // Push message + /** + * Push message + * @template T of Message + * @param T $message + * @param int<1, max> $size + * @return T + */ public function push(Message $message, int $size = self::DEFAULT_SIZE): Message { $frames = $message->getFrames($size); @@ -70,26 +76,26 @@ public function pull(): Message $payload = $frame->getPayload(); // Continuation and factual opcode - $payload_opcode = $continuation ? $this->readBuffer['opcode'] : $opcode; + $payload_opcode = $continuation ? $this->readBuffer->opcode : $opcode; // First continuation frame, create buffer if (!$final && !$continuation) { - $this->readBuffer = ['opcode' => $opcode, 'payload' => $payload, 'frames' => 1]; + $this->readBuffer = (object)['opcode' => $opcode, 'payload' => $payload, 'frames' => 1]; continue; // Continue reading } // Subsequent continuation frames, add to buffer if ($continuation) { - $this->readBuffer['payload'] .= $payload; - $this->readBuffer['frames']++; + $this->readBuffer->payload .= $payload; + $this->readBuffer->frames++; } } while (!$final); // Final, return payload $frames = 1; if ($continuation) { - $payload = $this->readBuffer['payload']; - $frames = $this->readBuffer['frames']; + $payload = $this->readBuffer->payload; + $frames = $this->readBuffer->frames; $this->readBuffer = null; } diff --git a/src/Middleware/MiddlewareHandler.php b/src/Middleware/MiddlewareHandler.php index 035ea73..7cb891e 100644 --- a/src/Middleware/MiddlewareHandler.php +++ b/src/Middleware/MiddlewareHandler.php @@ -138,9 +138,10 @@ public function processIncoming(Connection $connection): Message /** * Process middlewares for outgoing messages. + * @template T of Message * @param Connection $connection - * @param Message $message - * @return Message + * @param T $message + * @return T */ public function processOutgoing(Connection $connection, Message $message): Message { diff --git a/src/Middleware/ProcessHttpStack.php b/src/Middleware/ProcessHttpStack.php index c384e88..68a9ab0 100644 --- a/src/Middleware/ProcessHttpStack.php +++ b/src/Middleware/ProcessHttpStack.php @@ -45,6 +45,7 @@ public function __construct(Connection $connection, HttpHandler $httpHandler, ar */ public function handleHttpIncoming(): MessageInterface { + /** @var ProcessHttpIncomingInterface|null $processor */ $processor = array_shift($this->processors); if ($processor) { return $processor->processHttpIncoming($this, $this->connection); @@ -59,6 +60,7 @@ public function handleHttpIncoming(): MessageInterface */ public function handleHttpOutgoing(MessageInterface $message): MessageInterface { + /** @var ProcessHttpOutgoingInterface|null $processor */ $processor = array_shift($this->processors); if ($processor) { return $processor->processHttpOutgoing($this, $this->connection, $message); diff --git a/src/Middleware/ProcessOutgoingInterface.php b/src/Middleware/ProcessOutgoingInterface.php index 311ac0f..3e67702 100644 --- a/src/Middleware/ProcessOutgoingInterface.php +++ b/src/Middleware/ProcessOutgoingInterface.php @@ -16,5 +16,10 @@ */ interface ProcessOutgoingInterface extends MiddlewareInterface { + /** + * @template T of Message + * @param T $message + * @return T + */ public function processOutgoing(ProcessStack $stack, Connection $connection, Message $message): Message; } diff --git a/src/Middleware/ProcessStack.php b/src/Middleware/ProcessStack.php index d338898..142e7b7 100644 --- a/src/Middleware/ProcessStack.php +++ b/src/Middleware/ProcessStack.php @@ -47,6 +47,7 @@ public function __construct(Connection $connection, MessageHandler $messageHandl */ public function handleIncoming(): Message { + /** @var ProcessIncomingInterface|null $processor */ $processor = array_shift($this->processors); if ($processor) { return $processor->processIncoming($this, $this->connection); @@ -56,11 +57,13 @@ public function handleIncoming(): Message /** * Process middleware for outgoing message. - * @param Message $message - * @return Message + * @template T of Message + * @param T $message + * @return T */ public function handleOutgoing(Message $message): Message { + /** @var ProcessOutgoingInterface|null $processor */ $processor = array_shift($this->processors); if ($processor) { return $processor->processOutgoing($this, $this->connection, $message); diff --git a/tests/mock/EchoLog.php b/tests/mock/EchoLog.php index 0a02575..a168659 100644 --- a/tests/mock/EchoLog.php +++ b/tests/mock/EchoLog.php @@ -26,7 +26,8 @@ public function log($level, $message, array $context = []) echo str_pad($level, 8) . " | {$message} {$context_string}\n"; } - public function interpolate($message, array $context = []) + /** @param array $context */ + public function interpolate(string $message, array $context = []): string { // Build a replacement array with braces around the context keys $replace = []; diff --git a/tests/mock/MockStreamTrait.php b/tests/mock/MockStreamTrait.php index 33d6d2a..272d6f8 100644 --- a/tests/mock/MockStreamTrait.php +++ b/tests/mock/MockStreamTrait.php @@ -7,20 +7,31 @@ namespace WebSocket\Test; +use Phrity\Net\Mock\Stack\{ + ExpectSocketClientTrait, + ExpectSocketServerTrait, + StackItem +}; use Phrity\Net\Mock\StreamCollection; -use Phrity\Net\Mock\Stack\StackItem; /** * This trait is used by phpunit tests to mock and track various socket/stream calls. */ trait MockStreamTrait { + use ExpectSocketClientTrait; + use ExpectSocketServerTrait; + + /** @var array $stack */ private array $stack = []; private string $last_ws_key = ''; /* ---------- WebSocket Client combinded asserts --------------------------------------------------------------- */ + /** + * @param array $context + */ private function expectWsClientConnect( string $scheme = 'tcp', string $host = 'localhost', @@ -83,7 +94,7 @@ private function expectWsClientPerformHandshake( $this->expectSocketStreamWrite()->addAssert( function (string $method, array $params) use ($host, $path, $headers): void { preg_match('/Sec-WebSocket-Key: ([\S]*)\r\n/', $params[0], $m); - $this->last_ws_key = $m[1]; + $this->last_ws_key = $m[1] ?? ''; $this->assertEquals( "GET {$path} HTTP/1.1\r\nHost: {$host}\r\nUser-Agent: websocket-client-php\r\nConnection: Upgrade" . "\r\nUpgrade: websocket\r\nSec-WebSocket-Key: {$this->last_ws_key}\r\nSec-WebSocket-Version: 13" @@ -123,6 +134,9 @@ function (string $method, array $params) use ($host, $path, $headers): void { /* ---------- WebSocket Server combinded asserts --------------------------------------------------------------- */ + /** + * @param array $context + */ private function expectWsServerSetup(string $scheme = 'tcp', int $port = 8000, array $context = []): void { $this->expectStreamFactoryCreateSocketServer()->addAssert(function ($method, $params) use ($scheme, $port) { @@ -145,6 +159,9 @@ private function expectWsServerSetup(string $scheme = 'tcp', int $port = 8000, a }); } + /** + * @param array $keys + */ private function expectWsSelectConnections(array $keys = []): StackItem { $this->expectStreamCollectionWaitRead()->setReturn(function ($params, $default, $collection) use ($keys) { @@ -164,6 +181,9 @@ private function expectWsSelectConnections(array $keys = []): StackItem return $last; } + /** + * @param array $headers + */ private function expectWsServerPerformHandshake( string $host = 'localhost:8000', string $path = '/my/mock/path', diff --git a/tests/suites/client/ClientTest.php b/tests/suites/client/ClientTest.php index a057cd8..3055ace 100644 --- a/tests/suites/client/ClientTest.php +++ b/tests/suites/client/ClientTest.php @@ -159,6 +159,7 @@ public function testPayload128(): void $this->expectWsClientPerformHandshake(); $client->connect(); + /** @var string $payload */ $payload = file_get_contents(__DIR__ . '/../../mock/payload.128.txt'); // Sending message @@ -205,6 +206,7 @@ public function testPayload65536(): void $this->expectWsClientPerformHandshake(); $client->connect(); + /** @var string $payload */ $payload = file_get_contents(__DIR__ . '/../../mock/payload.65536.txt'); // Sending message @@ -986,7 +988,7 @@ public function testAlreadyStarted(): void $client->start(); $this->expectSocketStreamClose(); - unset($server); + unset($client); } public function testRunConnectionClosedException(): void @@ -1007,7 +1009,7 @@ public function testRunConnectionClosedException(): void $this->expectWsSelectConnections(['localhost:8000']); $this->expectSocketStreamRead()->addAssert(function (string $method, array $params) { $this->assertEquals(2, $params[0]); - })->setReturn(function () use ($client) { + })->setReturn(function () { throw new ConnectionClosedException(); }); $this->expectSocketStreamIsConnected(); @@ -1036,7 +1038,7 @@ public function testRunClientException(): void $this->expectWsSelectConnections(['localhost:8000']); $this->expectSocketStreamRead()->addAssert(function (string $method, array $params) { $this->assertEquals(2, $params[0]); - })->setReturn(function () use ($client) { + })->setReturn(function () { throw new ClientException(); }); $this->expectSocketStreamIsConnected(); diff --git a/tests/suites/client/ConfigTest.php b/tests/suites/client/ConfigTest.php index 661017f..1aa8344 100644 --- a/tests/suites/client/ConfigTest.php +++ b/tests/suites/client/ConfigTest.php @@ -132,10 +132,10 @@ public function testUriInstanceWssDefaultPort(): void unset($client); } + /** @return array */ public static function uriStringAuthorizationDataProvider(): array { $encoded = urlencode('7{v^pF8;uPK.6VWu'); - return [ [ 'usename:password', diff --git a/tests/suites/connection/ConnectionTest.php b/tests/suites/connection/ConnectionTest.php index 18cc31a..6f255c5 100644 --- a/tests/suites/connection/ConnectionTest.php +++ b/tests/suites/connection/ConnectionTest.php @@ -80,7 +80,7 @@ public function testCreate(): void $this->assertSame($connection, $connection->setTimeout(10)); $this->assertEquals(10, $connection->getTimeout()); - $this->assertEmpty($connection->setLogger(new NullLogger())); + $connection->setLogger(new NullLogger()); $this->assertSame($connection, $connection->setFrameSize(64)); $this->assertEquals(64, $connection->getFrameSize()); $this->assertSame($connection, $connection->addMiddleware(new Callback())); diff --git a/tests/suites/frame/FrameHandlerTest.php b/tests/suites/frame/FrameHandlerTest.php index 64372bd..429acdc 100644 --- a/tests/suites/frame/FrameHandlerTest.php +++ b/tests/suites/frame/FrameHandlerTest.php @@ -55,7 +55,7 @@ public function testPushUnmaskedFrame(): void $this->assertEquals(base64_decode('gQxUZXh0IG1lc3NhZ2U='), $params[0]); $this->assertEquals('Text message', substr($params[0], 2)); }); - $written = $handler->push($frame, false); + $written = $handler->push($frame); $this->assertEquals(14, $written); $this->assertEquals('WebSocket\Frame\FrameHandler', "{$handler}"); @@ -103,7 +103,7 @@ public function testPushMaskedFrame(): void $this->expectSocketStreamWrite()->addAssert(function ($method, $params) { $this->assertEquals(18, strlen($params[0])); }); - $written = $handler->push($frame, true); + $written = $handler->push($frame); $this->assertEquals(18, $written); fclose($temp); @@ -144,6 +144,7 @@ public function testPullMaskedFrame(): void public function testPushPayload128(): void { $temp = tmpfile(); + /** @var string $payload */ $payload = file_get_contents(__DIR__ . '/../../mock/payload.128.txt'); $this->expectSocketStream(); @@ -159,7 +160,7 @@ public function testPushPayload128(): void $this->assertEquals(base64_decode('AIA='), substr($params[0], 2, 2)); $this->assertEquals($payload, substr($params[0], 4)); }); - $written = $handler->push($frame, false); + $written = $handler->push($frame); $this->assertEquals(132, $written); fclose($temp); @@ -202,6 +203,7 @@ public function testPullPayload128(): void public function testPushPayload65536(): void { $temp = tmpfile(); + /** @var string $payload */ $payload = file_get_contents(__DIR__ . '/../../mock/payload.65536.txt'); $this->expectSocketStream(); @@ -217,7 +219,7 @@ public function testPushPayload65536(): void $this->assertEquals(base64_decode('AAAAAAABAAA='), substr($params[0], 2, 8)); $this->assertEquals($payload, substr($params[0], 10)); }); - $written = $handler->push($frame, false); + $written = $handler->push($frame); $this->assertEquals(65546, $written); fclose($temp); @@ -274,7 +276,7 @@ public function testWriteError(): void $this->expectException(RuntimeException::class); $this->expectExceptionCode(0); $this->expectExceptionMessage("Could only write 0 out of 16 bytes."); - $handler->push($frame, false); + $handler->push($frame); fclose($temp); } diff --git a/tests/suites/http/RequestTest.php b/tests/suites/http/RequestTest.php index 4caf409..032a31a 100644 --- a/tests/suites/http/RequestTest.php +++ b/tests/suites/http/RequestTest.php @@ -258,6 +258,7 @@ public static function provideInvalidHeaderValues(): Generator yield [[[0]]]; } + /** @param array $expected */ #[DataProvider('provideValidHeaderValues')] public function testHeaderValueValidVariants(mixed $value, array $expected): void { diff --git a/tests/suites/http/ServerRequestTest.php b/tests/suites/http/ServerRequestTest.php index dd06ca2..71238db 100644 --- a/tests/suites/http/ServerRequestTest.php +++ b/tests/suites/http/ServerRequestTest.php @@ -134,7 +134,7 @@ public function testGetParsedBodyError(): void $this->expectException(BadMethodCallException::class); $this->expectExceptionCode(0); $this->expectExceptionMessage('Not implemented.'); - $request->getParsedBody([]); + $request->getParsedBody(); } public function testWithParsedBodyError(): void diff --git a/tests/suites/middleware/CallbackTest.php b/tests/suites/middleware/CallbackTest.php index f855d17..9fee338 100644 --- a/tests/suites/middleware/CallbackTest.php +++ b/tests/suites/middleware/CallbackTest.php @@ -15,7 +15,10 @@ use Psr\Log\NullLogger; use Stringable; use WebSocket\Connection; -use WebSocket\Http\Response; +use WebSocket\Http\{ + Request, + Response, +}; use WebSocket\Message\Text; use WebSocket\Middleware\Callback; @@ -128,6 +131,7 @@ public function testHttpIncoming(): void $this->expectSocketStreamReadLine()->setReturn(function () { return "\r\n"; }); + /** @var Request $message */ $message = $connection->pullHttp(); $this->assertEquals('POST', $message->getMethod()); @@ -155,6 +159,7 @@ public function testHttpOutgoing(): void return $message; })); $this->expectSocketStreamWrite(); + /** @var Response $message */ $message = $connection->pushHttp(new Response(200)); $this->assertEquals(400, $message->getStatusCode()); diff --git a/tests/suites/server/ConfigTest.php b/tests/suites/server/ConfigTest.php index 53ed69c..0158069 100644 --- a/tests/suites/server/ConfigTest.php +++ b/tests/suites/server/ConfigTest.php @@ -107,8 +107,8 @@ public function testServerConfiguration(): void $this->expectWsServerPerformHandshake(); $server->start(); - $this->assertEmpty($server->setLogger(new NullLogger())); - $this->expectSocketStreamSetTimeout()->addAssert(function ($method, $params) use ($server) { + $server->setLogger(new NullLogger()); + $this->expectSocketStreamSetTimeout()->addAssert(function ($method, $params) { $this->assertEquals(300, $params[0]); }); $this->assertSame($server, $server->setTimeout(300));