Ver Fonte

Merge pull request #927 from Chance-fyi/phpstan

Add static analysis tool PHPStan
walkor há 2 anos atrás
pai
commit
240eda91c2

+ 3 - 1
.github/workflows/test.yml

@@ -41,7 +41,9 @@ jobs:
           timeout_minutes: 5
           max_attempts: 5
           command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress
-#          command: composer install --prefer-dist --no-interaction --no-progress
+
+      - name: Static analysis
+        run: composer analyse
 
       - name: Execute tests
         run: vendor/bin/pest --coverage

+ 5 - 1
composer.json

@@ -44,11 +44,15 @@
     "require-dev": {
         "pestphp/pest": "2.x-dev",
         "mockery/mockery": "2.0.x-dev",
-        "guzzlehttp/guzzle": "^7.0"
+        "guzzlehttp/guzzle": "^7.0",
+        "phpstan/phpstan": "1.11.x-dev"
     },
     "config": {
         "allow-plugins": {
             "pestphp/pest-plugin": true
         }
+    },
+    "scripts": {
+        "analyse": "phpstan analyse -c phpstan.neon --memory-limit=-1"
     }
 }

+ 34 - 0
phpstan.neon

@@ -0,0 +1,34 @@
+parameters:
+	level: 5
+	paths:
+		- src
+		- tests
+	ignoreErrors:
+	    -
+	        path: src/Events/Revolt.php
+	        messages:
+	            - '#Property Workerman\\Events\\Revolt::\$driver has unknown class Revolt\\EventLoop\\Driver as its type.#'
+	            - '#Call to static method getDriver\(\) on an unknown class Revolt\\EventLoop.#'
+	            - '#Method Workerman\\Events\\Revolt::driver\(\) has invalid return type Revolt\\EventLoop\\Driver.#'
+	            - '#Call to method .* on an unknown class Revolt\\EventLoop\\Driver.#'
+	    -
+	        path: src/Events/Swow.php
+	        messages:
+	            - '#Used function Swow\\Sync\\waitAll not found.#'
+	            - '#Call to static method .* on an unknown class Swow\\.*.#'
+	            - '#Function msleep not found.#'
+	            - '#Function stream_poll_one not found.#'
+	            - '#Caught class Swow\\SignalException not found.#'
+	            - '#Function Swow\\Sync\\waitAll not found.#'
+	            - '#Constant STREAM_POLLHUP not found.#'
+	            - '#Constant STREAM_POLLIN not found.#'
+	            - '#Constant STREAM_POLLNONE not found.#'
+	            - '#Constant STREAM_POLLOUT not found.#'
+	            - '#Property Workerman\\Events\\Swow::.* has unknown class Swow\\Coroutine as its type.#'
+	    - path: src/Timer.php
+	      message: '#Call to static method getSuspension\(\) on an unknown class Revolt\\EventLoop.#'
+	    - path: tests/Pest.php
+	      message: '#Undefined variable: \$this#'
+	    - path: src/Worker.php
+	      message: '#Constant LINE_VERSION_LENGTH not found.#'
+	    - message: '#Parameter \#1 \$callback of function set_error_handler expects \(callable\(int, string, string, int\): bool\)\|null,.*#'

+ 2 - 2
src/Connection/TcpConnection.php

@@ -53,7 +53,7 @@ use const STREAM_CRYPTO_METHOD_SSLv2_SERVER;
 
 /**
  * TcpConnection.
- * @property string websocketType
+ * @property string $websocketType
  */
 class TcpConnection extends ConnectionInterface implements JsonSerializable
 {
@@ -1033,7 +1033,7 @@ class TcpConnection extends ConnectionInterface implements JsonSerializable
     /**
      * Enable or disable Cache.
      *
-     * @param mixed $value
+     * @param bool $value
      */
     public static function enableCache(bool $value = true): void
     {

+ 2 - 0
src/Events/Event.php

@@ -166,6 +166,7 @@ class Event implements EventInterface
         $className = $this->eventClassName;
         $fdKey = (int)$stream;
         $event = new $this->eventClassName($this->eventBase, $stream, $className::READ | $className::PERSIST, $func, $stream);
+        // @phpstan-ignore-next-line Negated boolean expression is always false.
         if (!$event || !$event->add()) {
             return;
         }
@@ -194,6 +195,7 @@ class Event implements EventInterface
         $className = $this->eventClassName;
         $fdKey = (int)$stream;
         $event = new $this->eventClassName($this->eventBase, $stream, $className::WRITE | $className::PERSIST, $func, $stream);
+        // @phpstan-ignore-next-line Negated boolean expression is always false.
         if (!$event || !$event->add()) {
             return;
         }

+ 1 - 0
src/Events/Swow.php

@@ -85,6 +85,7 @@ class Swow implements EventInterface
         $t = max($t, 1);
         $that = $this;
         $coroutine = Coroutine::run(static function () use ($t, $func, $args, $that): void {
+            // @phpstan-ignore-next-line While loop condition is always true.
             while (true) {
                 msleep($t);
                 try {

+ 9 - 2
src/Protocols/Http/Request.php

@@ -99,6 +99,13 @@ class Request
     protected static bool $enableCache = true;
 
     /**
+     * Session id.
+     *
+     * @var mixed|string
+     */
+    protected mixed $sid;
+
+    /**
      * Request constructor.
      *
      * @param string $buffer
@@ -296,11 +303,11 @@ class Request
     /**
      * Get/Set session id.
      *
-     * @param null $sessionId
+     * @param string|null $sessionId
      * @return string
      * @throws Exception
      */
-    public function sessionId($sessionId = null): string
+    public function sessionId(string $sessionId = null): string
     {
         if ($sessionId) {
             unset($this->sid);

+ 1 - 1
src/Protocols/Http/Response.php

@@ -401,7 +401,7 @@ class Response
 
         $fileInfo = pathinfo($file);
         $extension = $fileInfo['extension'] ?? '';
-        $baseName = $fileInfo['basename'] ?? 'unknown';
+        $baseName = $fileInfo['basename'] ?: 'unknown';
         if (!isset($headers['Content-Type'])) {
             if (isset(self::$mimeTypeMap[$extension])) {
                 $head .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n";

+ 0 - 2
src/Protocols/Http/Session.php

@@ -17,7 +17,6 @@ declare(strict_types=1);
 namespace Workerman\Protocols\Http;
 
 use Exception;
-use JetBrains\PhpStorm\ArrayShape;
 use RuntimeException;
 use Workerman\Protocols\Http\Session\FileSessionHandler;
 use Workerman\Protocols\Http\Session\SessionHandlerInterface;
@@ -381,7 +380,6 @@ class Session
      *
      * @return array
      */
-    #[ArrayShape(['lifetime' => "int", 'path' => "string", 'domain' => "string", 'secure' => "bool", 'httponly' => "bool", 'samesite' => "string"])]
     public static function getCookieParams(): array
     {
         return [

+ 4 - 12
src/Protocols/Ws.php

@@ -233,17 +233,13 @@ class Ws
     /**
      * Websocket encode.
      *
-     * @param mixed $payload
+     * @param string $payload
      * @param AsyncTcpConnection $connection
      * @return string
      * @throws Throwable
      */
-    public static function encode(mixed $payload, AsyncTcpConnection $connection): string
+    public static function encode(string $payload, AsyncTcpConnection $connection): string
     {
-        if (!is_scalar($payload)) {
-            throw new Exception("You can't send(" . gettype($payload) . ") to client, you need to convert it to string. ");
-        }
-
         if (empty($connection->websocketType)) {
             $connection->websocketType = self::BINARY_TYPE_BLOB;
         }
@@ -371,12 +367,8 @@ class Ws
         $userHeader = $connection->headers ?? null;
         $userHeaderStr = '';
         if (!empty($userHeader)) {
-            if (is_array($userHeader)) {
-                foreach ($userHeader as $k => $v) {
-                    $userHeaderStr .= "$k: $v\r\n";
-                }
-            } else {
-                $userHeaderStr .= $userHeader;
+            foreach ($userHeader as $k => $v) {
+                $userHeaderStr .= "$k: $v\r\n";
             }
             $userHeaderStr = "\r\n" . trim($userHeaderStr);
         }

+ 1 - 1
src/Timer.php

@@ -107,7 +107,7 @@ class Timer
      *
      * @param float $timeInterval
      * @param callable $func
-     * @param mixed|array $args
+     * @param null|array $args
      * @param bool $persistent
      * @return int
      */

+ 14 - 21
src/Worker.php

@@ -166,7 +166,7 @@ class Worker
     /**
      * Emitted when data is received.
      *
-     * @var callable
+     * @var ?callable
      */
     public $onMessage = null;
 
@@ -312,7 +312,7 @@ class Worker
     /**
      * EventLoopClass
      *
-     * @var class-string
+     * @var string|class-string
      */
     public static string $eventLoopClass = '';
 
@@ -340,7 +340,7 @@ class Worker
     /**
      * Listening socket.
      *
-     * @var resource
+     * @var ?resource
      */
     protected $mainSocket = null;
 
@@ -534,7 +534,7 @@ class Worker
 
     /**
      * Standard output stream
-     * @var resource
+     * @var ?resource
      */
     protected static $outputStream = null;
 
@@ -1279,7 +1279,7 @@ class Worker
             }
             // change output stream
             static::$outputStream = null;
-            static::outputStream($STDOUT);
+            self::outputStream($STDOUT);
             restore_error_handler();
             return;
         }
@@ -1516,14 +1516,10 @@ class Worker
             $process = $processData[0];
             $startFile = $processData[1];
             $status = proc_get_status($process);
-            if (isset($status['running'])) {
-                if (!$status['running']) {
-                    static::safeEcho("process $startFile terminated and try to restart\n");
-                    proc_close($process);
-                    static::forkOneWorkerForWindows($startFile);
-                }
-            } else {
-                static::safeEcho("proc_get_status fail\n");
+            if (!$status['running']) {
+                static::safeEcho("process $startFile terminated and try to restart\n");
+                proc_close($process);
+                static::forkOneWorkerForWindows($startFile);
             }
         }
     }
@@ -1686,6 +1682,7 @@ class Worker
     protected static function monitorWorkersForLinux(): void
     {
         static::$status = static::STATUS_RUNNING;
+        // @phpstan-ignore-next-line While loop condition is always true.
         while (1) {
             // Calls signal handlers for pending signals.
             pcntl_signal_dispatch();
@@ -1918,11 +1915,7 @@ class Worker
                 static::$workers = [];
                 static::$globalEvent?->stop();
 
-                try {
-                    exit($code);
-                } catch (Exception $e) {
-
-                }
+                exit($code);
             }
         }
     }
@@ -1972,7 +1965,6 @@ class Worker
         if (static::$masterPid === posix_getpid()) {
             $allWorkerInfo = [];
             foreach (static::$pidMap as $workerId => $pidArray) {
-                /** @var /Workerman/Worker $worker */
                 $worker = static::$workers[$workerId];
                 foreach ($pidArray as $pid) {
                     $allWorkerInfo[$pid] = ['name' => $worker->name, 'listen' => $worker->getSocketName()];
@@ -2086,7 +2078,6 @@ class Worker
         $currentWorker = current(static::$workers);
         $defaultWorkerName = $currentWorker->name;
 
-        /** @var static $worker */
         foreach (TcpConnection::$connections as $connection) {
             /** @var TcpConnection $connection */
             $transport = $connection->transport;
@@ -2180,7 +2171,7 @@ class Worker
      */
     public static function safeEcho(string $msg, bool $decorated = false): bool
     {
-        $stream = static::outputStream();
+        $stream = self::outputStream();
         if (!$stream) {
             return false;
         }
@@ -2213,6 +2204,7 @@ class Worker
         if (!$stream) {
             $stream = static::$outputStream ?: STDOUT;
         }
+        // @phpstan-ignore-next-line Negated boolean expression is always false.
         if (!$stream || !is_resource($stream) || 'stream' !== get_resource_type($stream)) {
             return false;
         }
@@ -2552,6 +2544,7 @@ class Worker
                 if ($this->protocol !== null) {
                     /** @var ProtocolInterface $parser */
                     $parser = $this->protocol;
+                    // @phpstan-ignore-next-line Left side of && is always true.
                     if ($parser && method_exists($parser, 'input')) {
                         while ($recvBuffer !== '') {
                             $len = $parser::input($recvBuffer, $connection);

+ 1 - 1
tests/Feature/UdpConnectionTest.php

@@ -32,7 +32,7 @@ afterAll(function () use (&$process) {
 
 it('tests udp connection', function () use ($serverAddress) {
     $socket = stream_socket_client($serverAddress, $errno, $errstr, 1);
-    expect($errno)->toBeInt(0);
+    expect($errno)->toBeInt()->toBe(0);
     fwrite($socket, 'xiami');
     $data = fread($socket, 1024);
     expect($data)->toBeString('received: xiami');

+ 3 - 0
tests/Unit/Protocols/HttpTest.php

@@ -55,6 +55,7 @@ it('tests ::input', function () {
 });
 
 it('tests ::encode for non-object response', function () {
+    /** @var TcpConnection $tcpConnection */
     $tcpConnection = Mockery::mock(TcpConnection::class);
     $tcpConnection->headers = [
         'foo' => 'bar',
@@ -73,6 +74,7 @@ it('tests ::encode for non-object response', function () {
 });
 
 it('tests ::encode for ' . Response::class, function () {
+    /** @var TcpConnection $tcpConnection */
     $tcpConnection = Mockery::mock(TcpConnection::class);
     $tcpConnection->headers = [
         'foo' => 'bar',
@@ -93,6 +95,7 @@ it('tests ::encode for ' . Response::class, function () {
 });
 
 it('tests ::decode', function () {
+    /** @var TcpConnection $tcpConnection */
     $tcpConnection = Mockery::mock(TcpConnection::class);
 
     //example request from ChatGPT :)

+ 1 - 0
tests/Unit/Protocols/TextTest.php

@@ -4,6 +4,7 @@ use Workerman\Connection\ConnectionInterface;
 use Workerman\Protocols\Text;
 
 test(Text::class, function () {
+    /** @var ConnectionInterface $connection */
     $connection = Mockery::mock(ConnectionInterface::class);
 
     //::input