diff --git a/README.md b/README.md index d243871..9dd4ad5 100644 --- a/README.md +++ b/README.md @@ -22,15 +22,12 @@ Once [installed](#install), you can use the following code to stream messages from any Server-Sent Events (SSE) server endpoint: ```php -$loop = Factory::create(); -$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop); +$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php'); $es->on('message', function (Clue\React\EventSource\MessageEvent $message) { //$data = json_decode($message->data); var_dump($message); }); - -$loop->run(); ``` See the [examples](examples). @@ -45,16 +42,18 @@ The `EventSource` object works very similar to the one found in common web browsers. Unless otherwise noted, it follows the same semantics as defined under https://html.spec.whatwg.org/multipage/server-sent-events.html -It requires the URL to the remote Server-Sent Events (SSE) endpoint and also -registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage) -in order to handle async HTTP requests. +Its constructor simply requires the URL to the remote Server-Sent Events (SSE) endpoint: ```php -$loop = React\EventLoop\Factory::create(); - -$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop); +$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php'); ``` +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + If you need custom connector settings (DNS resolution, TLS parameters, timeouts, proxy servers etc.), you can explicitly pass a custom instance of the [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface) @@ -62,19 +61,19 @@ to the [`Browser`](https://github.com/reactphp/http#browser) instance and pass it as an additional argument to the `EventSource` like this: ```php -$connector = new React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector(null, [ 'dns' => '127.0.0.1', - 'tcp' => array( + 'tcp' => [ 'bindto' => '192.168.10.1:0' - ), - 'tls' => array( + ], + 'tls' => [ 'verify_peer' => false, 'verify_peer_name' => false - ) -)); -$browser = new React\Http\Browser($loop, $connector); + ] +]); +$browser = new React\Http\Browser(null, $connector); -$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop, $browser); +$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', null, $browser); ``` ## Install diff --git a/composer.json b/composer.json index fe343d0..820b55f 100644 --- a/composer.json +++ b/composer.json @@ -7,22 +7,22 @@ "authors": [ { "name": "Christian Lück", - "email": "christian@lueck.tv" + "email": "christian@clue.engineering" } ], - "autoload": { - "psr-4": { "Clue\\React\\EventSource\\": "src/" } - }, - "autoload-dev": { - "psr-4": { "Clue\\Tests\\React\\EventSource\\": "tests/" } - }, "require": { "php": ">=5.4", "evenement/evenement": "^3.0 || ^2.0", - "react/event-loop": "^1.0", - "react/http": "^1.0" + "react/event-loop": "^1.2", + "react/http": "^1.4" }, "require-dev": { "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" + }, + "autoload": { + "psr-4": { "Clue\\React\\EventSource\\": "src/" } + }, + "autoload-dev": { + "psr-4": { "Clue\\Tests\\React\\EventSource\\": "tests/" } } } diff --git a/examples/stream.php b/examples/stream.php index 5b864eb..5b6a7cf 100644 --- a/examples/stream.php +++ b/examples/stream.php @@ -1,18 +1,14 @@ ' . PHP_EOL); } -$loop = Factory::create(); -$es = new EventSource($argv[1], $loop); +$es = new Clue\React\EventSource\EventSource($argv[1]); -$es->on('message', function ($message) { +$es->on('message', function (Clue\React\EventSource\MessageEvent $message) { //$data = json_decode($message->data); var_dump($message); }); @@ -22,11 +18,9 @@ }); $es->on('error', function (Exception $e) use ($es) { - if ($es->readyState === EventSource::CLOSED) { + if ($es->readyState === Clue\React\EventSource\EventSource::CLOSED) { echo 'Permanent error: ' . $e->getMessage() . PHP_EOL; } else { echo 'Temporary error: ' . $e->getMessage() . PHP_EOL; } }); - -$loop->run(); diff --git a/src/EventSource.php b/src/EventSource.php index 5760913..6ba400b 100644 --- a/src/EventSource.php +++ b/src/EventSource.php @@ -4,6 +4,7 @@ use Evenement\EventEmitter; use Psr\Http\Message\ResponseInterface; +use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Http\Browser; use React\Stream\ReadableStreamInterface; @@ -15,16 +16,18 @@ * web browsers. Unless otherwise noted, it follows the same semantics as defined * under https://html.spec.whatwg.org/multipage/server-sent-events.html * - * It requires the URL to the remote Server-Sent Events (SSE) endpoint and also - * registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage) - * in order to handle async HTTP requests. + * Its constructor simply requires the URL to the remote Server-Sent Events (SSE) endpoint: * * ```php - * $loop = React\EventLoop\Factory::create(); - * - * $es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop); + * $es = new Clue\React\EventSource\EventSource('https://example.com/stream.php'); * ``` * + * This class takes an optional `LoopInterface|null $loop` parameter that can be used to + * pass the event loop instance to use for this object. You can use a `null` value + * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). + * This value SHOULD NOT be given unless you're sure you want to explicitly use a + * given event loop instance. + * * If you need custom connector settings (DNS resolution, TLS parameters, timeouts, * proxy servers etc.), you can explicitly pass a custom instance of the * [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface) @@ -32,19 +35,19 @@ * and pass it as an additional argument to the `EventSource` like this: * * ```php - * $connector = new React\Socket\Connector($loop, array( + * $connector = new React\Socket\Connector(null, [ * 'dns' => '127.0.0.1', - * 'tcp' => array( + * 'tcp' => [ * 'bindto' => '192.168.10.1:0' - * ), - * 'tls' => array( + * ], + * 'tls' => [ * 'verify_peer' => false, * 'verify_peer_name' => false - * ) - * )); - * $browser = new React\Http\Browser($loop, $connector); + * ] + * ]); + * $browser = new React\Http\Browser(null, $connector); * - * $es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop, $browser); + * $es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', null, $browser); * ``` */ class EventSource extends EventEmitter @@ -78,18 +81,24 @@ class EventSource extends EventEmitter private $timer; private $reconnectTime = 3.0; - public function __construct($url, LoopInterface $loop, Browser $browser = null) + /** + * @param string $url + * @param ?LoopInterface $loop + * @param ?Browser $browser + * @throws \InvalidArgumentException for invalid URL + */ + public function __construct($url, LoopInterface $loop = null, Browser $browser = null) { $parts = parse_url($url); if (!isset($parts['scheme'], $parts['host']) || !in_array($parts['scheme'], array('http', 'https'))) { throw new \InvalidArgumentException(); } + $this->loop = $loop ?: Loop::get(); if ($browser === null) { - $browser = new Browser($loop); + $browser = new Browser($this->loop); } $this->browser = $browser->withRejectErrorResponse(false); - $this->loop = $loop; $this->url = $url; $this->readyState = self::CONNECTING; diff --git a/tests/EventSourceTest.php b/tests/EventSourceTest.php index ecf7f2b..0cf440f 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -34,6 +34,19 @@ public function testConstructorThrowsIfUriArgumentIncludesInvalidScheme() new EventSource('ftp://example.com', $loop); } + public function testConstructWithoutLoopAssignsLoopAutomatically() + { + $es = new EventSource('http://example.invalid'); + + $ref = new \ReflectionProperty($es, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($es); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + + $es->close(); + } + public function testConstructorCanBeCalledWithoutBrowser() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();