From 37fa61d0cb0baa531fd46c4703a84aca755f959a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 17 May 2024 14:43:39 +0200 Subject: [PATCH 1/3] Improve PHP 8.4+ support by avoiding implicitly nullable types --- src/DuplexResourceStream.php | 15 ++++++++++++++- src/ReadableResourceStream.php | 11 ++++++++++- src/WritableResourceStream.php | 12 +++++++++++- tests/DuplexResourceStreamTest.php | 16 ++++++++++++++++ tests/ReadableResourceStreamTest.php | 7 +++++++ tests/WritableResourceStreamTest.php | 8 ++++++++ 6 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/DuplexResourceStream.php b/src/DuplexResourceStream.php index 4da2139..d6de55c 100644 --- a/src/DuplexResourceStream.php +++ b/src/DuplexResourceStream.php @@ -38,7 +38,13 @@ final class DuplexResourceStream extends EventEmitter implements DuplexStreamInt private $closing = false; private $listening = false; - public function __construct($stream, LoopInterface $loop = null, $readChunkSize = null, WritableStreamInterface $buffer = null) + /** + * @param resource $stream + * @param ?LoopInterface $loop + * @param ?int $readChunkSize + * @param ?WritableStreamInterface $buffer + */ + public function __construct($stream, $loop = null, $readChunkSize = null, $buffer = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new InvalidArgumentException('First parameter must be a valid stream resource'); @@ -56,6 +62,13 @@ public function __construct($stream, LoopInterface $loop = null, $readChunkSize throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + if ($buffer !== null && !$buffer instanceof WritableStreamInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #4 ($buffer) expected null|React\Stream\WritableStreamInterface'); + } + // Use unbuffered read operations on the underlying stream resource. // Reading chunks from the stream may otherwise leave unread bytes in // PHP's stream buffers which some event loop implementations do not diff --git a/src/ReadableResourceStream.php b/src/ReadableResourceStream.php index 1b0b08c..823360a 100644 --- a/src/ReadableResourceStream.php +++ b/src/ReadableResourceStream.php @@ -40,7 +40,12 @@ final class ReadableResourceStream extends EventEmitter implements ReadableStrea private $closed = false; private $listening = false; - public function __construct($stream, LoopInterface $loop = null, $readChunkSize = null) + /** + * @param resource $stream + * @param ?LoopInterface $loop + * @param ?int $readChunkSize + */ + public function __construct($stream, $loop = null, $readChunkSize = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new InvalidArgumentException('First parameter must be a valid stream resource'); @@ -58,6 +63,10 @@ public function __construct($stream, LoopInterface $loop = null, $readChunkSize throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + // Use unbuffered read operations on the underlying stream resource. // Reading chunks from the stream may otherwise leave unread bytes in // PHP's stream buffers which some event loop implementations do not diff --git a/src/WritableResourceStream.php b/src/WritableResourceStream.php index 1af16b1..e3a7e74 100644 --- a/src/WritableResourceStream.php +++ b/src/WritableResourceStream.php @@ -28,7 +28,13 @@ final class WritableResourceStream extends EventEmitter implements WritableStrea private $closed = false; private $data = ''; - public function __construct($stream, LoopInterface $loop = null, $writeBufferSoftLimit = null, $writeChunkSize = null) + /** + * @param resource $stream + * @param ?LoopInterface $loop + * @param ?int $writeBufferSoftLimit + * @param ?int $writeChunkSize + */ + public function __construct($stream, $loop = null, $writeBufferSoftLimit = null, $writeChunkSize = null) { if (!\is_resource($stream) || \get_resource_type($stream) !== "stream") { throw new \InvalidArgumentException('First parameter must be a valid stream resource'); @@ -46,6 +52,10 @@ public function __construct($stream, LoopInterface $loop = null, $writeBufferSof throw new \RuntimeException('Unable to set stream resource to non-blocking mode'); } + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + $this->stream = $stream; $this->loop = $loop ?: Loop::get(); $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit; diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index e61f14b..cc72f69 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -106,6 +106,22 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( new DuplexResourceStream($stream, $loop); } + public function testContructorThrowsExceptionForInvalidLoop() + { + $stream = fopen('php://temp', 'r+'); + + $this->setExpectedException('InvalidArgumentException', 'Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + new DuplexResourceStream($stream, 42); + } + + public function testContructorThrowsExceptionForInvalidBuffer() + { + $stream = fopen('php://temp', 'r+'); + + $this->setExpectedException('InvalidArgumentException', 'Argument #4 ($buffer) expected null|React\Stream\WritableStreamInterface'); + new DuplexResourceStream($stream, null, null, 42); + } + /** * @covers React\Stream\DuplexResourceStream::__construct * @doesNotPerformAssertions diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index f534488..8385ab8 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -105,6 +105,13 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( new ReadableResourceStream($stream, $loop); } + public function testContructorThrowsExceptionForInvalidLoop() + { + $stream = fopen('php://temp', 'r+'); + + $this->setExpectedException('InvalidArgumentException', 'Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + new ReadableResourceStream($stream, 42); + } public function testCloseShouldEmitCloseEvent() { diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index 678db98..35c90db 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -103,6 +103,14 @@ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking( new WritableResourceStream($stream, $loop); } + public function testContructorThrowsExceptionForInvalidLoop() + { + $stream = fopen('php://temp', 'r+'); + + $this->setExpectedException('InvalidArgumentException', 'Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + new WritableResourceStream($stream, 42); + } + /** * @covers React\Stream\WritableResourceStream::write * @covers React\Stream\WritableResourceStream::handleWrite From 1e5b0acb8fe55143b5b426817155190eb6f5b18d Mon Sep 17 00:00:00 2001 From: Simon Frings Date: Tue, 11 Jun 2024 14:41:41 +0200 Subject: [PATCH 2/3] Prepare v1.4.0 release --- CHANGELOG.md | 11 +++++++++++ README.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e2307..639db65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 1.4.0 (2024-06-11) + +* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations. + (#179 by @clue) + +* Feature: Full PHP 8.3 compatibility. + (#172 by @clue) + +* Fix: Fix `drain` event of `ThroughStream` to handle potential race condition. + (#171 by @clue) + ## 1.3.0 (2023-06-16) * Feature: Full PHP 8.1 and PHP 8.2 compatibility. diff --git a/README.md b/README.md index 2bfcbbe..9c0468a 100644 --- a/README.md +++ b/README.md @@ -1203,7 +1203,7 @@ This project follows [SemVer](https://semver.org/). This will install the latest supported version: ```bash -composer require react/stream:^1.3 +composer require react/stream:^1.4 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. From 665194e5a1776f34fdeff9ff1b8a8813828feea6 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Fri, 10 Oct 2025 08:01:28 +0200 Subject: [PATCH 3/3] Run tests on PHP 8.4 and update test environment Builds on top of #172 and ports #181 to v1. --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc36a4d..bbc6bfb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,11 @@ on: jobs: PHPUnit: name: PHPUnit (PHP ${{ matrix.php }}) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: php: + - 8.4 - 8.3 - 8.2 - 8.1 @@ -40,13 +41,13 @@ jobs: PHPUnit-macOS: name: PHPUnit (macOS) - runs-on: macos-12 + runs-on: macos-14 continue-on-error: true steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.4 coverage: xdebug ini-file: development - run: composer install