Skip to content

Commit 9234da0

Browse files
committed
Add PHPStan to test environment
1 parent cf107f5 commit 9234da0

16 files changed

+112
-27
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/.github/ 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

+23
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,26 @@ jobs:
4747
- run: composer install
4848
- run: vendor/bin/phpunit --coverage-text
4949
- run: time php examples/91-benchmark-throughput.php
50+
51+
PHPStan:
52+
name: PHPStan (PHP ${{ matrix.php }})
53+
runs-on: ubuntu-22.04
54+
strategy:
55+
matrix:
56+
php:
57+
- 8.3
58+
- 8.2
59+
- 8.1
60+
- 8.0
61+
- 7.4
62+
- 7.3
63+
- 7.2
64+
- 7.1
65+
steps:
66+
- uses: actions/checkout@v4
67+
- uses: shivammathur/setup-php@v2
68+
with:
69+
php-version: ${{ matrix.php }}
70+
coverage: none
71+
- run: composer install
72+
- run: vendor/bin/phpstan

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,12 @@ If you do not want to run these, they can simply be skipped like this:
12431243
vendor/bin/phpunit --exclude-group internet
12441244
```
12451245

1246+
On top of this, we use PHPStan on level 5 to ensure type safety across the project:
1247+
1248+
```bash
1249+
vendor/bin/phpstan
1250+
```
1251+
12461252
## License
12471253

12481254
MIT, see [LICENSE file](LICENSE).

composer.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131
"evenement/evenement": "^3.0 || ^2.0 || ^1.0"
3232
},
3333
"require-dev": {
34-
"phpunit/phpunit": "^9.6 || ^7.5",
35-
"clue/stream-filter": "~1.2"
34+
"clue/stream-filter": "^1.2",
35+
"phpstan/phpstan": "1.11.1 || 1.4.10",
36+
"phpunit/phpunit": "^9.6 || ^7.5"
3637
},
3738
"autoload": {
3839
"psr-4": {

phpstan.neon.dist

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
level: 5
3+
4+
paths:
5+
- examples/
6+
- src/
7+
- tests/

src/DuplexResourceStream.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,15 @@ public function pipe(WritableStreamInterface $dest, array $options = []): Writab
171171
public function handleData($stream)
172172
{
173173
$error = null;
174-
\set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
174+
\set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error): bool {
175175
$error = new \ErrorException(
176176
$errstr,
177177
0,
178178
$errno,
179179
$errfile,
180180
$errline
181181
);
182+
return true;
182183
});
183184

184185
$data = \stream_get_contents($stream, $this->bufferSize);

src/ReadableResourceStream.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -124,14 +124,15 @@ public function close(): void
124124
public function handleData()
125125
{
126126
$error = null;
127-
\set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
127+
\set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error): bool {
128128
$error = new \ErrorException(
129129
$errstr,
130130
0,
131131
$errno,
132132
$errfile,
133133
$errline
134134
);
135+
return true;
135136
});
136137

137138
$data = \stream_get_contents($this->stream, $this->bufferSize);

src/ThroughStream.php

+2
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ public function write($data): bool
144144
}
145145

146146
// continue writing if still writable and not paused (throttled), false otherwise
147+
// @phpstan-ignore-next-line (may be false when write() causes stream to close)
147148
return $this->writable && !$this->paused;
148149
}
149150

@@ -157,6 +158,7 @@ public function end($data = null): void
157158
$this->write($data);
158159

159160
// return if write() already caused the stream to close
161+
// @phpstan-ignore-next-line (may be false when write() causes stream to close)
160162
if (!$this->writable) {
161163
return;
162164
}

src/WritableResourceStream.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ public function close(): void
122122
public function handleWrite()
123123
{
124124
$error = null;
125-
\set_error_handler(function ($_, $errstr) use (&$error) {
125+
\set_error_handler(function ($_, $errstr) use (&$error): bool {
126126
$error = $errstr;
127+
return true;
127128
});
128129

129130
if ($this->writeChunkSize === -1) {

tests/CompositeStreamTest.php

+18
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ public function itShouldCloseReadableIfNotWritable()
2323
$readable
2424
->expects($this->once())
2525
->method('close');
26+
assert($readable instanceof ReadableStreamInterface);
2627

2728
$writable = $this->createMock(WritableStreamInterface::class);
2829
$writable
2930
->expects($this->once())
3031
->method('isWritable')
3132
->willReturn(false);
33+
assert($writable instanceof WritableStreamInterface);
3234

3335
$composite = new CompositeStream($readable, $writable);
3436

@@ -44,11 +46,13 @@ public function itShouldCloseWritableIfNotReadable()
4446
->expects($this->once())
4547
->method('isReadable')
4648
->willReturn(false);
49+
assert($readable instanceof ReadableStreamInterface);
4750

4851
$writable = $this->createMock(WritableStreamInterface::class);
4952
$writable
5053
->expects($this->once())
5154
->method('close');
55+
assert($writable instanceof WritableStreamInterface);
5256

5357
$composite = new CompositeStream($readable, $writable);
5458

@@ -64,6 +68,7 @@ public function itShouldForwardWritableCallsToWritableStream()
6468
->expects($this->once())
6569
->method('isReadable')
6670
->willReturn(true);
71+
assert($readable instanceof ReadableStreamInterface);
6772

6873
$writable = $this->createMock(WritableStreamInterface::class);
6974
$writable
@@ -74,6 +79,7 @@ public function itShouldForwardWritableCallsToWritableStream()
7479
->expects($this->exactly(2))
7580
->method('isWritable')
7681
->willReturn(true);
82+
assert($writable instanceof WritableStreamInterface);
7783

7884
$composite = new CompositeStream($readable, $writable);
7985
$composite->write('foo');
@@ -94,12 +100,14 @@ public function itShouldForwardReadableCallsToReadableStream()
94100
$readable
95101
->expects($this->once())
96102
->method('resume');
103+
assert($readable instanceof ReadableStreamInterface);
97104

98105
$writable = $this->createMock(WritableStreamInterface::class);
99106
$writable
100107
->expects($this->any())
101108
->method('isWritable')
102109
->willReturn(true);
110+
assert($writable instanceof WritableStreamInterface);
103111

104112
$composite = new CompositeStream($readable, $writable);
105113
$composite->isReadable();
@@ -118,12 +126,14 @@ public function itShouldNotForwardResumeIfStreamIsNotWritable()
118126
$readable
119127
->expects($this->never())
120128
->method('resume');
129+
assert($readable instanceof ReadableStreamInterface);
121130

122131
$writable = $this->createMock(WritableStreamInterface::class);
123132
$writable
124133
->expects($this->exactly(2))
125134
->method('isWritable')
126135
->willReturnOnConsecutiveCalls(true, false);
136+
assert($writable instanceof WritableStreamInterface);
127137

128138
$composite = new CompositeStream($readable, $writable);
129139
$composite->resume();
@@ -137,6 +147,7 @@ public function endShouldDelegateToWritableWithData()
137147
->expects($this->once())
138148
->method('isReadable')
139149
->willReturn(true);
150+
assert($readable instanceof ReadableStreamInterface);
140151

141152
$writable = $this->createMock(WritableStreamInterface::class);
142153
$writable
@@ -147,6 +158,7 @@ public function endShouldDelegateToWritableWithData()
147158
->expects($this->once())
148159
->method('end')
149160
->with('foo');
161+
assert($writable instanceof WritableStreamInterface);
150162

151163
$composite = new CompositeStream($readable, $writable);
152164
$composite->end('foo');
@@ -163,6 +175,7 @@ public function closeShouldCloseBothStreams()
163175
$readable
164176
->expects($this->once())
165177
->method('close');
178+
assert($readable instanceof ReadableStreamInterface);
166179

167180
$writable = $this->createMock(WritableStreamInterface::class);
168181
$writable
@@ -172,6 +185,7 @@ public function closeShouldCloseBothStreams()
172185
$writable
173186
->expects($this->once())
174187
->method('close');
188+
assert($writable instanceof WritableStreamInterface);
175189

176190
$composite = new CompositeStream($readable, $writable);
177191
$composite->close();
@@ -231,13 +245,15 @@ public function itShouldHandlePipingCorrectly()
231245
->expects($this->once())
232246
->method('isReadable')
233247
->willReturn(true);
248+
assert($readable instanceof ReadableStreamInterface);
234249

235250
$writable = $this->createMock(WritableStreamInterface::class);
236251
$writable->expects($this->any())->method('isWritable')->willReturn(True);
237252
$writable
238253
->expects($this->once())
239254
->method('write')
240255
->with('foo');
256+
assert($writable instanceof WritableStreamInterface);
241257

242258
$composite = new CompositeStream($readable, $writable);
243259

@@ -253,6 +269,7 @@ public function itShouldForwardPipeCallsToReadableStream()
253269

254270
$writable = $this->createMock(WritableStreamInterface::class);
255271
$writable->expects($this->any())->method('isWritable')->willReturn(True);
272+
assert($writable instanceof WritableStreamInterface);
256273

257274
$composite = new CompositeStream($readable, $writable);
258275

@@ -262,6 +279,7 @@ public function itShouldForwardPipeCallsToReadableStream()
262279
->expects($this->once())
263280
->method('write')
264281
->with('foo');
282+
assert($output instanceof WritableStreamInterface);
265283

266284
$composite->pipe($output);
267285
$readable->emit('data', ['foo']);

tests/DuplexResourceStreamIntegrationTest.php

+11-11
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function () {
3737
public function testBufferReadsLargeChunks($condition, $loopFactory)
3838
{
3939
if (true !== $condition()) {
40-
return $this->markTestSkipped('Loop implementation not available');
40+
$this->markTestSkipped('Loop implementation not available');
4141
}
4242

4343
$loop = $loopFactory();
@@ -73,7 +73,7 @@ public function testBufferReadsLargeChunks($condition, $loopFactory)
7373
public function testWriteLargeChunk($condition, $loopFactory)
7474
{
7575
if (true !== $condition()) {
76-
return $this->markTestSkipped('Loop implementation not available');
76+
$this->markTestSkipped('Loop implementation not available');
7777
}
7878

7979
$loop = $loopFactory();
@@ -113,7 +113,7 @@ public function testWriteLargeChunk($condition, $loopFactory)
113113
public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory)
114114
{
115115
if (true !== $condition()) {
116-
return $this->markTestSkipped('Loop implementation not available');
116+
$this->markTestSkipped('Loop implementation not available');
117117
}
118118

119119
$loop = $loopFactory();
@@ -141,7 +141,7 @@ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFact
141141
public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory)
142142
{
143143
if (true !== $condition()) {
144-
return $this->markTestSkipped('Loop implementation not available');
144+
$this->markTestSkipped('Loop implementation not available');
145145
}
146146

147147
$loop = $loopFactory();
@@ -171,7 +171,7 @@ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition
171171
public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory)
172172
{
173173
if (true !== $condition()) {
174-
return $this->markTestSkipped('Loop implementation not available');
174+
$this->markTestSkipped('Loop implementation not available');
175175
}
176176

177177
$loop = $loopFactory();
@@ -204,7 +204,7 @@ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopF
204204
public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory)
205205
{
206206
if (true !== $condition()) {
207-
return $this->markTestSkipped('Loop implementation not available');
207+
$this->markTestSkipped('Loop implementation not available');
208208
}
209209

210210
$loop = $loopFactory();
@@ -237,7 +237,7 @@ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopF
237237
public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
238238
{
239239
if (true !== $condition()) {
240-
return $this->markTestSkipped('Loop implementation not available');
240+
$this->markTestSkipped('Loop implementation not available');
241241
}
242242

243243
$loop = $loopFactory();
@@ -256,7 +256,7 @@ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
256256
public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
257257
{
258258
if (true !== $condition()) {
259-
return $this->markTestSkipped('Loop implementation not available');
259+
$this->markTestSkipped('Loop implementation not available');
260260
}
261261

262262
$loop = $loopFactory();
@@ -282,7 +282,7 @@ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
282282
public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
283283
{
284284
if (true !== $condition()) {
285-
return $this->markTestSkipped('Loop implementation not available');
285+
$this->markTestSkipped('Loop implementation not available');
286286
}
287287

288288
$loop = $loopFactory();
@@ -308,7 +308,7 @@ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
308308
public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory)
309309
{
310310
if (true !== $condition()) {
311-
return $this->markTestSkipped('Loop implementation not available');
311+
$this->markTestSkipped('Loop implementation not available');
312312
}
313313

314314
$loop = $loopFactory();
@@ -328,7 +328,7 @@ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFac
328328
public function testEmptyReadShouldntFcloseStream($condition, $loopFactory)
329329
{
330330
if (true !== $condition()) {
331-
return $this->markTestSkipped('Loop implementation not available');
331+
$this->markTestSkipped('Loop implementation not available');
332332
}
333333

334334
$server = stream_socket_server('tcp://127.0.0.1:0');

0 commit comments

Comments
 (0)