Skip to content

Commit 830c2b2

Browse files
authored
Merge pull request #140 from clue-labs/phpstan
Add PHPStan to test environment with `max` level
2 parents 6a883a9 + 6e33521 commit 830c2b2

13 files changed

+181
-161
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/.github/workflows/ export-ignore
33
/.gitignore export-ignore
44
/examples/ export-ignore
5+
/phpstan.neon.dist export-ignore
56
/phpunit.xml.dist export-ignore
67
/phpunit.xml.legacy export-ignore
78
/tests/ export-ignore

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,25 @@ jobs:
3737
<?php
3838
$metrics = simplexml_load_file('clover.xml')->project->metrics;
3939
exit((int) $metrics['statements'] === (int) $metrics['coveredstatements'] ? 0 : 1);
40+
41+
PHPStan:
42+
name: PHPStan (PHP ${{ matrix.php }})
43+
runs-on: ubuntu-22.04
44+
strategy:
45+
matrix:
46+
php:
47+
- 8.2
48+
- 8.1
49+
- 8.0
50+
- 7.4
51+
- 7.3
52+
- 7.2
53+
- 7.1
54+
steps:
55+
- uses: actions/checkout@v3
56+
- uses: shivammathur/setup-php@v2
57+
with:
58+
php-version: ${{ matrix.php }}
59+
coverage: none
60+
- run: composer install
61+
- run: vendor/bin/phpstan

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
},
2222
"require-dev": {
2323
"clue/block-react": "^1.5",
24+
"phpstan/phpstan": "1.8.11 || 1.4.10",
2425
"phpunit/phpunit": "^9.5 || ^7.5"
2526
},
2627
"autoload": {

examples/cli.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525

2626
$params = explode(' ', $line);
2727
$method = array_shift($params);
28-
$promise = call_user_func_array([$redis, $method], $params);
28+
29+
assert(is_callable([$redis, $method]));
30+
$promise = $redis->$method(...$params);
2931

3032
// special method such as end() / close() called
3133
if (!$promise instanceof React\Promise\PromiseInterface) {

phpstan.neon.dist

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
parameters:
2+
level: max
3+
4+
paths:
5+
- examples/
6+
- src/
7+
- tests/
8+
9+
reportUnmatchedIgnoredErrors: false
10+
ignoreErrors:
11+
# ignore generic usage like `PromiseInterface<T>` until fixed upstream
12+
- '/^PHPDoc tag @return contains generic type React\\Promise\\PromiseInterface<.+> but interface React\\Promise\\PromiseInterface is not generic\.$/'
13+
# ignore undefined methods due to magic `__call()` method
14+
- '/^Call to an undefined method Clue\\React\\Redis\\RedisClient::.+\(\)\.$/'
15+
- '/^Call to an undefined method Clue\\React\\Redis\\Io\\StreamingClient::.+\(\)\.$/'
16+
# ignore incomplete type information for mocks in legacy PHPUnit 7.5
17+
- '/^Parameter #\d+ .+ of .+ expects .+, PHPUnit\\Framework\\MockObject\\MockObject given\.$/'

src/Io/Factory.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public function createClient(string $uri): PromiseInterface
7575
if ($parts['scheme'] === 'rediss') {
7676
$authority = 'tls://' . $authority;
7777
} elseif ($parts['scheme'] === 'redis+unix') {
78+
assert(isset($parts['path']));
7879
$authority = 'unix://' . substr($parts['path'], 1);
7980
unset($parts['path']);
8081
}
@@ -91,6 +92,7 @@ public function createClient(string $uri): PromiseInterface
9192
$connecting->then(function (ConnectionInterface $connection) {
9293
$connection->close();
9394
});
95+
assert(\method_exists($connecting, 'cancel'));
9496
$connecting->cancel();
9597
});
9698

@@ -106,7 +108,7 @@ public function createClient(string $uri): PromiseInterface
106108

107109
// use `?password=secret` query or `user:secret@host` password form URL
108110
if (isset($args['password']) || isset($parts['pass'])) {
109-
$pass = $args['password'] ?? rawurldecode($parts['pass']);
111+
$pass = $args['password'] ?? rawurldecode($parts['pass']); // @phpstan-ignore-line
110112
$promise = $promise->then(function (StreamingClient $redis) use ($pass, $uri) {
111113
return $redis->auth($pass)->then(
112114
function () use ($redis) {
@@ -134,7 +136,7 @@ function (\Exception $e) use ($redis, $uri) {
134136

135137
// use `?db=1` query or `/1` path (skip first slash)
136138
if (isset($args['db']) || (isset($parts['path']) && $parts['path'] !== '/')) {
137-
$db = $args['db'] ?? substr($parts['path'], 1);
139+
$db = $args['db'] ?? substr($parts['path'], 1); // @phpstan-ignore-line
138140
$promise = $promise->then(function (StreamingClient $redis) use ($db, $uri) {
139141
return $redis->select($db)->then(
140142
function () use ($redis) {

src/Io/StreamingClient.php

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Clue\React\Redis\Io;
44

5-
use Clue\Redis\Protocol\Factory as ProtocolFactory;
65
use Clue\Redis\Protocol\Model\ErrorReply;
76
use Clue\Redis\Protocol\Model\ModelInterface;
87
use Clue\Redis\Protocol\Model\MultiBulkReply;
@@ -22,9 +21,6 @@ class StreamingClient extends EventEmitter
2221
/** @var DuplexStreamInterface */
2322
private $stream;
2423

25-
/** @var ParserInterface */
26-
private $parser;
27-
2824
/** @var SerializerInterface */
2925
private $serializer;
3026

@@ -43,18 +39,8 @@ class StreamingClient extends EventEmitter
4339
/** @var int */
4440
private $psubscribed = 0;
4541

46-
public function __construct(DuplexStreamInterface $stream, ParserInterface $parser = null, SerializerInterface $serializer = null)
42+
public function __construct(DuplexStreamInterface $stream, ParserInterface $parser, SerializerInterface $serializer)
4743
{
48-
if ($parser === null || $serializer === null) {
49-
$factory = new ProtocolFactory();
50-
if ($parser === null) {
51-
$parser = $factory->createResponseParser();
52-
}
53-
if ($serializer === null) {
54-
$serializer = $factory->createSerializer();
55-
}
56-
}
57-
5844
$stream->on('data', function (string $chunk) use ($parser) {
5945
try {
6046
$models = $parser->pushIncoming($chunk);
@@ -82,10 +68,10 @@ public function __construct(DuplexStreamInterface $stream, ParserInterface $pars
8268
$stream->on('close', [$this, 'close']);
8369

8470
$this->stream = $stream;
85-
$this->parser = $parser;
8671
$this->serializer = $serializer;
8772
}
8873

74+
/** @param string[] $args */
8975
public function __call(string $name, array $args): PromiseInterface
9076
{
9177
$request = new Deferred();
@@ -139,6 +125,7 @@ public function handleMessage(ModelInterface $message): void
139125
{
140126
if (($this->subscribed !== 0 || $this->psubscribed !== 0) && $message instanceof MultiBulkReply) {
141127
$array = $message->getValueNative();
128+
assert(\is_array($array));
142129
$first = array_shift($array);
143130

144131
// pub/sub messages are to be forwarded and should not be processed as request responses

src/RedisClient.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class RedisClient extends EventEmitter
4646
/** @var float */
4747
private $idlePeriod = 0.001;
4848

49-
/** @var ?TimerInterface */
49+
/** @var ?\React\EventLoop\TimerInterface */
5050
private $idleTimer = null;
5151

5252
/** @var int */
@@ -161,6 +161,7 @@ public function __call(string $name, array $args): PromiseInterface
161161

162162
return $this->client()->then(function (StreamingClient $redis) use ($name, $args) {
163163
$this->awake();
164+
assert(\is_callable([$redis, $name])); // @phpstan-ignore-next-line
164165
return \call_user_func_array([$redis, $name], $args)->then(
165166
function ($result) {
166167
$this->idle();
@@ -221,6 +222,7 @@ public function close(): void
221222
$redis->close();
222223
});
223224
if ($this->promise !== null) {
225+
assert(\method_exists($this->promise, 'cancel'));
224226
$this->promise->cancel();
225227
$this->promise = null;
226228
}
@@ -251,6 +253,7 @@ private function idle(): void
251253

252254
if ($this->pending < 1 && $this->idlePeriod >= 0 && !$this->subscribed && !$this->psubscribed && $this->promise !== null) {
253255
$this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () {
256+
assert($this->promise instanceof PromiseInterface);
254257
$this->promise->then(function (StreamingClient $redis) {
255258
$redis->close();
256259
});

tests/FunctionalTest.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function setUp(): void
2626
$this->loop = new StreamSelectLoop();
2727
}
2828

29-
public function testPing()
29+
public function testPing(): void
3030
{
3131
$redis = new RedisClient($this->uri, null, $this->loop);
3232

@@ -38,7 +38,7 @@ public function testPing()
3838
$this->assertEquals('PONG', $ret);
3939
}
4040

41-
public function testPingLazy()
41+
public function testPingLazy(): void
4242
{
4343
$redis = new RedisClient($this->uri, null, $this->loop);
4444

@@ -53,7 +53,7 @@ public function testPingLazy()
5353
/**
5454
* @doesNotPerformAssertions
5555
*/
56-
public function testPingLazyWillNotBlockLoop()
56+
public function testPingLazyWillNotBlockLoop(): void
5757
{
5858
$redis = new RedisClient($this->uri, null, $this->loop);
5959

@@ -65,7 +65,7 @@ public function testPingLazyWillNotBlockLoop()
6565
/**
6666
* @doesNotPerformAssertions
6767
*/
68-
public function testLazyClientWithoutCommandsWillNotBlockLoop()
68+
public function testLazyClientWithoutCommandsWillNotBlockLoop(): void
6969
{
7070
$redis = new RedisClient($this->uri, null, $this->loop);
7171

@@ -74,7 +74,7 @@ public function testLazyClientWithoutCommandsWillNotBlockLoop()
7474
unset($redis);
7575
}
7676

77-
public function testMgetIsNotInterpretedAsSubMessage()
77+
public function testMgetIsNotInterpretedAsSubMessage(): void
7878
{
7979
$redis = new RedisClient($this->uri, null, $this->loop);
8080

@@ -86,7 +86,7 @@ public function testMgetIsNotInterpretedAsSubMessage()
8686
await($promise, $this->loop);
8787
}
8888

89-
public function testPipeline()
89+
public function testPipeline(): void
9090
{
9191
$redis = new RedisClient($this->uri, null, $this->loop);
9292

@@ -98,20 +98,21 @@ public function testPipeline()
9898
await($promise, $this->loop);
9999
}
100100

101-
public function testInvalidCommand()
101+
public function testInvalidCommand(): void
102102
{
103103
$redis = new RedisClient($this->uri, null, $this->loop);
104104
$promise = $redis->doesnotexist(1, 2, 3);
105105

106106
if (method_exists($this, 'expectException')) {
107107
$this->expectException('Exception');
108108
} else {
109+
assert(method_exists($this, 'setExpectedException'));
109110
$this->setExpectedException('Exception');
110111
}
111112
await($promise, $this->loop);
112113
}
113114

114-
public function testMultiExecEmpty()
115+
public function testMultiExecEmpty(): void
115116
{
116117
$redis = new RedisClient($this->uri, null, $this->loop);
117118
$redis->multi()->then($this->expectCallableOnceWith('OK'));
@@ -120,7 +121,7 @@ public function testMultiExecEmpty()
120121
await($promise, $this->loop);
121122
}
122123

123-
public function testMultiExecQueuedExecHasValues()
124+
public function testMultiExecQueuedExecHasValues(): void
124125
{
125126
$redis = new RedisClient($this->uri, null, $this->loop);
126127

@@ -134,7 +135,7 @@ public function testMultiExecQueuedExecHasValues()
134135
await($promise, $this->loop);
135136
}
136137

137-
public function testPubSub()
138+
public function testPubSub(): void
138139
{
139140
$consumer = new RedisClient($this->uri, null, $this->loop);
140141
$producer = new RedisClient($this->uri, null, $this->loop);
@@ -155,7 +156,7 @@ public function testPubSub()
155156
await($deferred->promise(), $this->loop, 0.1);
156157
}
157158

158-
public function testClose()
159+
public function testClose(): void
159160
{
160161
$redis = new RedisClient($this->uri, null, $this->loop);
161162

@@ -166,7 +167,7 @@ public function testClose()
166167
$redis->get('willBeRejectedRightAway')->then(null, $this->expectCallableOnce());
167168
}
168169

169-
public function testCloseLazy()
170+
public function testCloseLazy(): void
170171
{
171172
$redis = new RedisClient($this->uri, null, $this->loop);
172173

0 commit comments

Comments
 (0)