From 808bbf2dc5fa2c86fce93ed82ede6f9b47fbfc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 4 Oct 2023 17:45:00 +0200 Subject: [PATCH 1/2] Support PHP 8.2 with dynamic property notices ignored --- .github/workflows/ci.yml | 1 + README.md | 13 ++++++++++++- phpunit.xml.dist | 7 ++++++- phpunit.xml.legacy | 3 +++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b9eba24..32d659e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: - "7.4" - "8.0" - "8.1" + - "8.2" dependencies: - "highest" include: diff --git a/README.md b/README.md index 1b3a879b..8c40e825 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,20 @@ composer require cboden/ratchet:^0.4.4 See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP -extensions and supports running on legacy PHP 5.4 through PHP 8.1+ with limited support for PHP 8.2+. +extensions and supports running on legacy PHP 5.4 through PHP 8.2+ with limited support for PHP 8.2+. It's *highly recommended to use the latest supported PHP version* for this project. +Note that PHP 8.2+ has limited support due to a number of deprecation notices +caused by dynamic property access. The code works without limitations otherwise, +so for now, we recommend running without reporting deprecation notices like this: + +```php +// Report all errors except E_DEPRECATED +error_reporting(E_ALL & ~E_DEPRECATED); +``` + +Addressing these deprecation notices is on the roadmap for future releases, but +requires major effort to refactor the codebase to avoid dynamic property access. See above note about [Reviving Ratchet](#reviving-ratchet) for newer PHP support. ## License diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e8ab6917..285d3b30 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,7 +5,8 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd" bootstrap="tests/bootstrap.php" cacheResult="false" - colors="true"> + colors="true" + convertDeprecationsToExceptions="true"> ./tests/unit/ @@ -16,4 +17,8 @@ ./src/ + + + + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy index f88bf5b7..934a8d82 100644 --- a/phpunit.xml.legacy +++ b/phpunit.xml.legacy @@ -15,4 +15,7 @@ ./src/ + + + From 6fe567ea1dd998b3ea0b426054ddc5f8125063a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Thu, 5 Oct 2023 10:14:21 +0200 Subject: [PATCH 2/2] Work around reporting dynamic properties on PHP 8.2+ --- README.md | 13 +------------ phpunit.xml.dist | 3 +-- src/Ratchet/AbstractConnectionDecorator.php | 5 +++++ src/Ratchet/ConnectionInterface.php | 6 ++++++ src/Ratchet/Server/IoConnection.php | 5 +++-- src/Ratchet/Server/IoServer.php | 8 +++++++- .../AbstractMessageComponentTestCase.php | 4 ++-- tests/helpers/Ratchet/Mock/Connection.php | 1 + tests/unit/AbstractConnectionDecoratorTest.php | 2 +- tests/unit/Http/HttpRequestParserTest.php | 4 ++-- tests/unit/Http/RouterTest.php | 4 ++-- tests/unit/Server/IoServerTest.php | 5 +++++ tests/unit/Server/IpBlackListComponentTest.php | 2 +- tests/unit/Wamp/TopicManagerTest.php | 2 +- tests/unit/Wamp/TopicTest.php | 18 +++++++++--------- tests/unit/Wamp/WampConnectionTest.php | 4 ++-- 16 files changed, 49 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 8c40e825..09f30f6d 100644 --- a/README.md +++ b/README.md @@ -101,20 +101,9 @@ composer require cboden/ratchet:^0.4.4 See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. This project aims to run on any platform and thus does not require any PHP -extensions and supports running on legacy PHP 5.4 through PHP 8.2+ with limited support for PHP 8.2+. +extensions and supports running on legacy PHP 5.4 through PHP 8.2+ with limited support for newer PHP. It's *highly recommended to use the latest supported PHP version* for this project. -Note that PHP 8.2+ has limited support due to a number of deprecation notices -caused by dynamic property access. The code works without limitations otherwise, -so for now, we recommend running without reporting deprecation notices like this: - -```php -// Report all errors except E_DEPRECATED -error_reporting(E_ALL & ~E_DEPRECATED); -``` - -Addressing these deprecation notices is on the roadmap for future releases, but -requires major effort to refactor the codebase to avoid dynamic property access. See above note about [Reviving Ratchet](#reviving-ratchet) for newer PHP support. ## License diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 285d3b30..d54c2560 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -18,7 +18,6 @@ - - + diff --git a/src/Ratchet/AbstractConnectionDecorator.php b/src/Ratchet/AbstractConnectionDecorator.php index 97079511..23aaf45c 100644 --- a/src/Ratchet/AbstractConnectionDecorator.php +++ b/src/Ratchet/AbstractConnectionDecorator.php @@ -4,6 +4,11 @@ /** * Wraps ConnectionInterface objects via the decorator pattern but allows * parameters to bubble through with magic methods + * + * Note that this instance does not use the `#[\AllowDynamicProperties]` + * attribute for PHP 8.2+ compatibility as any properties added to this class + * will be forwarded to the wrapped instance. + * * @todo It sure would be nice if I could make most of this a trait... */ abstract class AbstractConnectionDecorator implements ConnectionInterface { diff --git a/src/Ratchet/ConnectionInterface.php b/src/Ratchet/ConnectionInterface.php index 72138111..1c405f7a 100644 --- a/src/Ratchet/ConnectionInterface.php +++ b/src/Ratchet/ConnectionInterface.php @@ -10,6 +10,12 @@ /** * A proxy object representing a connection to the application * This acts as a container to store data (in memory) about the connection + * + * Note that Ratchet makes heavy use of the decorator pattern and dynamic + * properties. This means any object that implements this interface may not + * have the same methods or properties as the ones in this interface. + * Implementations of this interface may have to use the `#[\AllowDynamicProperties]` + * attribute to allow dynamic properties on PHP 8.2+. */ interface ConnectionInterface { /** diff --git a/src/Ratchet/Server/IoConnection.php b/src/Ratchet/Server/IoConnection.php index 9f864bb9..101bf41c 100644 --- a/src/Ratchet/Server/IoConnection.php +++ b/src/Ratchet/Server/IoConnection.php @@ -1,11 +1,12 @@ conn = $conn; } diff --git a/src/Ratchet/Server/IoServer.php b/src/Ratchet/Server/IoServer.php index b3fb7e0e..0e84ae41 100644 --- a/src/Ratchet/Server/IoServer.php +++ b/src/Ratchet/Server/IoServer.php @@ -2,6 +2,7 @@ namespace Ratchet\Server; use Ratchet\MessageComponentInterface; use React\EventLoop\LoopInterface; +use React\Socket\ConnectionInterface as SocketConnection; use React\Socket\ServerInterface; use React\EventLoop\Factory as LoopFactory; use React\Socket\Server as Reactor; @@ -79,8 +80,13 @@ public function run() { * Triggered when a new connection is received from React * @param \React\Socket\ConnectionInterface $conn */ - public function handleConnect($conn) { + public function handleConnect(SocketConnection $conn) { + // assign dynamic `$decor` property used by Ratchet without raising notice on PHP 8.2+ + // need this hack because we can't use `#[\AllowDynamicProperties]` on vendor code + set_error_handler(function () { }, E_DEPRECATED); $conn->decor = new IoConnection($conn); + restore_error_handler(); + $conn->decor->resourceId = (int)$conn->stream; $uri = $conn->getRemoteAddress(); diff --git a/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php b/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php index c8d724a6..da8f5a2b 100644 --- a/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php +++ b/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php @@ -19,7 +19,7 @@ public function setUpConnection() { $this->_app = $this->getMockBuilder($this->getComponentClassString())->getMock(); $decorator = $this->getDecoratorClassString(); $this->_serv = new $decorator($this->_app); - $this->_conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock(); + $this->_conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $this->doOpen($this->_conn); } @@ -34,7 +34,7 @@ public function isExpectedConnection() { public function testOpen() { $this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection()); - $this->doOpen($this->getMockBuilder('Ratchet\ConnectionInterface')->getMock()); + $this->doOpen($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()); } public function testOnClose() { diff --git a/tests/helpers/Ratchet/Mock/Connection.php b/tests/helpers/Ratchet/Mock/Connection.php index 59182965..d0add5fc 100644 --- a/tests/helpers/Ratchet/Mock/Connection.php +++ b/tests/helpers/Ratchet/Mock/Connection.php @@ -2,6 +2,7 @@ namespace Ratchet\Mock; use Ratchet\ConnectionInterface; +#[\AllowDynamicProperties] class Connection implements ConnectionInterface { public $last = array( 'send' => '' diff --git a/tests/unit/AbstractConnectionDecoratorTest.php b/tests/unit/AbstractConnectionDecoratorTest.php index ddb0b83d..8c5b379c 100644 --- a/tests/unit/AbstractConnectionDecoratorTest.php +++ b/tests/unit/AbstractConnectionDecoratorTest.php @@ -16,7 +16,7 @@ class AbstractConnectionDecoratorTest extends TestCase { * @before */ public function setUpConnection() { - $this->mock = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock(); + $this->mock = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $this->l1 = new ConnectionDecorator($this->mock); $this->l2 = new ConnectionDecorator($this->l1); } diff --git a/tests/unit/Http/HttpRequestParserTest.php b/tests/unit/Http/HttpRequestParserTest.php index bf7ee1a9..05f2a76b 100644 --- a/tests/unit/Http/HttpRequestParserTest.php +++ b/tests/unit/Http/HttpRequestParserTest.php @@ -35,7 +35,7 @@ public function testIsEom($expected, $message) { } public function testBufferOverflowResponse() { - $conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock(); + $conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $this->parser->maxSize = 20; @@ -51,7 +51,7 @@ public function testBufferOverflowResponse() { } public function testReturnTypeIsRequest() { - $conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock(); + $conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $return = $this->parser->onMessage($conn, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"); $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $return); diff --git a/tests/unit/Http/RouterTest.php b/tests/unit/Http/RouterTest.php index 3a73341c..8218a57b 100644 --- a/tests/unit/Http/RouterTest.php +++ b/tests/unit/Http/RouterTest.php @@ -22,7 +22,7 @@ class RouterTest extends TestCase { * @before */ public function setUpConnection() { - $this->_conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock(); + $this->_conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $this->_uri = $this->getMockBuilder('Psr\Http\Message\UriInterface')->getMock(); $this->_req = $this->getMockBuilder('Psr\Http\Message\RequestInterface')->getMock(); $this->_req @@ -80,7 +80,7 @@ public function testControllerOnOpen() { $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller))); $this->_router->onOpen($this->_conn, $this->_req); - $expectedConn = $this->isInstanceOf('Ratchet\ConnectionInterface'); + $expectedConn = $this->isInstanceOf('Ratchet\Mock\Connection'); $controller->expects($this->once())->method('onOpen')->with($expectedConn, $this->_req); $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller))); diff --git a/tests/unit/Server/IoServerTest.php b/tests/unit/Server/IoServerTest.php index 6547fcc1..f337f1ea 100644 --- a/tests/unit/Server/IoServerTest.php +++ b/tests/unit/Server/IoServerTest.php @@ -117,7 +117,12 @@ public function testNoLoopProvidedError() { public function testOnErrorPassesException() { $conn = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock(); + + // assign dynamic property without raising notice on PHP 8.2+ + set_error_handler(function () { }, E_DEPRECATED); $conn->decor = $this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock(); + restore_error_handler(); + $err = new \Exception("Nope"); $this->app->expects($this->once())->method('onError')->with($conn->decor, $err); diff --git a/tests/unit/Server/IpBlackListComponentTest.php b/tests/unit/Server/IpBlackListComponentTest.php index 7037a347..8365cd08 100644 --- a/tests/unit/Server/IpBlackListComponentTest.php +++ b/tests/unit/Server/IpBlackListComponentTest.php @@ -121,7 +121,7 @@ public function testUnblockingSilentlyFails() { } protected function newConn() { - $conn = $this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock(); + $conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $conn->remoteAddress = '127.0.0.1'; return $conn; diff --git a/tests/unit/Wamp/TopicManagerTest.php b/tests/unit/Wamp/TopicManagerTest.php index b0509872..fd67fdd5 100644 --- a/tests/unit/Wamp/TopicManagerTest.php +++ b/tests/unit/Wamp/TopicManagerTest.php @@ -23,7 +23,7 @@ class TopicManagerTest extends TestCase { * @before */ public function setUpManager() { - $this->conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock(); + $this->conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $this->mock = $this->getMockBuilder('Ratchet\Wamp\WampServerInterface')->getMock(); $this->mngr = new TopicManager($this->mock); diff --git a/tests/unit/Wamp/TopicTest.php b/tests/unit/Wamp/TopicTest.php index 3714ece9..827c081f 100644 --- a/tests/unit/Wamp/TopicTest.php +++ b/tests/unit/Wamp/TopicTest.php @@ -42,8 +42,8 @@ public function testBroadcast() { $name = 'Batman'; $protocol = json_encode(array(8, $name, $msg)); - $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()))->getMock(); - $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()))->getMock(); + $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock(); + $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock(); $first->expects($this->once()) ->method('send') @@ -65,9 +65,9 @@ public function testBroadcastWithExclude() { $name = 'Excluding'; $protocol = json_encode(array(8, $name, $msg)); - $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()))->getMock(); - $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()))->getMock(); - $third = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()))->getMock(); + $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock(); + $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock(); + $third = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock(); $first->expects($this->once()) ->method('send') @@ -92,9 +92,9 @@ public function testBroadcastWithEligible() { $name = 'Eligible'; $protocol = json_encode(array(8, $name, $msg)); - $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()))->getMock(); - $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()))->getMock(); - $third = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()))->getMock(); + $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock(); + $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock(); + $third = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock(); $first->expects($this->once()) ->method('send') @@ -161,6 +161,6 @@ public function testDoesNotHaveAfterRemove() { } protected function newConn() { - return new WampConnection($this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock()); + return new WampConnection($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()); } } diff --git a/tests/unit/Wamp/WampConnectionTest.php b/tests/unit/Wamp/WampConnectionTest.php index a51051bc..fb23c7b5 100644 --- a/tests/unit/Wamp/WampConnectionTest.php +++ b/tests/unit/Wamp/WampConnectionTest.php @@ -14,7 +14,7 @@ class WampConnectionTest extends TestCase { * @before */ public function setUpConnection() { - $this->mock = $this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock(); + $this->mock = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $this->conn = new WampConnection($this->mock); } @@ -72,7 +72,7 @@ public function testGetUriWhenNoCurieGiven() { } public function testClose() { - $mock = $this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock(); + $mock = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock(); $conn = new WampConnection($mock); $mock->expects($this->once())->method('close');