Skip to content

Commit 46ee38c

Browse files
authored
Merge pull request #41 from clue-labs/messageevent-constructor
Public `MessageEvent` constructor
2 parents cb72ebf + 034ac2f commit 46ee38c

File tree

3 files changed

+154
-8
lines changed

3 files changed

+154
-8
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ of [ReactPHP](https://reactphp.org/)'s event-driven architecture.
2020
* [EventSource::$url](#eventsourceurl)
2121
* [close()](#close)
2222
* [MessageEvent](#messageevent)
23+
* [MessageEvent::__construct()](#messageevent__construct)
2324
* [MessageEvent::$data](#messageeventdata)
2425
* [MessageEvent::$lastEventId](#messageeventlasteventid)
2526
* [MessageEvent::$type](#messageeventtype)
@@ -240,6 +241,19 @@ This will close any active connections or connection attempts and go into the
240241

241242
The `MessageEvent` class represents an incoming EventSource message.
242243

244+
#### MessageEvent::__construct()
245+
246+
The `new MessageEvent(string $data, string $lastEventId = '', string $type = 'message')` constructor can be used to
247+
create a new `MessageEvent` instance.
248+
249+
This is mostly used internally to represent each incoming message event
250+
(see also [`message` event](#message-event)). Likewise, you can also use
251+
this class in test cases to test how your application reacts to incoming
252+
messages.
253+
254+
The constructor validates and initializes all properties of this class.
255+
It throws an `InvalidArgumentException` if any parameters are invalid.
256+
243257
#### MessageEvent::$data
244258

245259
The `readonly string $data` property can be used to

src/MessageEvent.php

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public static function parse($data, $lastEventId, &$retryTime = 0.0)
4343
$data = substr($data, 0, -1);
4444
}
4545

46+
/** @throws void because parameter values are validated above already */
4647
return new self($data, $id, $type);
4748
}
4849

@@ -52,15 +53,41 @@ private static function utf8($string)
5253
return \htmlspecialchars_decode(\htmlspecialchars($string, \ENT_NOQUOTES | \ENT_SUBSTITUTE, 'utf-8'));
5354
}
5455

56+
/** @return bool */
57+
private static function isUtf8($string)
58+
{
59+
return $string === self::utf8($string);
60+
}
61+
5562
/**
56-
* @internal
57-
* @param string $data
58-
* @param string $lastEventId
59-
* @param string $type
63+
* Create a new `MessageEvent` instance.
64+
*
65+
* This is mostly used internally to represent each incoming message event
66+
* (see also [`message` event](#message-event)). Likewise, you can also use
67+
* this class in test cases to test how your application reacts to incoming
68+
* messages.
69+
*
70+
* The constructor validates and initializes all properties of this class.
71+
* It throws an `InvalidArgumentException` if any parameters are invalid.
72+
*
73+
* @param string $data message data (requires valid UTF-8 data, possibly multi-line)
74+
* @param string $lastEventId optional last event ID (defaults to empty string, requires valid UTF-8, no null bytes, single line)
75+
* @param string $type optional event type (defaults to "message", requires valid UTF-8, single line)
76+
* @throws \InvalidArgumentException if any parameters are invalid
6077
*/
61-
private function __construct($data, $lastEventId, $type)
78+
final public function __construct($data, $lastEventId = '', $type = 'message')
6279
{
63-
$this->data = $data;
80+
if (!self::isUtf8($data)) {
81+
throw new \InvalidArgumentException('Invalid $data given, must be valid UTF-8 string');
82+
}
83+
if (!self::isUtf8($lastEventId) || \strpos($lastEventId, "\0") !== false || \strpos($lastEventId, "\r") !== false || \strpos($lastEventId, "\n") !== false) {
84+
throw new \InvalidArgumentException('Invalid $lastEventId given, must be valid UTF-8 string with no null bytes or newline characters');
85+
}
86+
if (!self::isUtf8($type) || $type === '' || \strpos($type, "\r") !== false || \strpos($type, "\n")) {
87+
throw new \InvalidArgumentException('Invalid $type given, must be valid UTF-8 string with no newline characters');
88+
}
89+
90+
$this->data = \preg_replace("/\r\n?/", "\n", $data);
6491
$this->lastEventId = $lastEventId;
6592
$this->type = $type;
6693
}
@@ -72,13 +99,13 @@ private function __construct($data, $lastEventId, $type)
7299
public $data = '';
73100

74101
/**
75-
* @var string
102+
* @var string defaults to empty string
76103
* @readonly
77104
*/
78105
public $lastEventId = '';
79106

80107
/**
81-
* @var string
108+
* @var string defaults to "message"
82109
* @readonly
83110
*/
84111
public $type = 'message';

tests/MessageEventTest.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,109 @@ public function testParseRetryTime($input, $expected)
175175

176176
$this->assertSame($expected, $retryTime);
177177
}
178+
179+
public function testConstructWithDefaultLastEventIdAndType()
180+
{
181+
$message = new MessageEvent('hello');
182+
183+
$this->assertEquals('hello', $message->data);
184+
$this->assertEquals('', $message->lastEventId);
185+
$this->assertEquals('message', $message->type);
186+
}
187+
188+
public function testConstructWithEmptyDataAndId()
189+
{
190+
$message = new MessageEvent('', '');
191+
192+
$this->assertEquals('', $message->data);
193+
$this->assertEquals('', $message->lastEventId);
194+
$this->assertEquals('message', $message->type);
195+
}
196+
197+
public function testConstructWithNullBytesInDataAndType()
198+
{
199+
$message = new MessageEvent("h\x00llo!", '', "h\x00llo!");
200+
201+
$this->assertEquals("h\x00llo!", $message->data);
202+
$this->assertEquals('', $message->lastEventId);
203+
$this->assertEquals("h\x00llo!", $message->type);
204+
}
205+
206+
public function testConstructWithCarriageReturnAndLineFeedsInDataReplacedWithSimpleLineFeeds()
207+
{
208+
$message = new MessageEvent("hello\rworld!\r\n");
209+
210+
$this->assertEquals("hello\nworld!\n", $message->data);
211+
}
212+
213+
public function testConstructWithInvalidDataUtf8Throws()
214+
{
215+
$this->setExpectedException('InvalidArgumentException', 'Invalid $data given, must be valid UTF-8 string');
216+
new MessageEvent("h\xFFllo!");
217+
}
218+
219+
public function testConstructWithInvalidLastEventIdUtf8Throws()
220+
{
221+
$this->setExpectedException('InvalidArgumentException', 'Invalid $lastEventId given, must be valid UTF-8 string with no null bytes or newline characters');
222+
new MessageEvent('hello', "h\xFFllo");
223+
}
224+
225+
public function testConstructWithInvalidLastEventIdNullThrows()
226+
{
227+
$this->setExpectedException('InvalidArgumentException', 'Invalid $lastEventId given, must be valid UTF-8 string with no null bytes or newline characters');
228+
new MessageEvent('hello', "h\x00llo");
229+
}
230+
231+
public function testConstructWithInvalidLastEventIdCarriageReturnThrows()
232+
{
233+
$this->setExpectedException('InvalidArgumentException', 'Invalid $lastEventId given, must be valid UTF-8 string with no null bytes or newline characters');
234+
new MessageEvent('hello', "hello\r");
235+
}
236+
237+
public function testConstructWithInvalidLastEventIdLineFeedThrows()
238+
{
239+
$this->setExpectedException('InvalidArgumentException', 'Invalid $lastEventId given, must be valid UTF-8 string with no null bytes or newline characters');
240+
new MessageEvent('hello', "hello\n");
241+
}
242+
243+
public function testConstructWithInvalidTypeUtf8Throws()
244+
{
245+
$this->setExpectedException('InvalidArgumentException', 'Invalid $type given, must be valid UTF-8 string with no newline characters');
246+
new MessageEvent('hello', '', "h\xFFllo");
247+
}
248+
249+
public function testConstructWithInvalidTypeEmptyThrows()
250+
{
251+
$this->setExpectedException('InvalidArgumentException', 'Invalid $type given, must be valid UTF-8 string with no newline characters');
252+
new MessageEvent('hello', '', '');
253+
}
254+
255+
public function testConstructWithInvalidTypeCarriageReturnThrows()
256+
{
257+
$this->setExpectedException('InvalidArgumentException', 'Invalid $type given, must be valid UTF-8 string with no newline characters');
258+
new MessageEvent('hello', '', "hello\r");
259+
}
260+
261+
public function testConstructWithInvalidTypeLineFeedThrows()
262+
{
263+
$this->setExpectedException('InvalidArgumentException', 'Invalid $type given, must be valid UTF-8 string with no newline characters');
264+
new MessageEvent('hello', '', "hello\r");
265+
}
266+
267+
public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null)
268+
{
269+
if (method_exists($this, 'expectException')) {
270+
// PHPUnit 5.2+
271+
$this->expectException($exception);
272+
if ($exceptionMessage !== '') {
273+
$this->expectExceptionMessage($exceptionMessage);
274+
}
275+
if ($exceptionCode !== null) {
276+
$this->expectExceptionCode($exceptionCode);
277+
}
278+
} else {
279+
// legacy PHPUnit < 5.2
280+
parent::setExpectedException($exception, $exceptionMessage, $exceptionCode);
281+
}
282+
}
178283
}

0 commit comments

Comments
 (0)