├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── examples └── redis │ ├── .gitignore │ ├── README.md │ ├── client.html │ ├── composer.json │ └── stream.php ├── phpunit.xml.dist ├── src └── Igorw │ └── EventSource │ ├── EchoHandler.php │ ├── Event.php │ ├── EventWrapper.php │ └── Stream.php └── tests ├── Igorw └── Tests │ └── EventSource │ ├── EchoHandlerTest.php │ ├── EventTest.php │ ├── EventWrapperTest.php │ └── StreamTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | 7 | before_script: 8 | - wget http://getcomposer.org/composer.phar 9 | - php composer.phar install 10 | 11 | script: phpunit --coverage-text 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Igor Wiedler 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EventSource 2 | 3 | A PHP 5.3 library for creating an [EventSource](http://dev.w3.org/html5/eventsource/) stream. 4 | 5 | EventSource or Server-Sent-Events is a W3C specification that defines a protocol and an API 6 | for pushing data from the server to the client. This library is a server-side implementation 7 | of this protocol. 8 | 9 | It is designed to be transport agnostic, allowing you to use it with apache directly or with 10 | other webservers, such as mongrel2. 11 | 12 | [![Build Status](https://secure.travis-ci.org/igorw/EventSource.png?branch=master)](http://travis-ci.org/igorw/EventSource) 13 | 14 | ## Fetch 15 | 16 | The recommended way to install EventSource is [through composer](http://packagist.org). 17 | 18 | Just create a composer.json file for your project: 19 | 20 | ```JSON 21 | { 22 | "require": { 23 | "igorw/event-source": "1.0.*" 24 | } 25 | } 26 | ``` 27 | 28 | And run these two commands to install it: 29 | 30 | $ curl -s http://getcomposer.org/installer | php 31 | $ php composer.phar install 32 | 33 | Now you can add the autoloader, and you will have access to the library: 34 | 35 | ```php 36 | $value) { 51 | header("$name: $value"); 52 | } 53 | ``` 54 | 55 | After that you create a ``Stream`` which provides a nice API for creating events. 56 | Once you call flush, all queued events are sent to the client. 57 | 58 | This example will send a new event every 2 seconds. 59 | 60 | ```php 61 | event() 70 | ->setData("Hello World") 71 | ->end() 72 | ->flush(); 73 | 74 | sleep(2); 75 | } 76 | ``` 77 | 78 | And an example JavaScript client: 79 | 80 | ```JavaScript 81 | var stream = new EventSource('stream.php'); 82 | 83 | stream.addEventListener('message', function (event) { 84 | console.log(event.data); 85 | }); 86 | ``` 87 | 88 | ## Advanced 89 | 90 | ### Last event id 91 | 92 | When your events have ids, the client will send a `Last-Event-ID` header on 93 | reconnection. You can read this value and re-send any events that occured after 94 | the one provided by the user. 95 | 96 | ```php 97 | event() 106 | ->setId($message['id']) 107 | ->setData($message['data']); 108 | } 109 | 110 | $stream->flush(); 111 | } 112 | ``` 113 | 114 | ### Event namespacing 115 | 116 | You can namespace events by using the `setEvent` method on the event. This 117 | allows you to bind to those event types specifically on the client side. 118 | 119 | Here is a stream that sends two events. One of type `foo` and one of type 120 | `bar`. 121 | 122 | ```php 123 | event() 127 | ->setEvent('foo') 128 | ->setData($message['data']); 129 | ->end() 130 | ->event() 131 | ->setEvent('bar') 132 | ->setData($message['data']); 133 | ->end() 134 | ->flush(); 135 | ``` 136 | 137 | On the client you bind to that event instead of the generic `message` one. 138 | Do note that the `message` event will not catch these messages. 139 | 140 | ```JavaScript 141 | var stream = new EventSource('stream.php'); 142 | 143 | stream.addEventListener('foo', function (event) { 144 | console.log('Received event foo!'); 145 | }); 146 | 147 | stream.addEventListener('bar', function (event) { 148 | console.log('Received event bar!'); 149 | }); 150 | ``` 151 | 152 | ### Sending JSON 153 | 154 | In most applications you will want to send more complex data as opposed to 155 | simple strings. The recommended way of doing this is to use the JSON format. 156 | It can encode and decode nested structures quite well. 157 | 158 | On the server side, simply use the `json_encode` function to encode a value: 159 | 160 | ```php 161 | array(21, 43, 127)); 164 | 165 | $stream 166 | ->event() 167 | ->setData(json_encode($data)); 168 | ->end() 169 | ->flush(); 170 | ``` 171 | 172 | On the client side, you can use `JSON.parse` to decode it. For old browsers, 173 | where `JSON` is not available, see [json2.js](https://github.com/douglascrockford/JSON-js). 174 | 175 | ```JavaScript 176 | var stream = new EventSource('stream.php'); 177 | 178 | stream.addEventListener('message', function (event) { 179 | var data = JSON.parse(event.data); 180 | console.log('User IDs: '+data.userIds.join(', ')); 181 | }); 182 | ``` 183 | 184 | ### Custom handler 185 | 186 | By default the library will assume you are running in a traditional apache-like 187 | environment. This means that output happens through echo. If you are using a 188 | server that handles web output in a different way (eg. app server), then you 189 | will want to change this. 190 | 191 | A handler is simply a function that takes a chunk (a single event) and sends it 192 | to the client. You can define it as a lambda. Here is the default handler: 193 | 194 | ```php 195 | =5.3.0" 15 | }, 16 | "autoload": { 17 | "psr-0": { 18 | "Igorw\\EventSource": "src" 19 | } 20 | }, 21 | "extra": { 22 | "branch-alias": { 23 | "dev-master": "1.0-dev" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/redis/.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | -------------------------------------------------------------------------------- /examples/redis/README.md: -------------------------------------------------------------------------------- 1 | # Redis PUB/SUB example 2 | 3 | How to use the EventSource library with redis PUB/SUB. 4 | 5 | This example uses the excellent [predis](https://github.com/nrk/predis) library 6 | to connect to a locally running redis server. 7 | 8 | The way it works is this: 9 | 10 | * `stream.php` accepts a request from an EventSource client. 11 | * It opens a connection to the redis server and subscribes to the 12 | `notification` channel. 13 | * It waits until a message is published into that channel. 14 | * Once a message comes in, it sends it to the client though the EventSource 15 | stream. 16 | 17 | As soon as anyone publishes something into that channel, all connected clients 18 | will receive the message. 19 | 20 | ## Stop talking! I want to run it now! 21 | 22 | Just follow these simple steps: 23 | 24 | $ wget http://getcomposer.org/composer.phar 25 | $ php composer.phar install 26 | # make sure you have redis installed 27 | $ redis-server 28 | # open client.html in your browser 29 | $ redis-cli PUBLISH notification "this is a test" 30 | $ redis-cli PUBLISH notification "this is another test" 31 | $ redis-cli PUBLISH notification "OMGOMGOMG ITS AMAZING" 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/redis/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EventSource client 6 | 7 | 8 | 9 |

Notifications

10 | 11 |
12 |
13 | 14 | 15 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/redis/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "predis/predis": ">=0.7.0", 4 | "igorw/event-source": "1.0.*" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/redis/stream.php: -------------------------------------------------------------------------------- 1 | pubSubLoop(); 11 | $pubsub->subscribe('notification'); 12 | 13 | foreach (Stream::getHeaders() as $name => $value) { 14 | header("$name: $value"); 15 | } 16 | 17 | $stream = new Stream(); 18 | 19 | foreach ($pubsub as $message) { 20 | if ('message' === $message->kind) { 21 | $stream 22 | ->event() 23 | ->setEvent($message->channel) 24 | ->setData($message->payload) 25 | ->end() 26 | ->flush(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests/Igorw/ 17 | 18 | 19 | 20 | 21 | 22 | ./src/ 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Igorw/EventSource/EchoHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Igorw\EventSource; 13 | 14 | class EchoHandler 15 | { 16 | public function __invoke($chunk) 17 | { 18 | echo $chunk; 19 | ob_flush(); 20 | flush(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Igorw/EventSource/Event.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Igorw\EventSource; 13 | 14 | class Event 15 | { 16 | private $comments = array(); 17 | private $id; 18 | private $event; 19 | private $retry; 20 | private $data = array(); 21 | 22 | public function addComment($comment) 23 | { 24 | $this->comments = array_merge( 25 | $this->comments, 26 | $this->extractNewlines($comment) 27 | ); 28 | 29 | return $this; 30 | } 31 | 32 | public function setId($id) 33 | { 34 | $this->id = $id; 35 | 36 | return $this; 37 | } 38 | 39 | public function setEvent($event) 40 | { 41 | $this->event = $event; 42 | 43 | return $this; 44 | } 45 | 46 | public function setRetry($retry) 47 | { 48 | if (!is_numeric($retry)) { 49 | throw new \InvalidArgumentException('Retry value must be numeric.'); 50 | } 51 | 52 | $this->retry = $retry; 53 | 54 | return $this; 55 | } 56 | 57 | public function setData($data) 58 | { 59 | $this->data = $this->extractNewlines($data); 60 | 61 | return $this; 62 | } 63 | 64 | public function appendData($data) 65 | { 66 | $this->data = array_merge( 67 | $this->data, 68 | $this->extractNewlines($data) 69 | ); 70 | 71 | return $this; 72 | } 73 | 74 | public function dump() 75 | { 76 | $response = $this->getFormattedComments(). 77 | $this->getFormattedId(). 78 | $this->getFormattedEvent(). 79 | $this->getFormattedRetry(). 80 | $this->getFormattedData(); 81 | 82 | return '' !== $response ? $response."\n" : ''; 83 | } 84 | 85 | public function getFormattedComments() 86 | { 87 | return $this->formatLines('', $this->comments); 88 | } 89 | 90 | public function getFormattedId() 91 | { 92 | return $this->formatLines('id', $this->id); 93 | } 94 | 95 | public function getFormattedEvent() 96 | { 97 | return $this->formatLines('event', $this->event); 98 | } 99 | 100 | public function getFormattedRetry() 101 | { 102 | return $this->formatLines('retry', $this->retry); 103 | } 104 | 105 | public function getFormattedData() 106 | { 107 | return $this->formatLines('data', $this->data); 108 | } 109 | 110 | private function extractNewlines($input) 111 | { 112 | return explode("\n", $input); 113 | } 114 | 115 | private function formatLines($key, $lines) 116 | { 117 | $formatted = array_map( 118 | function ($line) use ($key) { 119 | return $key.': '.$line."\n"; 120 | }, 121 | (array) $lines 122 | ); 123 | 124 | return implode('', $formatted); 125 | } 126 | 127 | static public function create() 128 | { 129 | return new static(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Igorw/EventSource/EventWrapper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Igorw\EventSource; 13 | 14 | class EventWrapper 15 | { 16 | private $event; 17 | private $source; 18 | 19 | public function __construct(Event $event, \Closure $source = null) 20 | { 21 | $this->event = $event; 22 | $this->source = $source; 23 | } 24 | 25 | public function getWrappedEvent() 26 | { 27 | return $this->event; 28 | } 29 | 30 | public function end() 31 | { 32 | if ($this->source) { 33 | return call_user_func($this->source); 34 | } 35 | } 36 | 37 | public function __call($name, $args) 38 | { 39 | if (!method_exists($this->event, $name)) { 40 | $message = "Could not call non-existent method '$name' on wrapped event.\n"; 41 | $message .= 'Must be one of: '.implode(', ', get_class_methods('Igorw\EventSource\Event')); 42 | throw new \InvalidArgumentException($message); 43 | } 44 | 45 | $method = array($this->event, $name); 46 | $value = call_user_func_array($method, $args); 47 | 48 | if ($this->event === $value) { 49 | return $this; 50 | } 51 | 52 | return $value; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Igorw/EventSource/Stream.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Igorw\EventSource; 13 | 14 | /** 15 | * Generates a stream in the W3C EventSource format 16 | * http://dev.w3.org/html5/eventsource/ 17 | */ 18 | class Stream 19 | { 20 | private $buffer; 21 | private $handler; 22 | 23 | public function __construct($handler = null) 24 | { 25 | $this->buffer = new \SplQueue(); 26 | $this->buffer->setIteratorMode(\SplQueue::IT_MODE_DELETE); 27 | $this->handler = $handler ?: new EchoHandler(); 28 | } 29 | 30 | public function event() 31 | { 32 | $event = new Event(); 33 | $this->buffer->enqueue($event); 34 | 35 | $that = $this; 36 | 37 | $wrapper = new EventWrapper($event, function () use ($that) { 38 | return $that; 39 | }); 40 | 41 | return $wrapper; 42 | } 43 | 44 | public function flush() 45 | { 46 | foreach ($this->buffer as $event) { 47 | $chunk = $event->dump(); 48 | if ('' !== $chunk) { 49 | call_user_func($this->handler, $chunk); 50 | } 51 | } 52 | } 53 | 54 | public function getHandler() 55 | { 56 | return $this->handler; 57 | } 58 | 59 | static public function getHeaders() 60 | { 61 | return array( 62 | 'Content-Type' => 'text/event-stream', 63 | 'Transfer-Encoding' => 'identity', 64 | 'Cache-Control' => 'no-cache', 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Igorw/Tests/EventSource/EchoHandlerTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Igorw\Tests\EventSource; 13 | 14 | use Igorw\EventSource\EchoHandler; 15 | 16 | class EchoHandlerTest extends \PHPUnit_Framework_TestCase 17 | { 18 | /** 19 | * @covers Igorw\EventSource\EchoHandler 20 | */ 21 | public function testInvoke() 22 | { 23 | $handler = new EchoHandler(); 24 | 25 | ob_start(); 26 | $handler('new year is over since two hours and 6 minutes'); 27 | $output = ob_get_clean(); 28 | 29 | $this->expectOutputString('new year is over since two hours and 6 minutes', $output); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Igorw/Tests/EventSource/EventTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Igorw\Tests\EventSource; 13 | 14 | use Igorw\EventSource\Event; 15 | 16 | class EventTest extends \PHPUnit_Framework_TestCase 17 | { 18 | /** 19 | * @covers Igorw\EventSource\Event 20 | */ 21 | public function testInitialFormattedValuesShouldBeEmpty() 22 | { 23 | $event = new Event(); 24 | $this->assertSame('', $event->getFormattedComments()); 25 | $this->assertSame('', $event->getFormattedId()); 26 | $this->assertSame('', $event->getFormattedEvent()); 27 | $this->assertSame('', $event->getFormattedRetry()); 28 | $this->assertSame('', $event->getFormattedData()); 29 | } 30 | 31 | /** 32 | * @covers Igorw\EventSource\Event::addComment 33 | * @covers Igorw\EventSource\Event::getFormattedComments 34 | */ 35 | public function testCommentFormatting() 36 | { 37 | $event = new Event(); 38 | $event->addComment('a comment'); 39 | $this->assertSame(": a comment\n", $event->getFormattedComments()); 40 | } 41 | 42 | /** 43 | * @covers Igorw\EventSource\Event::addComment 44 | * @covers Igorw\EventSource\Event::getFormattedComments 45 | */ 46 | public function testCommentFormattingWithManyComments() 47 | { 48 | $event = new Event(); 49 | $event->addComment('a comment'); 50 | $event->addComment('a second comment'); 51 | $event->addComment('another comment'); 52 | $this->assertSame(": a comment\n: a second comment\n: another comment\n", $event->getFormattedComments()); 53 | } 54 | 55 | /** 56 | * @covers Igorw\EventSource\Event::setId 57 | * @covers Igorw\EventSource\Event::getFormattedId 58 | */ 59 | public function testIdFormatting() 60 | { 61 | $event = new Event(); 62 | $event->setId('1'); 63 | $this->assertSame("id: 1\n", $event->getFormattedId()); 64 | } 65 | 66 | /** 67 | * @covers Igorw\EventSource\Event::setId 68 | * @covers Igorw\EventSource\Event::getFormattedId 69 | */ 70 | public function testIdOverride() 71 | { 72 | $event = new Event(); 73 | $event->setId('1'); 74 | $this->assertSame("id: 1\n", $event->getFormattedId()); 75 | $event->setId('2'); 76 | $this->assertSame("id: 2\n", $event->getFormattedId()); 77 | } 78 | 79 | /** 80 | * @covers Igorw\EventSource\Event::setEvent 81 | * @covers Igorw\EventSource\Event::getFormattedEvent 82 | */ 83 | public function testEventFormatting() 84 | { 85 | $event = new Event(); 86 | $event->setEvent('foo'); 87 | $this->assertSame("event: foo\n", $event->getFormattedEvent()); 88 | } 89 | 90 | /** 91 | * @covers Igorw\EventSource\Event::setEvent 92 | * @covers Igorw\EventSource\Event::getFormattedEvent 93 | */ 94 | public function testEventOverride() 95 | { 96 | $event = new Event(); 97 | $event->setEvent('foo'); 98 | $this->assertSame("event: foo\n", $event->getFormattedEvent()); 99 | $event->setEvent('bar'); 100 | $this->assertSame("event: bar\n", $event->getFormattedEvent()); 101 | } 102 | 103 | /** 104 | * @covers Igorw\EventSource\Event::setRetry 105 | * @covers Igorw\EventSource\Event::getFormattedRetry 106 | */ 107 | public function testRetryFormatting() 108 | { 109 | $event = new Event(); 110 | $event->setRetry(1000); 111 | $this->assertSame("retry: 1000\n", $event->getFormattedRetry()); 112 | } 113 | 114 | /** 115 | * @covers Igorw\EventSource\Event::setRetry 116 | * @covers Igorw\EventSource\Event::getFormattedRetry 117 | */ 118 | public function testRetryWithZero() 119 | { 120 | $event = new Event(); 121 | $event->setRetry(0); 122 | $this->assertSame("retry: 0\n", $event->getFormattedRetry()); 123 | } 124 | 125 | /** 126 | * @covers Igorw\EventSource\Event::setRetry 127 | * @expectedException InvalidArgumentException 128 | */ 129 | public function testRetryValueMustBeNumeric() 130 | { 131 | $event = new Event(); 132 | $event->setRetry('not an int'); 133 | } 134 | 135 | /** 136 | * @covers Igorw\EventSource\Event::setRetry 137 | * @covers Igorw\EventSource\Event::getFormattedRetry 138 | */ 139 | public function testRetryOverride() 140 | { 141 | $event = new Event(); 142 | $event->setRetry(1000); 143 | $this->assertSame("retry: 1000\n", $event->getFormattedRetry()); 144 | $event->setRetry(3000); 145 | $this->assertSame("retry: 3000\n", $event->getFormattedRetry()); 146 | } 147 | 148 | /** 149 | * @covers Igorw\EventSource\Event::setData 150 | * @covers Igorw\EventSource\Event::getFormattedData 151 | */ 152 | public function testDataFormatting() 153 | { 154 | $event = new Event(); 155 | $event->setData('happy new year'); 156 | $this->assertSame("data: happy new year\n", $event->getFormattedData()); 157 | } 158 | 159 | /** 160 | * @covers Igorw\EventSource\Event::setData 161 | * @covers Igorw\EventSource\Event::getFormattedData 162 | * @covers Igorw\EventSource\Event::extractNewlines 163 | * @covers Igorw\EventSource\Event::formatLines 164 | */ 165 | public function testDataFormattingWithManyLines() 166 | { 167 | $event = new Event(); 168 | $event->setData("we wish you a merry christmas\nand a happy new year"); 169 | $this->assertSame("data: we wish you a merry christmas\ndata: and a happy new year\n", $event->getFormattedData()); 170 | } 171 | 172 | /** 173 | * @covers Igorw\EventSource\Event::appendData 174 | * @covers Igorw\EventSource\Event::getFormattedData 175 | */ 176 | public function testDataFormattingWithAppend() 177 | { 178 | $event = new Event(); 179 | $event->appendData('we wish you a merry christmas'); 180 | $event->appendData('and a happy new year'); 181 | $this->assertSame("data: we wish you a merry christmas\ndata: and a happy new year\n", $event->getFormattedData()); 182 | } 183 | 184 | /** 185 | * @covers Igorw\EventSource\Event::dump 186 | */ 187 | public function testDumpIncludesEverything() 188 | { 189 | $event = new Event(); 190 | $event->addComment('a juicy comment'); 191 | $event->setId('11'); 192 | $event->setEvent('foo'); 193 | $event->setRetry(2000); 194 | $event->setData("we wish you a merry christmas\nand a happy new year"); 195 | 196 | $expected = <<assertSame($expected, $event->dump()); 207 | } 208 | 209 | /** 210 | * @covers Igorw\EventSource\Event::create 211 | */ 212 | public function testCreate() 213 | { 214 | $event = Event::create(); 215 | $this->assertInstanceOf('Igorw\EventSource\Event', $event); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /tests/Igorw/Tests/EventSource/EventWrapperTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Igorw\Tests\EventSource; 13 | 14 | use Igorw\EventSource\EventWrapper; 15 | 16 | class EventWrapperTest extends \PHPUnit_Framework_TestCase 17 | { 18 | /** 19 | * @covers Igorw\EventSource\EventWrapper::__construct 20 | * @covers Igorw\EventSource\EventWrapper::getWrappedEvent 21 | */ 22 | public function testGetWrappedEvent() 23 | { 24 | $event = $this->getMock('Igorw\EventSource\Event'); 25 | $wrapper = new EventWrapper($event); 26 | $this->assertSame($event, $wrapper->getWrappedEvent()); 27 | } 28 | 29 | /** 30 | * @covers Igorw\EventSource\EventWrapper::__construct 31 | * @covers Igorw\EventSource\EventWrapper::end 32 | */ 33 | public function testEnd() 34 | { 35 | $event = $this->getMock('Igorw\EventSource\Event'); 36 | $stream = $this->getMock('Igorw\EventSource\Stream'); 37 | $source = function () use ($stream) { 38 | return $stream; 39 | }; 40 | $wrapper = new EventWrapper($event, $source); 41 | $this->assertSame($stream, $wrapper->end()); 42 | } 43 | 44 | /** 45 | * @covers Igorw\EventSource\EventWrapper::__construct 46 | * @covers Igorw\EventSource\EventWrapper::end 47 | */ 48 | public function testEndWithoutSource() 49 | { 50 | $event = $this->getMock('Igorw\EventSource\Event'); 51 | $wrapper = new EventWrapper($event); 52 | $this->assertSame(null, $wrapper->end()); 53 | } 54 | 55 | /** 56 | * @covers Igorw\EventSource\EventWrapper::__construct 57 | * @covers Igorw\EventSource\EventWrapper::__call 58 | */ 59 | public function testWrappingMethodCalls() 60 | { 61 | $event = $this->getMock('Igorw\EventSource\Event'); 62 | $event 63 | ->expects($this->once()) 64 | ->method('addComment') 65 | ->will($this->returnValue($event)); 66 | $event 67 | ->expects($this->once()) 68 | ->method('setId') 69 | ->will($this->returnValue($event)); 70 | $event 71 | ->expects($this->once()) 72 | ->method('setEvent') 73 | ->will($this->returnValue($event)); 74 | $event 75 | ->expects($this->once()) 76 | ->method('setData') 77 | ->will($this->returnValue($event)); 78 | 79 | $stream = $this->getMock('Igorw\EventSource\Stream'); 80 | $source = function () use ($stream) { 81 | return $stream; 82 | }; 83 | 84 | $wrapper = new EventWrapper($event, $source); 85 | $wrapper 86 | ->addComment('a comment') 87 | ->setId('1') 88 | ->setEvent('foo') 89 | ->setData('new year is over'); 90 | } 91 | 92 | /** 93 | * @covers Igorw\EventSource\EventWrapper::__construct 94 | * @covers Igorw\EventSource\EventWrapper::__call 95 | */ 96 | public function testWrappingMethodCallsWithDump() 97 | { 98 | $event = $this->getMock('Igorw\EventSource\Event'); 99 | $event 100 | ->expects($this->once()) 101 | ->method('dump') 102 | ->will($this->returnValue('')); 103 | 104 | $wrapper = new EventWrapper($event); 105 | $wrapper 106 | ->dump(); 107 | } 108 | 109 | /** 110 | * @covers Igorw\EventSource\EventWrapper::__construct 111 | * @covers Igorw\EventSource\EventWrapper::__call 112 | * @expectedException InvalidArgumentException 113 | * @expectedExceptionMessage Could not call non-existent method 'nonExistentMethod' on wrapped event. 114 | */ 115 | public function testCallingNonExistentMethod() 116 | { 117 | $event = $this->getMock('Igorw\EventSource\Event'); 118 | 119 | $wrapper = new EventWrapper($event); 120 | $wrapper 121 | ->nonExistentMethod(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/Igorw/Tests/EventSource/StreamTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Igorw\Tests\EventSource; 13 | 14 | use Igorw\EventSource\Stream; 15 | 16 | class StreamTest extends \PHPUnit_Framework_TestCase 17 | { 18 | /** 19 | * @covers Igorw\EventSource\Stream::__construct 20 | * @covers Igorw\EventSource\Stream::event 21 | */ 22 | public function testEventReturnsEventWrapper() 23 | { 24 | $handler = function () {}; 25 | $stream = new Stream($handler); 26 | $this->assertInstanceOf('Igorw\EventSource\EventWrapper', $stream->event()); 27 | } 28 | 29 | /** 30 | * @covers Igorw\EventSource\Stream::__construct 31 | * @covers Igorw\EventSource\Stream::event 32 | */ 33 | public function testEventWrapperGetsSource() 34 | { 35 | $handler = function () {}; 36 | $stream = new Stream($handler); 37 | $wrapper = $stream->event(); 38 | $this->assertSame($stream, $wrapper->end()); 39 | } 40 | 41 | /** 42 | * @covers Igorw\EventSource\Stream::__construct 43 | * @covers Igorw\EventSource\Stream::flush 44 | */ 45 | public function testFlushCallsHandler() 46 | { 47 | $i = 0; 48 | 49 | $handler = function ($chunk) use (&$i) { 50 | $i++; 51 | }; 52 | 53 | $stream = new Stream($handler); 54 | $stream->event() 55 | ->setData('new year is over since one hour and 44 minutes'); 56 | 57 | $this->assertSame(0, $i); 58 | 59 | $stream->flush(); 60 | 61 | $this->assertSame(1, $i); 62 | } 63 | 64 | /** 65 | * @covers Igorw\EventSource\Stream::__construct 66 | * @covers Igorw\EventSource\Stream::flush 67 | */ 68 | public function testHandlerGetsData() 69 | { 70 | $that = $this; 71 | 72 | $handler = function ($chunk) use ($that) { 73 | $that->assertContains('new year is over since one hour and 48 minutes', $chunk); 74 | }; 75 | 76 | $stream = new Stream($handler); 77 | $stream->event() 78 | ->setData('new year is over since one hour and 48 minutes'); 79 | 80 | $stream->flush(); 81 | } 82 | 83 | /** 84 | * @covers Igorw\EventSource\Stream::__construct 85 | * @covers Igorw\EventSource\Stream::flush 86 | */ 87 | public function testFlushWithoutEvents() 88 | { 89 | $that = $this; 90 | 91 | $handler = function ($chunk) use ($that) { 92 | $that->fail('Handler was invoked although no events given'); 93 | }; 94 | 95 | $stream = new Stream($handler); 96 | $stream->flush(); 97 | } 98 | 99 | /** 100 | * @covers Igorw\EventSource\Stream::__construct 101 | * @covers Igorw\EventSource\Stream::getHandler 102 | */ 103 | public function testGetHandler() 104 | { 105 | $handler = function ($chunk) {}; 106 | $stream = new Stream($handler); 107 | $this->assertSame($handler, $stream->getHandler()); 108 | } 109 | 110 | /** 111 | * @covers Igorw\EventSource\Stream::__construct 112 | * @covers Igorw\EventSource\Stream::getHandler 113 | */ 114 | public function testDefaultEchoHandler() 115 | { 116 | $stream = new Stream(); 117 | $this->assertInstanceOf('Igorw\EventSource\EchoHandler', $stream->getHandler()); 118 | } 119 | 120 | /** 121 | * @covers Igorw\EventSource\Stream::__construct 122 | * @covers Igorw\EventSource\Stream::getHeaders 123 | */ 124 | public function testGetHeaders() 125 | { 126 | $headers = Stream::getHeaders(); 127 | $this->assertInternalType('array', $headers); 128 | $this->assertSame('text/event-stream', $headers['Content-Type']); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('Igorw\Tests', __DIR__); 5 | --------------------------------------------------------------------------------