Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
dependencies:
- "highest"
include:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ 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.3+ with limited support for newer PHP.
extensions and supports running on legacy PHP 5.4 through current PHP 8+.
It's *highly recommended to use the latest supported PHP version* for this project.

See above note about [Reviving Ratchet](#reviving-ratchet) for newer PHP support.
Expand Down
15 changes: 9 additions & 6 deletions src/Ratchet/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,19 @@ class App {
protected $_routeCounter = 0;

/**
* @param string $httpHost HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');`
* @param int $port Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843
* @param string $address IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine.
* @param LoopInterface $loop Specific React\EventLoop to bind the application to. null will create one for you.
* @param array $context
* @param string $httpHost HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');`
* @param int $port Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843
* @param string $address IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine.
* @param ?LoopInterface $loop Specific React\EventLoop to bind the application to. null will create one for you.
* @param array $context
*/
public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null, $context = array()) {
public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', $loop = null, $context = array()) {
if (extension_loaded('xdebug') && getenv('RATCHET_DISABLE_XDEBUG_WARN') === false) {
trigger_error('XDebug extension detected. Remember to disable this if performance testing or going live!', E_USER_WARNING);
}
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #4 ($loop) expected null|React\EventLoop\LoopInterface');
}

if (null === $loop) {
$loop = LoopFactory::create();
Expand Down
3 changes: 2 additions & 1 deletion src/Ratchet/Http/HttpServerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ interface HttpServerInterface extends MessageComponentInterface {
* @param \Psr\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!!
* @throws \UnexpectedValueException if a RequestInterface is not passed
*/
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null);
#[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null); /*
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null); /**/
Comment on lines -13 to +14
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reference: I understand this looks a whole lot like a BC break, but I don't think it qualifies as one. While this certainly looks nasty, I think this should be fully backward compatible across all supported PHP versions.

The comment has the effect that PHP 8 sees this definition public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) while legacy PHP < 8 would see the definition public function onOpen(ConnectionInterface $conn, RequestInterface $request = null). Note that the only difference here is the ?RequestInterface $request = null vs RequestInterface $request = null.

For all supported PHP versions, you would determine the parameter to be nullable, no matter if the ? is present or not:

  • For legacy PHP < 7.1, the former would be a syntax error, so only the latter definition applies.
  • For PHP 7.1 - PHP 8.3 this wouldn't make a difference at all. For PHP < 8 we apply the former definition, for PHP 8+ the latter, but either would be ok really.
  • For PHP 8.4+, they would still apply the exact same logic, but the former would be the recommended way, while the latter would report a deprecation notice.

Thanks to the nasty comment hack, PHP 8.4+ would never interpret the second line, so no deprecation would be triggered in any PHP version.

(If you want to learn more about how the hack works, follow the links above, e.g. https://x.com/another_clue/status/1671189006162464768)

}
3 changes: 2 additions & 1 deletion src/Ratchet/Http/NoOpHttpServerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
use Psr\Http\Message\RequestInterface;

class NoOpHttpServerController implements HttpServerInterface {
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
#[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
}

public function onMessage(ConnectionInterface $from, $msg) {
Expand Down
3 changes: 2 additions & 1 deletion src/Ratchet/Http/OriginCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public function __construct(MessageComponentInterface $component, array $allowed
/**
* {@inheritdoc}
*/
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
#[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
$header = (string)$request->getHeader('Origin')[0];
$origin = parse_url($header, PHP_URL_HOST) ?: $header;

Expand Down
3 changes: 2 additions & 1 deletion src/Ratchet/Http/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public function __construct(UrlMatcherInterface $matcher) {
* {@inheritdoc}
* @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface
*/
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
#[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
if (null === $request) {
throw new \UnexpectedValueException('$request can not be null');
}
Expand Down
6 changes: 5 additions & 1 deletion src/Ratchet/Server/IoServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ class IoServer {
* @param \React\Socket\ServerInterface $socket The React socket server to run the Ratchet application off of
* @param \React\EventLoop\LoopInterface|null $loop The React looper to run the Ratchet application off of
*/
public function __construct(MessageComponentInterface $app, ServerInterface $socket, LoopInterface $loop = null) {
public function __construct(MessageComponentInterface $app, ServerInterface $socket, $loop = null) {
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
}

if (false === strpos(PHP_VERSION, "hiphop")) {
gc_enable();
}
Expand Down
16 changes: 10 additions & 6 deletions src/Ratchet/Session/SessionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ class SessionProvider implements HttpServerInterface {
protected $_serializer;

/**
* @param \Ratchet\Http\HttpServerInterface $app
* @param \SessionHandlerInterface $handler
* @param array $options
* @param \Ratchet\Session\Serialize\HandlerInterface $serializer
* @param \Ratchet\Http\HttpServerInterface $app
* @param \SessionHandlerInterface $handler
* @param array $options
* @param ?\Ratchet\Session\Serialize\HandlerInterface $serializer
* @throws \RuntimeException
*/
public function __construct(HttpServerInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) {
public function __construct(HttpServerInterface $app, \SessionHandlerInterface $handler, array $options = array(), $serializer = null) {
if ($serializer !== null && !$serializer instanceof HandlerInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #4 ($serializer) expected null|Ratchet\Session\Serialize\HandlerInterface');
}
$this->_app = $app;
$this->_handler = $handler;
$this->_null = new NullSessionHandler;
Expand All @@ -70,7 +73,8 @@ public function __construct(HttpServerInterface $app, \SessionHandlerInterface $
/**
* {@inheritdoc}
*/
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
#[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
$sessionName = ini_get('session.name');

$id = array_reduce($request->getHeader('Cookie'), function($accumulator, $cookie) use ($sessionName) {
Expand Down
3 changes: 2 additions & 1 deletion src/Ratchet/WebSocket/WsServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ public function __construct(ComponentInterface $component) {
/**
* {@inheritdoc}
*/
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) {
#[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
if (null === $request) {
throw new \UnexpectedValueException('$request can not be null');
}
Expand Down
35 changes: 0 additions & 35 deletions tests/helpers/Ratchet/Mock/Component.php

This file was deleted.

17 changes: 17 additions & 0 deletions tests/unit/AppTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
namespace Ratchet;

use PHPUnit\Framework\TestCase;
use Ratchet\App;

class AppTest extends TestCase {
public function testCtorThrowsForInvalidLoop() {
if (method_exists($this, 'expectException')) {
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Argument #4 ($loop) expected null|React\EventLoop\LoopInterface');
} else {
$this->setExpectedException('InvalidArgumentException', 'Argument #4 ($loop) expected null|React\EventLoop\LoopInterface');
}
new App('localhost', 8080, '127.0.0.1', 'loop');
}
}
10 changes: 10 additions & 0 deletions tests/unit/Server/IoServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ public function setUpServer() {
$this->server = new IoServer($this->app, $this->reactor, $loop);
}

public function testCtorThrowsForInvalidLoop() {
if (method_exists($this, 'expectException')) {
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
} else {
$this->setExpectedException('InvalidArgumentException', 'Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
}
new IoServer($this->app, $this->reactor, 'loop');
}

public function testOnOpen() {
$this->app->expects($this->once())->method('onOpen')->with($this->isInstanceOf('Ratchet\\ConnectionInterface'));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ public function setUpProvider() {
$this->_serv = new SessionProvider($this->_app, new NullSessionHandler);
}

public function testCtorThrowsForInvalidSerializer() {
if (method_exists($this, 'expectException')) {
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Argument #4 ($serializer) expected null|Ratchet\Session\Serialize\HandlerInterface');
} else {
$this->setExpectedException('InvalidArgumentException', 'Argument #4 ($serializer) expected null|Ratchet\Session\Serialize\HandlerInterface');
}
new SessionProvider($this->_app, new NullSessionHandler(), [], 'serializer');
}

/**
* @after
*/
Expand Down