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/7] 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/7] 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/7] 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 From 583914e688c793d868c2dc07ea303bc31966342b Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 18 Nov 2025 17:12:42 +0100 Subject: [PATCH 4/7] [1.x] Run CI jobs on PHP8.5 --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbc6bfb..e126a6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.5 - 8.4 - 8.3 - 8.2 @@ -47,7 +48,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: 8.4 + php-version: 8.5 coverage: xdebug ini-file: development - run: composer install From 4e998a50a6ec8ccd52ff2078a938abb0058712e5 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 18 Nov 2025 17:14:47 +0100 Subject: [PATCH 5/7] [1.x] Fix reflection setAccessible deprecatoin warnings In PHP8.1 ReflectionProperty::setAccessible was made a no-op through https://wiki.php.net/rfc/make-reflection-setaccessible-no-op in PHP8.5 it is now throwing a deprecation warning by: https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_reflectionsetaccessible --- tests/DuplexResourceStreamTest.php | 4 +++- tests/ReadableResourceStreamTest.php | 4 +++- tests/WritableResourceStreamTest.php | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/DuplexResourceStreamTest.php b/tests/DuplexResourceStreamTest.php index cc72f69..0abc039 100644 --- a/tests/DuplexResourceStreamTest.php +++ b/tests/DuplexResourceStreamTest.php @@ -27,7 +27,9 @@ public function testConstructWithoutLoopAssignsLoopAutomatically() $stream = new DuplexResourceStream($resource); $ref = new \ReflectionProperty($stream, 'loop'); - $ref->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } $loop = $ref->getValue($stream); $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); diff --git a/tests/ReadableResourceStreamTest.php b/tests/ReadableResourceStreamTest.php index 8385ab8..9afc4d3 100644 --- a/tests/ReadableResourceStreamTest.php +++ b/tests/ReadableResourceStreamTest.php @@ -26,7 +26,9 @@ public function testConstructWithoutLoopAssignsLoopAutomatically() $stream = new ReadableResourceStream($resource); $ref = new \ReflectionProperty($stream, 'loop'); - $ref->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } $loop = $ref->getValue($stream); $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index 35c90db..7658307 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -26,7 +26,9 @@ public function testConstructWithoutLoopAssignsLoopAutomatically() $stream = new WritableResourceStream($resource); $ref = new \ReflectionProperty($stream, 'loop'); - $ref->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $ref->setAccessible(true); + } $loop = $ref->getValue($stream); $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); From e139356f3bd0123bb607ce00866886b57a71f175 Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Tue, 18 Nov 2025 17:18:10 +0100 Subject: [PATCH 6/7] [1.x] Skip writing to a closed stream test on PHP8.5 While I couldn't pin it to the exact commit/PR/NEWS/UPGRADING item, PHP8.5 has seen a number of improvements to streams to make them less buggy. As a result, this test, which already confirmed the expected behavior is now failing because an error is tossed. --- tests/WritableResourceStreamTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/WritableResourceStreamTest.php b/tests/WritableResourceStreamTest.php index 7658307..e09f6cb 100644 --- a/tests/WritableResourceStreamTest.php +++ b/tests/WritableResourceStreamTest.php @@ -474,6 +474,10 @@ public function testDoubleCloseWillEmitOnlyOnce() */ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream() { + if (PHP_VERSION_ID >= 80500) { + $this->markTestSkipped('Since PHP 8.5 attempting to write to a closed stream will result in an error'); + } + $stream = fopen('php://temp', 'r+'); $filterBuffer = ''; $loop = $this->createLoopMock(); From 84d6c360d8d5809773cb27bd8ad1f2b0841a467d Mon Sep 17 00:00:00 2001 From: Cees-Jan Kiewiet Date: Sat, 30 May 2026 22:30:18 +0200 Subject: [PATCH 7/7] [1.x] Update test environment for PHP 7.2 to compatible PHPUnit version This changes ensures we can continue run PHPUnit on PHP 7.2 by updating PHPUnit to 8.5. This is due to recent improvements in composer as discussed in https://github.com/reactphp/event-loop/pull/284. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 09d8b71..7bdffaf 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "evenement/evenement": "^3.0 || ^2.0 || ^1.0" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "phpunit/phpunit": "^9.6 || ^8.5 || ^5.7 || ^4.8.36", "clue/stream-filter": "~1.2" }, "autoload": {