├── .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 | [](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 |
--------------------------------------------------------------------------------