aboutsummaryrefslogtreecommitdiffhomepage
path: root/assets/php/vendor/react/stream
diff options
context:
space:
mode:
authormarvin-borner@live.com2018-04-10 21:50:16 +0200
committermarvin-borner@live.com2018-04-10 21:54:48 +0200
commitfc9401f04a3aca5abb22f87ebc210de8afe11d32 (patch)
treeb0b310f3581764ec3955f4e496a05137a32951c3 /assets/php/vendor/react/stream
parent286d643180672f20526f3dc3bd19d7b751e2fa97 (diff)
Initial Commit
Diffstat (limited to 'assets/php/vendor/react/stream')
-rw-r--r--assets/php/vendor/react/stream/.gitignore2
-rw-r--r--assets/php/vendor/react/stream/.travis.yml50
-rw-r--r--assets/php/vendor/react/stream/CHANGELOG.md377
-rw-r--r--assets/php/vendor/react/stream/LICENSE19
-rw-r--r--assets/php/vendor/react/stream/README.md1224
-rw-r--r--assets/php/vendor/react/stream/composer.json25
-rw-r--r--assets/php/vendor/react/stream/examples/01-http.php40
-rw-r--r--assets/php/vendor/react/stream/examples/02-https.php40
-rw-r--r--assets/php/vendor/react/stream/examples/11-cat.php28
-rw-r--r--assets/php/vendor/react/stream/examples/91-benchmark-throughput.php62
-rw-r--r--assets/php/vendor/react/stream/phpunit.xml.dist25
-rw-r--r--assets/php/vendor/react/stream/src/CompositeStream.php82
-rw-r--r--assets/php/vendor/react/stream/src/DuplexResourceStream.php224
-rw-r--r--assets/php/vendor/react/stream/src/DuplexStreamInterface.php39
-rw-r--r--assets/php/vendor/react/stream/src/ReadableResourceStream.php177
-rw-r--r--assets/php/vendor/react/stream/src/ReadableStreamInterface.php362
-rw-r--r--assets/php/vendor/react/stream/src/ThroughStream.php190
-rw-r--r--assets/php/vendor/react/stream/src/Util.php75
-rw-r--r--assets/php/vendor/react/stream/src/WritableResourceStream.php171
-rw-r--r--assets/php/vendor/react/stream/src/WritableStreamInterface.php347
-rw-r--r--assets/php/vendor/react/stream/tests/CallableStub.php10
-rw-r--r--assets/php/vendor/react/stream/tests/CompositeStreamTest.php267
-rw-r--r--assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php352
-rw-r--r--assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php495
-rw-r--r--assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php35
-rw-r--r--assets/php/vendor/react/stream/tests/FunctionalInternetTest.php122
-rw-r--r--assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php372
-rw-r--r--assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php61
-rw-r--r--assets/php/vendor/react/stream/tests/TestCase.php54
-rw-r--r--assets/php/vendor/react/stream/tests/ThroughStreamTest.php267
-rw-r--r--assets/php/vendor/react/stream/tests/UtilTest.php273
-rw-r--r--assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php534
32 files changed, 6401 insertions, 0 deletions
diff --git a/assets/php/vendor/react/stream/.gitignore b/assets/php/vendor/react/stream/.gitignore
new file mode 100644
index 0000000..987e2a2
--- /dev/null
+++ b/assets/php/vendor/react/stream/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/assets/php/vendor/react/stream/.travis.yml b/assets/php/vendor/react/stream/.travis.yml
new file mode 100644
index 0000000..f4e3376
--- /dev/null
+++ b/assets/php/vendor/react/stream/.travis.yml
@@ -0,0 +1,50 @@
+language: php
+
+php:
+# - 5.3 # requires old distro, see below
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+# - 7.0 # Mac OS X test setup, ignore errors, see below
+ - 7.1
+ - 7.2
+ - nightly # ignore errors, see below
+ - hhvm # ignore errors, see below
+
+# lock distro so new future defaults will not break the build
+dist: trusty
+
+matrix:
+ include:
+ - php: 5.3
+ dist: precise
+ include:
+ - os: osx
+ language: generic
+ php: 7.0 # just to look right on travis
+ env:
+ - PACKAGE: php70
+ allow_failures:
+ - php: nightly
+ - php: hhvm
+ - os: osx
+
+install:
+ # OSX install inspired by https://github.com/kiler129/TravisCI-OSX-PHP
+ - |
+ if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
+ brew tap homebrew/homebrew-php
+ echo "Installing PHP ..."
+ brew install "${PACKAGE}"
+ brew install "${PACKAGE}"-xdebug
+ brew link "${PACKAGE}"
+ echo "Installing composer ..."
+ curl -s http://getcomposer.org/installer | php
+ mv composer.phar /usr/local/bin/composer
+ fi
+ - composer install --no-interaction
+
+script:
+ - vendor/bin/phpunit --coverage-text
+ - time php examples/91-benchmark-throughput.php
diff --git a/assets/php/vendor/react/stream/CHANGELOG.md b/assets/php/vendor/react/stream/CHANGELOG.md
new file mode 100644
index 0000000..f64815d
--- /dev/null
+++ b/assets/php/vendor/react/stream/CHANGELOG.md
@@ -0,0 +1,377 @@
+# Changelog
+
+## 0.7.7 (2018-01-19)
+
+* Improve test suite by fixing forward compatibility with upcoming EventLoop
+ releases, avoid risky tests and add test group to skip integration tests
+ relying on internet connection and apply appropriate test timeouts.
+ (#128, #131 and #132 by @clue)
+
+## 0.7.6 (2017-12-21)
+
+* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12
+ (#126 by @clue)
+
+* Improve test suite by simplifying test bootstrapping logic via Composer and
+ test against PHP 7.2
+ (#127 by @clue and #124 by @carusogabriel)
+
+## 0.7.5 (2017-11-20)
+
+* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream`
+ (#119 by @clue)
+
+* Fix: Fix forward compatibility with upcoming EventLoop releases
+ (#121 by @clue)
+
+* Restructure examples to ease getting started
+ (#123 by @clue)
+
+* Improve test suite by adding forward compatibility with PHPUnit 6 and
+ ignore Mac OS X test failures for now until Travis tests work again
+ (#122 by @gabriel-caruso and #120 by @clue)
+
+## 0.7.4 (2017-10-11)
+
+* Fix: Remove event listeners from `CompositeStream` once closed and
+ remove undocumented left-over `close` event argument
+ (#116 by @clue)
+
+* Minor documentation improvements: Fix wrong class name in example,
+ fix typos in README and
+ fix forward compatibility with upcoming EventLoop releases in example
+ (#113 by @docteurklein and #114 and #115 by @clue)
+
+* Improve test suite by running against Mac OS X on Travis
+ (#112 by @clue)
+
+## 0.7.3 (2017-08-05)
+
+* Improvement: Support Événement 3.0 a long side 2.0 and 1.0
+ (#108 by @WyriHaximus)
+
+* Readme: Corrected loop initialization in usage example
+ (#109 by @pulyavin)
+
+* Travis: Lock linux distribution preventing future builds from breaking
+ (#110 by @clue)
+
+## 0.7.2 (2017-06-15)
+
+* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream.
+ (#107 by @WyriHaximus)
+
+## 0.7.1 (2017-05-20)
+
+* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of
+ bytes to write at once.
+ (#105 by @clue)
+
+ ```php
+ $stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+ ```
+
+* Ignore HHVM test failures for now until Travis tests work again
+ (#106 by @clue)
+
+## 0.7.0 (2017-05-04)
+
+* Removed / BC break: Remove deprecated and unneeded functionality
+ (#45, #87, #90, #91 and #93 by @clue)
+
+ * Remove deprecated `Stream` class, use `DuplexResourceStream` instead
+ (#87 by @clue)
+
+ * Remove public `$buffer` property, use new constructor parameters instead
+ (#91 by @clue)
+
+ * Remove public `$stream` property from all resource streams
+ (#90 by @clue)
+
+ * Remove undocumented and now unused `ReadableStream` and `WritableStream`
+ (#93 by @clue)
+
+ * Remove `BufferedSink`
+ (#45 by @clue)
+
+* Feature / BC break: Simplify `ThroughStream` by using data callback instead of
+ inheritance. It is now a direct implementation of `DuplexStreamInterface`.
+ (#88 and #89 by @clue)
+
+ ```php
+ $through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+ });
+ $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+ $through->write(array(2, true));
+ ```
+
+* Feature / BC break: The `CompositeStream` starts closed if either side is
+ already closed and forwards pause to pipe source on first write attempt.
+ (#96 and #103 by @clue)
+
+ If either side of the composite stream closes, it will also close the other
+ side. We now also ensure that if either side is already closed during
+ instantiation, it will also close the other side.
+
+* BC break: Mark all classes as `final` and
+ mark internal API as `private` to discourage inheritance
+ (#95 and #99 by @clue)
+
+* Feature / BC break: Only emit `error` event for fatal errors
+ (#92 by @clue)
+
+ > The `error` event was previously also allowed to be emitted for non-fatal
+ errors, but our implementations actually only ever emitted this as a fatal
+ error and then closed the stream.
+
+* Feature: Explicitly allow custom events and exclude any semantics
+ (#97 by @clue)
+
+* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation
+ (#100 and #102 by @clue)
+
+* Actually require all dependencies so this is self-contained and improve
+ forward compatibility with EventLoop v1.0 and v0.5
+ (#94 and #98 by @clue)
+
+## 0.6.0 (2017-03-26)
+
+* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream`
+ (#85 by @clue)
+
+ ```php
+ // old (does still work for BC reasons)
+ $stream = new Stream($connection, $loop);
+
+ // new
+ $stream = new DuplexResourceStream($connection, $loop);
+ ```
+
+ Note that the `DuplexResourceStream` now rejects read-only or write-only
+ streams, so this may affect BC. If you want a read-only or write-only
+ resource, use `ReadableResourceStream` or `WritableResourceStream` instead of
+ `DuplexResourceStream`.
+
+ > BC note: This class was previously called `Stream`. The `Stream` class still
+ exists for BC reasons and will be removed in future versions of this package.
+
+* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`)
+ (#84 by @clue)
+
+ ```php
+ // old
+ $stream = new Buffer(STDOUT, $loop);
+
+ // new
+ $stream = new WritableResourceStream(STDOUT, $loop);
+ ```
+
+* Feature: Add `ReadableResourceStream`
+ (#83 by @clue)
+
+ ```php
+ $stream = new ReadableResourceStream(STDIN, $loop);
+ ```
+
+* Fix / BC Break: Enforce using non-blocking I/O
+ (#46 by @clue)
+
+ > BC note: This is known to affect process pipes on Windows which do not
+ support non-blocking I/O and could thus block the whole EventLoop previously.
+
+* Feature / Fix / BC break: Consistent semantics for
+ `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side
+ (#86 by @clue)
+
+* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4
+ (#80 by @clue)
+
+## 0.5.0 (2017-03-08)
+
+* Feature / BC break: Consistent `end` event semantics (EOF)
+ (#70 by @clue)
+
+ The `end` event will now only be emitted for a *successful* end, not if the
+ stream closes due to an unrecoverable `error` event or if you call `close()`
+ explicitly.
+ If you want to detect when the stream closes (terminates), use the `close`
+ event instead.
+
+* BC break: Remove custom (undocumented) `full-drain` event from `Buffer`
+ (#63 and #68 by @clue)
+
+ > The `full-drain` event was undocumented and mostly used internally.
+ Relying on this event has attracted some low-quality code in the past, so
+ we've removed this from the public API in order to work out a better
+ solution instead.
+ If you want to detect when the buffer finishes flushing data to the stream,
+ you may want to look into its `end()` method or the `close` event instead.
+
+* Feature / BC break: Consistent event semantics and documentation,
+ explicitly state *when* events will be emitted and *which* arguments they
+ receive.
+ (#73 and #69 by @clue)
+
+ The documentation now explicitly defines each event and its arguments.
+ Custom events and event arguments are still supported.
+ Most notably, all defined events only receive inherently required event
+ arguments and no longer transmit the instance they are emitted on for
+ consistency and performance reasons.
+
+ ```php
+ // old (inconsistent and not supported by all implementations)
+ $stream->on('data', function ($data, $stream) {
+ // process $data
+ });
+
+ // new (consistent throughout the whole ecosystem)
+ $stream->on('data', function ($data) use ($stream) {
+ // process $data
+ });
+ ```
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature / BC break: Consistent method semantics and documentation
+ (#72 by @clue)
+
+ > This mostly adds documentation (and thus some stricter, consistent
+ definitions) for the existing behavior, it does NOT define any major
+ changes otherwise.
+ Most existing code should be compatible with these changes, unless
+ it relied on some undocumented/unintended semantics.
+
+* Feature: Consistent `pipe()` semantics for closed and closing streams
+ (#71 from @clue)
+
+ The source stream will now always be paused via `pause()` when the
+ destination stream closes. Also, properly stop piping if the source
+ stream closes and remove all event forwarding.
+
+* Improve test suite by adding PHPUnit to `require-dev` and improving coverage.
+ (#74 and #75 by @clue, #66 by @nawarian)
+
+## 0.4.6 (2017-01-25)
+
+* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone)
+ (#62 by @clue)
+
+* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream`
+ (#60 by @clue)
+
+* Fix: Consistent `close` event behavior for `Buffer`
+ (#61 by @clue)
+
+## 0.4.5 (2016-11-13)
+
+* Feature: Support setting read buffer size to `null` (infinite)
+ (#42 by @clue)
+
+* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event
+ (#55 by @clue)
+
+* Vastly improved performance by factor of 10x to 20x.
+ Raise default buffer sizes to 64 KiB and simplify and improve error handling
+ and unneeded function calls.
+ (#53, #55, #56 by @clue)
+
+## 0.4.4 (2016-08-22)
+
+* Bug fix: Emit `error` event and close `Stream` when accessing the underlying
+ stream resource fails with a permanent error.
+ (#52 and #40 by @clue, #25 by @lysenkobv)
+
+* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF)
+ (#39 by @clue)
+
+* Bug fix: Ignore empty writes to `Buffer`
+ (#51 by @clue)
+
+* Add benchmarking script to measure throughput in CI
+ (#41 by @clue)
+
+## 0.4.3 (2015-10-07)
+
+* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau)
+* Bug fix: No double-write during drain call (@arnaud-lb)
+* Bug fix: Support HHVM (@clue)
+* Adjust compatibility to 5.3 (@clue)
+
+## 0.4.2 (2014-09-09)
+
+* Added DuplexStreamInterface
+* Stream sets stream resources to non-blocking
+* Fixed potential race condition in pipe
+
+## 0.4.1 (2014-04-13)
+
+* Bug fix: v0.3.4 changes merged for v0.4.1
+
+## 0.3.4 (2014-03-30)
+
+* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream
+
+## 0.4.0 (2014-02-02)
+
+* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
+* BC break: Update to Evenement 2.0
+* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
+
+## 0.3.3 (2013-07-08)
+
+* Bug fix: [Stream] Correctly detect closed connections
+
+## 0.3.2 (2013-05-10)
+
+* Bug fix: [Stream] Make sure CompositeStream is closed properly
+
+## 0.3.1 (2013-04-21)
+
+* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()`
+
+## 0.3.0 (2013-04-14)
+
+* Feature: [Stream] Factory method for BufferedSink
+
+## 0.2.6 (2012-12-26)
+
+* Version bump
+
+## 0.2.5 (2012-11-26)
+
+* Feature: Make BufferedSink trigger progress events on the promise (@jsor)
+
+## 0.2.4 (2012-11-18)
+
+* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream
+* Feature: Added BufferedSink
+
+## 0.2.3 (2012-11-14)
+
+* Version bump
+
+## 0.2.2 (2012-10-28)
+
+* Version bump
+
+## 0.2.1 (2012-10-14)
+
+* Bug fix: Check for EOF in `Buffer::write()`
+
+## 0.2.0 (2012-09-10)
+
+* Version bump
+
+## 0.1.1 (2012-07-12)
+
+* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8
+
+## 0.1.0 (2012-07-11)
+
+* First tagged release
diff --git a/assets/php/vendor/react/stream/LICENSE b/assets/php/vendor/react/stream/LICENSE
new file mode 100644
index 0000000..a808108
--- /dev/null
+++ b/assets/php/vendor/react/stream/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Igor Wiedler, Chris Boden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/assets/php/vendor/react/stream/README.md b/assets/php/vendor/react/stream/README.md
new file mode 100644
index 0000000..c362534
--- /dev/null
+++ b/assets/php/vendor/react/stream/README.md
@@ -0,0 +1,1224 @@
+# Stream
+
+[![Build Status](https://travis-ci.org/reactphp/stream.svg?branch=master)](https://travis-ci.org/reactphp/stream)
+
+Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/).
+
+In order to make the [EventLoop](https://github.com/reactphp/event-loop)
+easier to use, this component introduces the powerful concept of "streams".
+Streams allow you to efficiently process huge amounts of data (such as a multi
+Gigabyte file download) in small chunks without having to store everything in
+memory at once.
+They are very similar to the streams found in PHP itself,
+but have an interface more suited for async, non-blocking I/O.
+
+**Table of contents**
+
+* [Stream usage](#stream-usage)
+ * [ReadableStreamInterface](#readablestreaminterface)
+ * [data event](#data-event)
+ * [end event](#end-event)
+ * [error event](#error-event)
+ * [close event](#close-event)
+ * [isReadable()](#isreadable)
+ * [pause()](#pause)
+ * [resume()](#resume)
+ * [pipe()](#pipe)
+ * [close()](#close)
+ * [WritableStreamInterface](#writablestreaminterface)
+ * [drain event](#drain-event)
+ * [pipe event](#pipe-event)
+ * [error event](#error-event-1)
+ * [close event](#close-event-1)
+ * [isWritable()](#iswritable)
+ * [write()](#write)
+ * [end()](#end)
+ * [close()](#close-1)
+ * [DuplexStreamInterface](#duplexstreaminterface)
+* [Creating streams](#creating-streams)
+ * [ReadableResourceStream](#readableresourcestream)
+ * [WritableResourceStream](#writableresourcestream)
+ * [DuplexResourceStream](#duplexresourcestream)
+ * [ThroughStream](#throughstream)
+ * [CompositeStream](#compositestream)
+* [Usage](#usage)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+* [More](#more)
+
+## Stream usage
+
+ReactPHP uses the concept of "streams" throughout its ecosystem to provide a
+consistent higher-level abstraction for processing streams of arbitrary data
+contents and size.
+While a stream itself is a quite low-level concept, it can be used as a powerful
+abstraction to build higher-level components and protocols on top.
+
+If you're new to this concept, it helps to think of them as a water pipe:
+You can consume water from a source or you can produce water and forward (pipe)
+it to any destination (sink).
+
+Similarly, streams can either be
+
+* readable (such as `STDIN` terminal input) or
+* writable (such as `STDOUT` terminal output) or
+* duplex (both readable *and* writable, such as a TCP/IP connection)
+
+Accordingly, this package defines the following three interfaces
+
+* [`ReadableStreamInterface`](#readablestreaminterface)
+* [`WritableStreamInterface`](#writablestreaminterface)
+* [`DuplexStreamInterface`](#duplexstreaminterface)
+
+### ReadableStreamInterface
+
+The `ReadableStreamInterface` is responsible for providing an interface for
+read-only streams and the readable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### data event
+
+The `data` event will be emitted whenever some data was read/received
+from this source stream.
+The event receives a single mixed argument for incoming data.
+
+```php
+$stream->on('data', function ($data) {
+ echo $data;
+});
+```
+
+This event MAY be emitted any number of times, which may be zero times if
+this stream does not send any data at all.
+It SHOULD not be emitted after an `end` or `close` event.
+
+The given `$data` argument may be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit the raw (binary) payload data that is received over the wire as
+chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end event
+
+The `end` event will be emitted once the source stream has successfully
+reached the end of the stream (EOF).
+
+```php
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+a successful end was detected.
+It SHOULD NOT be emitted after a previous `end` or `close` event.
+It MUST NOT be emitted if the stream closes due to a non-successful
+end, such as after a previous `error` event.
+
+After the stream is ended, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+This event will only be emitted if the *end* was reached successfully,
+not if the stream was interrupted by an unrecoverable error or explicitly
+closed. Not all streams know this concept of a "successful end".
+Many use-cases involve detecting when the stream closes (terminates)
+instead, in this case you should use the `close` event.
+After the stream emits an `end` event, it SHOULD usually be followed by a
+`close` event.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will emit this event if either the remote side closes the connection or
+a file handle was successfully read until reaching its end (EOF).
+
+Note that this event should not be confused with the `end()` method.
+This event defines a successful end *reading* from a source stream, while
+the `end()` method defines *writing* a successful end to a destination
+stream.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to read from this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$server->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error or after an unexpected `data` or premature
+`end` event.
+It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-readable mode, see
+also `close()` and `isReadable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and do not make assumption about data
+boundaries (such as unexpected `data` or premature `end` events).
+In other words, many lower-level protocols (such as TCP/IP) may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-readable mode,
+see also `isReadable()`.
+
+Unlike the `end` event, this event SHOULD be emitted whenever the stream
+closes, irrespective of whether this happens implicitly due to an
+unrecoverable error or explicitly when either side closes the stream.
+If you only want to detect a *successful* end, you should use the `end`
+event instead.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after reading a *successful* `end`
+event or after a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isReadable()
+
+The `isReadable(): bool` method can be used to
+check whether this stream is in a readable state (not closed already).
+
+This method can be used to check if the stream still accepts incoming
+data events or if it is ended or closed already.
+Once the stream is non-readable, no further `data` or `end` events SHOULD
+be emitted.
+
+```php
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+A successfully opened stream always MUST start in readable mode.
+
+Once the stream ends or closes, it MUST switch to non-readable mode.
+This can happen any time, explicitly through `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-readable mode, it MUST NOT transition
+back to readable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements an `isWritable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### pause()
+
+The `pause(): void` method can be used to
+pause reading incoming data events.
+
+Removes the data source file descriptor from the event loop. This
+allows you to throttle incoming data.
+
+Unless otherwise noted, a successfully opened stream SHOULD NOT start
+in paused state.
+
+Once the stream is paused, no futher `data` or `end` events SHOULD
+be emitted.
+
+```php
+$stream->pause();
+
+$stream->on('data', assertShouldNeverCalled());
+$stream->on('end', assertShouldNeverCalled());
+```
+
+This method is advisory-only, though generally not recommended, the
+stream MAY continue emitting `data` events.
+
+You can continue processing events by calling `resume()` again.
+
+Note that both methods can be called any number of times, in particular
+calling `pause()` more than once SHOULD NOT have any effect.
+
+See also `resume()`.
+
+#### resume()
+
+The `resume(): void` method can be used to
+resume reading incoming data events.
+
+Re-attach the data source after a previous `pause()`.
+
+```php
+$stream->pause();
+
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->resume();
+});
+```
+
+Note that both methods can be called any number of times, in particular
+calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+
+See also `pause()`.
+
+#### pipe()
+
+The `pipe(WritableStreamInterface $dest, array $options = [])` method can be used to
+pipe all the data from this readable source into the given writable destination.
+
+Automatically sends all incoming data to the destination.
+Automatically throttles the source based on what the destination can handle.
+
+```php
+$source->pipe($dest);
+```
+
+Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+into itself in order to write back all the data that is received.
+This may be a useful feature for a TCP/IP echo service:
+
+```php
+$connection->pipe($connection);
+```
+
+This method returns the destination stream as-is, which can be used to
+set up chains of piped streams:
+
+```php
+$source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+```
+
+By default, this will call `end()` on the destination stream once the
+source stream emits an `end` event. This can be disabled like this:
+
+```php
+$source->pipe($dest, array('end' => false));
+```
+
+Note that this only applies to the `end` event.
+If an `error` or explicit `close` event happens on the source stream,
+you'll have to manually close the destination stream:
+
+```php
+$source->pipe($dest);
+$source->on('close', function () use ($dest) {
+ $dest->end('BYE!');
+});
+```
+
+If the source stream is not readable (closed state), then this is a NO-OP.
+
+```php
+$source->close();
+$source->pipe($dest); // NO-OP
+```
+
+If the destinantion stream is not writable (closed state), then this will simply
+throttle (pause) the source stream:
+
+```php
+$dest->close();
+$source->pipe($dest); // calls $source->pause()
+```
+
+Similarly, if the destination stream is closed while the pipe is still
+active, it will also throttle (pause) the source stream:
+
+```php
+$source->pipe($dest);
+$dest->close(); // calls $source->pause()
+```
+
+Once the pipe is set up successfully, the destination stream MUST emit
+a `pipe` event with this source stream an event argument.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to (forcefully) close the stream.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-readable
+mode, see also `isReadable()`.
+This means that no further `data` or `end` events SHOULD be emitted.
+
+```php
+$stream->close();
+assert($stream->isReadable() === false);
+
+$stream->on('data', assertNeverCalled());
+$stream->on('end', assertNeverCalled());
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the writable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isWritable()`.
+Note that this method should not be confused with the `end()` method.
+
+### WritableStreamInterface
+
+The `WritableStreamInterface` is responsible for providing an interface for
+write-only streams and the writable side of duplex streams.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to certain events.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+#### drain event
+
+The `drain` event will be emitted whenever the write buffer became full
+previously and is now ready to accept more data.
+
+```php
+$stream->on('drain', function () use ($stream) {
+ echo 'Stream is now ready to accept more data';
+});
+```
+
+This event SHOULD be emitted once every time the buffer became full
+previously and is now ready to accept more data.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if the buffer never became full in the first place.
+This event SHOULD NOT be emitted if the buffer has not become full
+previously.
+
+This event is mostly used internally, see also `write()` for more details.
+
+#### pipe event
+
+The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+into this stream.
+The event receives a single `ReadableStreamInterface` argument for the
+source stream.
+
+```php
+$stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ echo 'Now receiving piped data';
+
+ // explicitly close target if source emits an error
+ $source->on('error', function () use ($stream) {
+ $stream->close();
+ });
+});
+
+$source->pipe($stream);
+```
+
+This event MUST be emitted once for each readable stream that is
+successfully piped into this destination stream.
+In other words, this event MAY be emitted any number of times, which may
+be zero times if no stream is ever piped into this stream.
+This event MUST NOT be emitted if either the source is not readable
+(closed already) or this destination is not writable (closed already).
+
+This event is mostly used internally, see also `pipe()` for more details.
+
+#### error event
+
+The `error` event will be emitted once a fatal error occurs, usually while
+trying to write to this stream.
+The event receives a single `Exception` argument for the error instance.
+
+```php
+$stream->on('error', function (Exception $e) {
+ echo 'Error: ' . $e->getMessage() . PHP_EOL;
+});
+```
+
+This event SHOULD be emitted once the stream detects a fatal error, such
+as a fatal transmission error.
+It SHOULD NOT be emitted after a previous `error` or `close` event.
+It MUST NOT be emitted if this is not a fatal error condition, such as
+a temporary network issue that did not cause any data to be lost.
+
+After the stream errors, it MUST close the stream and SHOULD thus be
+followed by a `close` event and then switch to non-writable mode, see
+also `close()` and `isWritable()`.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+only deal with data transmission and may choose
+to only emit this for a fatal transmission error once and will then
+close (terminate) the stream in response.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `error` event.
+In other words, an error may occur while either reading or writing the
+stream which should result in the same error processing.
+
+#### close event
+
+The `close` event will be emitted once the stream closes (terminates).
+
+```php
+$stream->on('close', function () {
+ echo 'CLOSED';
+});
+```
+
+This event SHOULD be emitted once or never at all, depending on whether
+the stream ever terminates.
+It SHOULD NOT be emitted after a previous `close` event.
+
+After the stream is closed, it MUST switch to non-writable mode,
+see also `isWritable()`.
+
+This event SHOULD be emitted whenever the stream closes, irrespective of
+whether this happens implicitly due to an unrecoverable error or
+explicitly when either side closes the stream.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will likely choose to emit this event after flushing the buffer from
+the `end()` method, after receiving a *successful* `end` event or after
+a fatal transmission `error` event.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close` event.
+In other words, after receiving this event, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+Note that this event should not be confused with the `end` event.
+
+#### isWritable()
+
+The `isWritable(): bool` method can be used to
+check whether this stream is in a writable state (not closed already).
+
+This method can be used to check if the stream still accepts writing
+any data or if it is ended or closed already.
+Writing any data to a non-writable stream is a NO-OP:
+
+```php
+assert($stream->isWritable() === false);
+
+$stream->write('end'); // NO-OP
+$stream->end('end'); // NO-OP
+```
+
+A successfully opened stream always MUST start in writable mode.
+
+Once the stream ends or closes, it MUST switch to non-writable mode.
+This can happen any time, explicitly through `end()` or `close()` or
+implicitly due to a remote close or an unrecoverable transmission error.
+Once a stream has switched to non-writable mode, it MUST NOT transition
+back to writable mode.
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements an `isReadable()`
+method. Unless this is a half-open duplex stream, they SHOULD usually
+have the same return value.
+
+#### write()
+
+The `write(mixed $data): bool` method can be used to
+write some data into the stream.
+
+A successful write MUST be confirmed with a boolean `true`, which means
+that either the data was written (flushed) immediately or is buffered and
+scheduled for a future write. Note that this interface gives you no
+control over explicitly flushing the buffered data, as finding the
+appropriate time for this is beyond the scope of this interface and left
+up to the implementation of this interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+an `error` event and MAY `close()` the stream if it can not recover from
+this error.
+
+If the internal buffer is full after adding `$data`, then `write()`
+SHOULD return `false`, indicating that the caller should stop sending
+data until the buffer drains.
+The stream SHOULD send a `drain` event once the buffer is ready to accept
+more data.
+
+Similarly, if the the stream is not writable (already in a closed state)
+it MUST NOT process the given `$data` and SHOULD return `false`,
+indicating that the caller should stop sending data.
+
+The given `$data` argument MAY be of mixed type, but it's usually
+recommended it SHOULD be a `string` value or MAY use a type that allows
+representation as a `string` for maximum compatibility.
+
+Many common streams (such as a TCP/IP connection or a file-based stream)
+will only accept the raw (binary) payload data that is transferred over
+the wire as chunks of `string` values.
+
+Due to the stream-based nature of this, the sender may send any number
+of chunks with varying sizes. There are no guarantees that these chunks
+will be received with the exact same framing the sender intended to send.
+In other words, many lower-level protocols (such as TCP/IP) transfer the
+data in chunks that may be anywhere between single-byte values to several
+dozens of kilobytes. You may want to apply a higher-level protocol to
+these low-level data chunks in order to achieve proper message framing.
+
+#### end()
+
+The `end(mixed $data = null): void` method can be used to
+successfully end the stream (after optionally sending some final data).
+
+This method can be used to successfully end the stream, i.e. close
+the stream after sending out all data that is currently buffered.
+
+```php
+$stream->write('hello');
+$stream->write('world');
+$stream->end();
+```
+
+If there's no data currently buffered and nothing to be flushed, then
+this method MAY `close()` the stream immediately.
+
+If there's still data in the buffer that needs to be flushed first, then
+this method SHOULD try to write out this data and only then `close()`
+the stream.
+Once the stream is closed, it SHOULD emit a `close` event.
+
+Note that this interface gives you no control over explicitly flushing
+the buffered data, as finding the appropriate time for this is beyond the
+scope of this interface and left up to the implementation of this
+interface.
+
+Many common streams (such as a TCP/IP connection or file-based stream)
+may choose to buffer all given data and schedule a future flush by using
+an underlying EventLoop to check when the resource is actually writable.
+
+You can optionally pass some final data that is written to the stream
+before ending the stream. If a non-`null` value is given as `$data`, then
+this method will behave just like calling `write($data)` before ending
+with no data.
+
+```php
+// shorter version
+$stream->end('bye');
+
+// same as longer version
+$stream->write('bye');
+$stream->end();
+```
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->end();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+also end its readable side, unless the stream supports half-open mode.
+In other words, after calling this method, these streams SHOULD switch
+into non-writable AND non-readable mode, see also `isReadable()`.
+This implies that in this case, the stream SHOULD NOT emit any `data`
+or `end` events anymore.
+Streams MAY choose to use the `pause()` method logic for this, but
+special care may have to be taken to ensure a following call to the
+`resume()` method SHOULD NOT continue emitting readable events.
+
+Note that this method should not be confused with the `close()` method.
+
+#### close()
+
+The `close(): void` method can be used to
+close the stream (forcefully).
+
+This method can be used to forcefully close the stream, i.e. close
+the stream without waiting for any buffered data to be flushed.
+If there's still data in the buffer, this data SHOULD be discarded.
+
+```php
+$stream->close();
+```
+
+Once the stream is closed, it SHOULD emit a `close` event.
+Note that this event SHOULD NOT be emitted more than once, in particular
+if this method is called multiple times.
+
+After calling this method, the stream MUST switch into a non-writable
+mode, see also `isWritable()`.
+This means that no further writes are possible, so any additional
+`write()` or `end()` calls have no effect.
+
+```php
+$stream->close();
+assert($stream->isWritable() === false);
+
+$stream->write('nope'); // NO-OP
+$stream->end(); // NO-OP
+```
+
+Note that this method should not be confused with the `end()` method.
+Unlike the `end()` method, this method does not take care of any existing
+buffers and simply discards any buffer contents.
+Likewise, this method may also be called after calling `end()` on a
+stream in order to stop waiting for the stream to flush its final data.
+
+```php
+$stream->end();
+$loop->addTimer(1.0, function () use ($stream) {
+ $stream->close();
+});
+```
+
+If this stream is a `DuplexStreamInterface`, you should also notice
+how the readable side of the stream also implements a `close()` method.
+In other words, after calling this method, the stream MUST switch into
+non-writable AND non-readable mode, see also `isReadable()`.
+
+### DuplexStreamInterface
+
+The `DuplexStreamInterface` is responsible for providing an interface for
+duplex streams (both readable and writable).
+
+It builds on top of the existing interfaces for readable and writable streams
+and follows the exact same method and event semantics.
+If you're new to this concept, you should look into the
+`ReadableStreamInterface` and `WritableStreamInterface` first.
+
+Besides defining a few methods, this interface also implements the
+`EventEmitterInterface` which allows you to react to the same events defined
+on the `ReadbleStreamInterface` and `WritableStreamInterface`.
+
+The event callback functions MUST be a valid `callable` that obeys strict
+parameter definitions and MUST accept event parameters exactly as documented.
+The event callback functions MUST NOT throw an `Exception`.
+The return value of the event callback functions will be ignored and has no
+effect, so for performance reasons you're recommended to not return any
+excessive data structures.
+
+Every implementation of this interface MUST follow these event semantics in
+order to be considered a well-behaving stream.
+
+> Note that higher-level implementations of this interface may choose to
+ define additional events with dedicated semantics not defined as part of
+ this low-level stream specification. Conformance with these event semantics
+ is out of scope for this interface, so you may also have to refer to the
+ documentation of such a higher-level implementation.
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+## Creating streams
+
+ReactPHP uses the concept of "streams" throughout its ecosystem, so that
+many higher-level consumers of this package only deal with
+[stream usage](#stream-usage).
+This implies that stream instances are most often created within some
+higher-level components and many consumers never actually have to deal with
+creating a stream instance.
+
+* Use [react/socket](https://github.com/reactphp/socket)
+ if you want to accept incoming or establish outgoing plaintext TCP/IP or
+ secure TLS socket connection streams.
+* Use [react/http](https://github.com/reactphp/http)
+ if you want to receive an incoming HTTP request body streams.
+* Use [react/child-process](https://github.com/reactphp/child-process)
+ if you want to communicate with child processes via process pipes such as
+ STDIN, STDOUT, STDERR etc.
+* Use experimental [react/filesystem](https://github.com/reactphp/filesystem)
+ if you want to read from / write to the filesystem.
+* See also the last chapter for [more real-world applications](#more).
+
+However, if you are writing a lower-level component or want to create a stream
+instance from a stream resource, then the following chapter is for you.
+
+> Note that the following examples use `fopen()` and `stream_socket_client()`
+ for illustration purposes only.
+ These functions SHOULD NOT be used in a truly async program because each call
+ may take several seconds to complete and would block the EventLoop otherwise.
+ Additionally, the `fopen()` call will return a file handle on some platforms
+ which may or may not be supported by all EventLoop implementations.
+ As an alternative, you may want to use higher-level libraries listed above.
+
+### ReadableResourceStream
+
+The `ReadableResourceStream` is a concrete implementation of the
+[`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-only resource like a file stream opened in
+readable mode or a stream such as `STDIN`:
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop);
+$stream->on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('end', function () {
+ echo 'END';
+});
+```
+
+See also [`ReadableStreamInterface`](#readablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened in reading mode (e.g. `fopen()` mode `r`).
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new ReadableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDIN etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new ReadableResourceStream(STDIN, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$stream = new ReadableResourceStream(STDIN, $loop, 8192);
+```
+
+> PHP bug warning: If the PHP process has explicitly been started without a
+ `STDIN` stream, then trying to read from `STDIN` may return data from
+ another stream resource. This does not happen if you start this with an empty
+ stream like `php test.php < /dev/null` instead of `php test.php <&-`.
+ See [#81](https://github.com/reactphp/stream/issues/81) for more details.
+
+### WritableResourceStream
+
+The `WritableResourceStream` is a concrete implementation of the
+[`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources.
+
+This can be used to represent a write-only resource like a file stream opened in
+writable mode or a stream such as `STDOUT` or `STDERR`:
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`WritableStreamInterface`](#writablestreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new WritableResourceStream(false, $loop);
+```
+
+See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new WritableResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls
+this maximum buffer size in bytes.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, 8192);
+```
+
+This class takes an optional `int|null $writeChunkSize` parameter that controls
+this maximum buffer size in bytes to write at once to the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be written
+at once to the underlying stream resource. Note that the actual number
+of bytes written may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "write everything available" to the
+underlying stream resource.
+
+```php
+$stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
+```
+
+See also [`write()`](#write) for more details.
+
+### DuplexResourceStream
+
+The `DuplexResourceStream` is a concrete implementation of the
+[`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources.
+
+This can be used to represent a read-and-write resource like a file stream opened
+in read and write mode mode or a stream such as a TCP/IP connection:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop);
+$stream->write('hello!');
+$stream->end();
+```
+
+See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details.
+
+The first parameter given to the constructor MUST be a valid stream resource
+that is opened for reading *and* writing.
+Otherwise, it will throw an `InvalidArgumentException`:
+
+```php
+// throws InvalidArgumentException
+$stream = new DuplexResourceStream(false, $loop);
+```
+
+See also the [`ReadableResourceStream`](#readableresourcestream) for read-only
+and the [`WritableResourceStream`](#writableresourcestream) for write-only
+stream resources otherwise.
+
+Internally, this class tries to enable non-blocking mode on the stream resource
+which may not be supported for all stream resources.
+Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.).
+If this fails, it will throw a `RuntimeException`:
+
+```php
+// throws RuntimeException on Windows
+$stream = new DuplexResourceStream(STDOUT, $loop);
+```
+
+Once the constructor is called with a valid stream resource, this class will
+take care of the underlying stream resource.
+You SHOULD only use its public API and SHOULD NOT interfere with the underlying
+stream resource manually.
+
+This class takes an optional `int|null $readChunkSize` parameter that controls
+the maximum buffer size in bytes to read at once from the stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+This can be a positive number which means that up to X bytes will be read
+at once from the underlying stream resource. Note that the actual number
+of bytes read may be lower if the stream resource has less than X bytes
+currently available.
+This can be `-1` which means "read everything available" from the
+underlying stream resource.
+This should read until the stream resource is not readable anymore
+(i.e. underlying buffer drained), note that this does not neccessarily
+mean it reached EOF.
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$stream = new DuplexResourceStream($conn, $loop, 8192);
+```
+
+Any `write()` calls to this class will not be performed instantly, but will
+be performed asynchronously, once the EventLoop reports the stream resource is
+ready to accept data.
+For this, it uses an in-memory buffer string to collect all outstanding writes.
+This buffer has a soft-limit applied which defines how much data it is willing
+to accept before the caller SHOULD stop sending further data.
+
+This class takes another optional `WritableStreamInterface|null $buffer` parameter
+that controls this write behavior of this stream.
+You can use a `null` value here in order to apply its default value.
+This value SHOULD NOT be changed unless you know what you're doing.
+
+If you want to change the write buffer soft limit, you can pass an instance of
+[`WritableResourceStream`](#writableresourcestream) like this:
+
+```php
+$conn = stream_socket_client('tcp://google.com:80');
+$buffer = new WritableResourceStream($conn, $loop, 8192);
+$stream = new DuplexResourceStream($conn, $loop, null, $buffer);
+```
+
+See also [`WritableResourceStream`](#writableresourcestream) for more details.
+
+### ThroughStream
+
+The `ThroughStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
+you write to it through to its readable end.
+
+```php
+$through = new ThroughStream();
+$through->on('data', $this->expectCallableOnceWith('hello'));
+
+$through->write('hello');
+```
+
+Similarly, the [`end()` method](#end) will end the stream and emit an
+[`end` event](#end-event) and then [`close()`](#close-1) the stream.
+The [`close()` method](#close-1) will close the stream and emit a
+[`close` event](#close-event).
+Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+
+```php
+$through = new ThroughStream();
+$source->pipe($through)->pipe($dest);
+```
+
+Optionally, its constructor accepts any callable function which will then be
+used to *filter* any data written to it. This function receives a single data
+argument as passed to the writable side and must return the data as it will be
+passed to its readable end:
+
+```php
+$through = new ThroughStream('strtoupper');
+$source->pipe($through)->pipe($dest);
+```
+
+Note that this class makes no assumptions about any data types. This can be
+used to convert data, for example for transforming any structured data into
+a newline-delimited JSON (NDJSON) stream like this:
+
+```php
+$through = new ThroughStream(function ($data) {
+ return json_encode($data) . PHP_EOL;
+});
+$through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+
+$through->write(array(2, true));
+```
+
+The callback function is allowed to throw an `Exception`. In this case,
+the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+
+```php
+$through = new ThroughStream(function ($data) {
+ if (!is_string($data)) {
+ throw new \UnexpectedValueException('Only strings allowed');
+ }
+ return $data;
+});
+$through->on('error', $this->expectCallableOnce()));
+$through->on('close', $this->expectCallableOnce()));
+$through->on('data', $this->expectCallableNever()));
+
+$through->write(2);
+```
+
+### CompositeStream
+
+The `CompositeStream` implements the
+[`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a
+single duplex stream from two individual streams implementing
+[`ReadableStreamInterface`](#readablestreaminterface) and
+[`WritableStreamInterface`](#writablestreaminterface) respectively.
+
+This is useful for some APIs which may require a single
+[`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often
+more convenient to work with a single stream instance like this:
+
+```php
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdout = new WritableResourceStream(STDOUT, $loop);
+
+$stdio = new CompositeStream($stdin, $stdout);
+
+$stdio->on('data', function ($chunk) use ($stdio) {
+ $stdio->write('You said: ' . $chunk);
+});
+```
+
+This is a well-behaving stream which forwards all stream events from the
+underlying streams and forwards all streams calls to the underlying streams.
+
+If you `write()` to the duplex stream, it will simply `write()` to the
+writable side and return its status.
+
+If you `end()` the duplex stream, it will `end()` the writable side and will
+`pause()` the readable side.
+
+If you `close()` the duplex stream, both input streams will be closed.
+If either of the two input streams emits a `close` event, the duplex stream
+will also close.
+If either of the two input streams is already closed while constructing the
+duplex stream, it will `close()` the other side and return a closed stream.
+
+## Usage
+
+The following example can be used to pipe the contents of a source file into
+a destination file without having to ever read the whole file into memory:
+
+```php
+$loop = new React\EventLoop\StreamSelectLoop;
+
+$source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r'), $loop);
+$dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w'), $loop);
+
+$source->pipe($dest);
+
+$loop->run();
+```
+
+> Note that this example uses `fopen()` for illustration purposes only.
+ This should not be used in a truly async program because the filesystem is
+ inherently blocking and each call could potentially take several seconds.
+ See also [creating streams](#creating-streams) for more sophisticated
+ examples.
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This will install the latest supported version:
+
+```bash
+$ composer require react/stream:^0.7.7
+```
+
+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.3 through current PHP 7+ and HHVM.
+It's *highly recommended to use PHP 7+* for this project due to its vast
+performance improvements.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ php vendor/bin/phpunit
+```
+
+The test suite also contains a number of functional integration tests that rely
+on a stable internet connection.
+If you do not want to run these, they can simply be skipped like this:
+
+```bash
+$ php vendor/bin/phpunit --exclude-group internet
+```
+
+## License
+
+MIT, see [LICENSE file](LICENSE).
+
+## More
+
+* See [creating streams](#creating-streams) for more information on how streams
+ are created in real-world applications.
+* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
+ [dependents on Packagist](https://packagist.org/packages/react/stream/dependents)
+ for a list of packages that use streams in real-world applications.
diff --git a/assets/php/vendor/react/stream/composer.json b/assets/php/vendor/react/stream/composer.json
new file mode 100644
index 0000000..f6faa66
--- /dev/null
+++ b/assets/php/vendor/react/stream/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "react/stream",
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35",
+ "clue/stream-filter": "~1.2"
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "React\\Tests\\Stream\\": "tests"
+ }
+ }
+}
diff --git a/assets/php/vendor/react/stream/examples/01-http.php b/assets/php/vendor/react/stream/examples/01-http.php
new file mode 100644
index 0000000..3687f7c
--- /dev/null
+++ b/assets/php/vendor/react/stream/examples/01-http.php
@@ -0,0 +1,40 @@
+<?php
+
+// Simple plaintext HTTP client example (for illustration purposes only).
+// This shows how a plaintext TCP/IP connection is established to then send an
+// application level protocol message (HTTP).
+// Real applications should use react/http-client instead!
+//
+// This simple example only accepts an optional host parameter to send the
+// request to.
+//
+// $ php examples/01-http.php
+// $ php examples/01-http.php reactphp.org
+
+use React\EventLoop\Factory;
+use React\Stream\DuplexResourceStream;
+
+require __DIR__ . '/../vendor/autoload.php';
+
+$host = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+// connect to tcp://www.google.com:80 (blocking call!)
+// for illustration purposes only, should use react/http-client or react/socket instead!
+$resource = stream_socket_client('tcp://' . $host . ':80');
+if (!$resource) {
+ exit(1);
+}
+
+$loop = Factory::create();
+$stream = new DuplexResourceStream($resource, $loop);
+
+$stream->on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/assets/php/vendor/react/stream/examples/02-https.php b/assets/php/vendor/react/stream/examples/02-https.php
new file mode 100644
index 0000000..163f7c8
--- /dev/null
+++ b/assets/php/vendor/react/stream/examples/02-https.php
@@ -0,0 +1,40 @@
+<?php
+
+// Simple secure HTTPS client example (for illustration purposes only).
+// This shows how a secure TLS connection is established to then send an
+// application level protocol message (HTTP).
+// Real applications should use react/http-client instead!
+//
+// This simple example only accepts an optional host parameter to send the
+// request to.
+//
+// $ php examples/02-https.php
+// $ php examples/02-https.php reactphp.org
+
+use React\EventLoop\Factory;
+use React\Stream\DuplexResourceStream;
+
+require __DIR__ . '/../vendor/autoload.php';
+
+$host = isset($argv[1]) ? $argv[1] : 'www.google.com';
+
+// connect to tls://www.google.com:443 (blocking call!)
+// for illustration purposes only, should use react/http-client or react/socket instead!
+$resource = stream_socket_client('tls://' . $host . ':443');
+if (!$resource) {
+ exit(1);
+}
+
+$loop = Factory::create();
+$stream = new DuplexResourceStream($resource, $loop);
+
+$stream->on('data', function ($chunk) {
+ echo $chunk;
+});
+$stream->on('close', function () {
+ echo '[CLOSED]' . PHP_EOL;
+});
+
+$stream->write("GET / HTTP/1.0\r\nHost: $host\r\n\r\n");
+
+$loop->run();
diff --git a/assets/php/vendor/react/stream/examples/11-cat.php b/assets/php/vendor/react/stream/examples/11-cat.php
new file mode 100644
index 0000000..90fadc0
--- /dev/null
+++ b/assets/php/vendor/react/stream/examples/11-cat.php
@@ -0,0 +1,28 @@
+<?php
+
+// Simple example piping everything from STDIN to STDOUT.
+// This allows you to output everything you type on your keyboard or to redirect
+// the pipes to show contents of files and other streams.
+//
+// $ php examples/11-cat.php
+// $ php examples/11-cat.php < README.md
+// $ echo hello | php examples/11-cat.php
+
+use React\EventLoop\Factory;
+use React\Stream\ReadableResourceStream;
+use React\Stream\WritableResourceStream;
+
+require __DIR__ . '/../vendor/autoload.php';
+
+if (DIRECTORY_SEPARATOR === '\\') {
+ fwrite(STDERR, 'Non-blocking console I/O not supported on Microsoft Windows' . PHP_EOL);
+ exit(1);
+}
+
+$loop = Factory::create();
+
+$stdout = new WritableResourceStream(STDOUT, $loop);
+$stdin = new ReadableResourceStream(STDIN, $loop);
+$stdin->pipe($stdout);
+
+$loop->run();
diff --git a/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php b/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php
new file mode 100644
index 0000000..ecf695c
--- /dev/null
+++ b/assets/php/vendor/react/stream/examples/91-benchmark-throughput.php
@@ -0,0 +1,62 @@
+<?php
+
+// Benchmark to measure throughput performance piping an input stream to an output stream.
+// This allows you to get an idea of how fast stream processing with PHP can be
+// and also to play around with differnt types of input and output streams.
+//
+// This example accepts a number of parameters to control the timeout (-t 1),
+// the input file (-i /dev/zero) and the output file (-o /dev/null).
+//
+// $ php examples/91-benchmark-throughput.php
+// $ php examples/91-benchmark-throughput.php -t 10 -o zero.bin
+// $ php examples/91-benchmark-throughput.php -t 60 -i zero.bin
+
+require __DIR__ . '/../vendor/autoload.php';
+
+if (DIRECTORY_SEPARATOR === '\\') {
+ fwrite(STDERR, 'Non-blocking console I/O not supported on Microsoft Windows' . PHP_EOL);
+ exit(1);
+}
+
+$args = getopt('i:o:t:');
+$if = isset($args['i']) ? $args['i'] : '/dev/zero';
+$of = isset($args['o']) ? $args['o'] : '/dev/null';
+$t = isset($args['t']) ? $args['t'] : 1;
+
+// passing file descriptors requires mapping paths (https://bugs.php.net/bug.php?id=53465)
+$if = str_replace('/dev/fd/', 'php://fd/', $if);
+$of = str_replace('/dev/fd/', 'php://fd/', $of);
+
+$loop = new React\EventLoop\StreamSelectLoop();
+
+// setup information stream
+$info = new React\Stream\WritableResourceStream(STDERR, $loop);
+if (extension_loaded('xdebug')) {
+ $info->write('NOTICE: The "xdebug" extension is loaded, this has a major impact on performance.' . PHP_EOL);
+}
+$info->write('piping from ' . $if . ' to ' . $of . ' (for max ' . $t . ' second(s)) ...'. PHP_EOL);
+
+// setup input and output streams and pipe inbetween
+$fh = fopen($if, 'r');
+$in = new React\Stream\ReadableResourceStream($fh, $loop);
+$out = new React\Stream\WritableResourceStream(fopen($of, 'w'), $loop);
+$in->pipe($out);
+
+// stop input stream in $t seconds
+$start = microtime(true);
+$timeout = $loop->addTimer($t, function () use ($in, &$bytes) {
+ $in->close();
+});
+
+// print stream position once stream closes
+$in->on('close', function () use ($fh, $start, $loop, $timeout, $info) {
+ $t = microtime(true) - $start;
+ $loop->cancelTimer($timeout);
+
+ $bytes = ftell($fh);
+
+ $info->write('read ' . $bytes . ' byte(s) in ' . round($t, 3) . ' second(s) => ' . round($bytes / 1024 / 1024 / $t, 1) . ' MiB/s' . PHP_EOL);
+ $info->write('peak memory usage of ' . round(memory_get_peak_usage(true) / 1024 / 1024, 1) . ' MiB' . PHP_EOL);
+});
+
+$loop->run();
diff --git a/assets/php/vendor/react/stream/phpunit.xml.dist b/assets/php/vendor/react/stream/phpunit.xml.dist
new file mode 100644
index 0000000..13d3fab
--- /dev/null
+++ b/assets/php/vendor/react/stream/phpunit.xml.dist
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="vendor/autoload.php"
+>
+ <testsuites>
+ <testsuite name="React Test Suite">
+ <directory>./tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./src/</directory>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/assets/php/vendor/react/stream/src/CompositeStream.php b/assets/php/vendor/react/stream/src/CompositeStream.php
new file mode 100644
index 0000000..153f2a3
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/CompositeStream.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+
+final class CompositeStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable;
+ private $writable;
+ private $closed = false;
+
+ public function __construct(ReadableStreamInterface $readable, WritableStreamInterface $writable)
+ {
+ $this->readable = $readable;
+ $this->writable = $writable;
+
+ if (!$readable->isReadable() || !$writable->isWritable()) {
+ return $this->close();
+ }
+
+ Util::forwardEvents($this->readable, $this, array('data', 'end', 'error'));
+ Util::forwardEvents($this->writable, $this, array('drain', 'error', 'pipe'));
+
+ $this->readable->on('close', array($this, 'close'));
+ $this->writable->on('close', array($this, 'close'));
+ }
+
+ public function isReadable()
+ {
+ return $this->readable->isReadable();
+ }
+
+ public function pause()
+ {
+ $this->readable->pause();
+ }
+
+ public function resume()
+ {
+ if (!$this->writable->isWritable()) {
+ return;
+ }
+
+ $this->readable->resume();
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isWritable()
+ {
+ return $this->writable->isWritable();
+ }
+
+ public function write($data)
+ {
+ return $this->writable->write($data);
+ }
+
+ public function end($data = null)
+ {
+ $this->readable->pause();
+ $this->writable->end($data);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->readable->close();
+ $this->writable->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/DuplexResourceStream.php b/assets/php/vendor/react/stream/src/DuplexResourceStream.php
new file mode 100644
index 0000000..982ebb0
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/DuplexResourceStream.php
@@ -0,0 +1,224 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+use React\EventLoop\LoopInterface;
+use InvalidArgumentException;
+
+final class DuplexResourceStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $stream;
+ private $loop;
+
+ /**
+ * Controls the maximum buffer size in bytes to read at once from the stream.
+ *
+ * This can be a positive number which means that up to X bytes will be read
+ * at once from the underlying stream resource. Note that the actual number
+ * of bytes read may be lower if the stream resource has less than X bytes
+ * currently available.
+ *
+ * This can be `-1` which means read everything available from the
+ * underlying stream resource.
+ * This should read until the stream resource is not readable anymore
+ * (i.e. underlying buffer drained), note that this does not neccessarily
+ * mean it reached EOF.
+ *
+ * @var int
+ */
+ private $bufferSize;
+ private $buffer;
+
+ private $readable = true;
+ private $writable = true;
+ private $closing = false;
+ private $listening = false;
+
+ public function __construct($stream, LoopInterface $loop, $readChunkSize = null, WritableStreamInterface $buffer = null)
+ {
+ if (!is_resource($stream) || get_resource_type($stream) !== "stream") {
+ throw new InvalidArgumentException('First parameter must be a valid stream resource');
+ }
+
+ // ensure resource is opened for reading and wrting (fopen mode must contain "+")
+ $meta = stream_get_meta_data($stream);
+ if (isset($meta['mode']) && $meta['mode'] !== '' && strpos($meta['mode'], '+') === false) {
+ throw new InvalidArgumentException('Given stream resource is not opened in read and write mode');
+ }
+
+ // this class relies on non-blocking I/O in order to not interrupt the event loop
+ // e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
+ if (stream_set_blocking($stream, 0) !== true) {
+ throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
+ }
+
+ // Use unbuffered read operations on the underlying stream resource.
+ // Reading chunks from the stream may otherwise leave unread bytes in
+ // PHP's stream buffers which some event loop implementations do not
+ // trigger events on (edge triggered).
+ // This does not affect the default event loop implementation (level
+ // triggered), so we can ignore platforms not supporting this (HHVM).
+ // Pipe streams (such as STDIN) do not seem to require this and legacy
+ // PHP versions cause SEGFAULTs on unbuffered pipe streams, so skip this.
+ if (function_exists('stream_set_read_buffer') && !$this->isLegacyPipe($stream)) {
+ stream_set_read_buffer($stream, 0);
+ }
+
+ if ($buffer === null) {
+ $buffer = new WritableResourceStream($stream, $loop);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+ $this->buffer = $buffer;
+
+ $that = $this;
+
+ $this->buffer->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ });
+
+ $this->buffer->on('close', array($this, 'close'));
+
+ $this->buffer->on('drain', function () use ($that) {
+ $that->emit('drain');
+ });
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && $this->readable) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ return $this->buffer->write($data);
+ }
+
+ public function close()
+ {
+ if (!$this->writable && !$this->closing) {
+ return;
+ }
+
+ $this->closing = false;
+
+ $this->readable = false;
+ $this->writable = false;
+
+ $this->emit('close');
+ $this->pause();
+ $this->buffer->close();
+ $this->removeAllListeners();
+
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ $this->closing = true;
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->pause();
+
+ $this->buffer->end($data);
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ /** @internal */
+ public function handleData($stream)
+ {
+ $error = null;
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = stream_get_contents($stream, $this->bufferSize);
+
+ restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } else{
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512)) {
+ $meta = stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/DuplexStreamInterface.php b/assets/php/vendor/react/stream/src/DuplexStreamInterface.php
new file mode 100644
index 0000000..631ce31
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/DuplexStreamInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace React\Stream;
+
+/**
+ * The `DuplexStreamInterface` is responsible for providing an interface for
+ * duplex streams (both readable and writable).
+ *
+ * It builds on top of the existing interfaces for readable and writable streams
+ * and follows the exact same method and event semantics.
+ * If you're new to this concept, you should look into the
+ * `ReadableStreamInterface` and `WritableStreamInterface` first.
+ *
+ * Besides defining a few methods, this interface also implements the
+ * `EventEmitterInterface` which allows you to react to the same events defined
+ * on the `ReadbleStreamInterface` and `WritableStreamInterface`.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see ReadableStreamInterface
+ * @see WritableStreamInterface
+ */
+interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface
+{
+}
diff --git a/assets/php/vendor/react/stream/src/ReadableResourceStream.php b/assets/php/vendor/react/stream/src/ReadableResourceStream.php
new file mode 100644
index 0000000..015a96b
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/ReadableResourceStream.php
@@ -0,0 +1,177 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+use React\EventLoop\LoopInterface;
+use InvalidArgumentException;
+
+final class ReadableResourceStream extends EventEmitter implements ReadableStreamInterface
+{
+ /**
+ * @var resource
+ */
+ private $stream;
+
+ private $loop;
+
+ /**
+ * Controls the maximum buffer size in bytes to read at once from the stream.
+ *
+ * This value SHOULD NOT be changed unless you know what you're doing.
+ *
+ * This can be a positive number which means that up to X bytes will be read
+ * at once from the underlying stream resource. Note that the actual number
+ * of bytes read may be lower if the stream resource has less than X bytes
+ * currently available.
+ *
+ * This can be `-1` which means read everything available from the
+ * underlying stream resource.
+ * This should read until the stream resource is not readable anymore
+ * (i.e. underlying buffer drained), note that this does not neccessarily
+ * mean it reached EOF.
+ *
+ * @var int
+ */
+ private $bufferSize;
+
+ private $closed = false;
+ private $listening = false;
+
+ public function __construct($stream, LoopInterface $loop, $readChunkSize = null)
+ {
+ if (!is_resource($stream) || get_resource_type($stream) !== "stream") {
+ throw new InvalidArgumentException('First parameter must be a valid stream resource');
+ }
+
+ // ensure resource is opened for reading (fopen mode must contain "r" or "+")
+ $meta = stream_get_meta_data($stream);
+ if (isset($meta['mode']) && $meta['mode'] !== '' && strpos($meta['mode'], 'r') === strpos($meta['mode'], '+')) {
+ throw new InvalidArgumentException('Given stream resource is not opened in read mode');
+ }
+
+ // this class relies on non-blocking I/O in order to not interrupt the event loop
+ // e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
+ if (stream_set_blocking($stream, 0) !== true) {
+ throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
+ }
+
+ // Use unbuffered read operations on the underlying stream resource.
+ // Reading chunks from the stream may otherwise leave unread bytes in
+ // PHP's stream buffers which some event loop implementations do not
+ // trigger events on (edge triggered).
+ // This does not affect the default event loop implementation (level
+ // triggered), so we can ignore platforms not supporting this (HHVM).
+ // Pipe streams (such as STDIN) do not seem to require this and legacy
+ // PHP versions cause SEGFAULTs on unbuffered pipe streams, so skip this.
+ if (function_exists('stream_set_read_buffer') && !$this->isLegacyPipe($stream)) {
+ stream_set_read_buffer($stream, 0);
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->bufferSize = ($readChunkSize === null) ? 65536 : (int)$readChunkSize;
+
+ $this->resume();
+ }
+
+ public function isReadable()
+ {
+ return !$this->closed;
+ }
+
+ public function pause()
+ {
+ if ($this->listening) {
+ $this->loop->removeReadStream($this->stream);
+ $this->listening = false;
+ }
+ }
+
+ public function resume()
+ {
+ if (!$this->listening && !$this->closed) {
+ $this->loop->addReadStream($this->stream, array($this, 'handleData'));
+ $this->listening = true;
+ }
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+
+ $this->emit('close');
+ $this->pause();
+ $this->removeAllListeners();
+
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleData()
+ {
+ $error = null;
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = new \ErrorException(
+ $errstr,
+ 0,
+ $errno,
+ $errfile,
+ $errline
+ );
+ });
+
+ $data = stream_get_contents($this->stream, $this->bufferSize);
+
+ restore_error_handler();
+
+ if ($error !== null) {
+ $this->emit('error', array(new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)));
+ $this->close();
+ return;
+ }
+
+ if ($data !== '') {
+ $this->emit('data', array($data));
+ } else{
+ // no data read => we reached the end and close the stream
+ $this->emit('end');
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether this is a pipe resource in a legacy environment
+ *
+ * This works around a legacy PHP bug (#61019) that was fixed in PHP 5.4.28+
+ * and PHP 5.5.12+ and newer.
+ *
+ * @param resource $resource
+ * @return bool
+ * @link https://github.com/reactphp/child-process/issues/40
+ *
+ * @codeCoverageIgnore
+ */
+ private function isLegacyPipe($resource)
+ {
+ if (PHP_VERSION_ID < 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50512)) {
+ $meta = stream_get_meta_data($resource);
+
+ if (isset($meta['stream_type']) && $meta['stream_type'] === 'STDIO') {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/ReadableStreamInterface.php b/assets/php/vendor/react/stream/src/ReadableStreamInterface.php
new file mode 100644
index 0000000..2b4c3d0
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/ReadableStreamInterface.php
@@ -0,0 +1,362 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitterInterface;
+
+/**
+ * The `ReadableStreamInterface` is responsible for providing an interface for
+ * read-only streams and the readable side of duplex streams.
+ *
+ * Besides defining a few methods, this interface also implements the
+ * `EventEmitterInterface` which allows you to react to certain events:
+ *
+ * data event:
+ * The `data` event will be emitted whenever some data was read/received
+ * from this source stream.
+ * The event receives a single mixed argument for incoming data.
+ *
+ * ```php
+ * $stream->on('data', function ($data) {
+ * echo $data;
+ * });
+ * ```
+ *
+ * This event MAY be emitted any number of times, which may be zero times if
+ * this stream does not send any data at all.
+ * It SHOULD not be emitted after an `end` or `close` event.
+ *
+ * The given `$data` argument may be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit the raw (binary) payload data that is received over the wire as
+ * chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * end event:
+ * The `end` event will be emitted once the source stream has successfully
+ * reached the end of the stream (EOF).
+ *
+ * ```php
+ * $stream->on('end', function () {
+ * echo 'END';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * a successful end was detected.
+ * It SHOULD NOT be emitted after a previous `end` or `close` event.
+ * It MUST NOT be emitted if the stream closes due to a non-successful
+ * end, such as after a previous `error` event.
+ *
+ * After the stream is ended, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * This event will only be emitted if the *end* was reached successfully,
+ * not if the stream was interrupted by an unrecoverable error or explicitly
+ * closed. Not all streams know this concept of a "successful end".
+ * Many use-cases involve detecting when the stream closes (terminates)
+ * instead, in this case you should use the `close` event.
+ * After the stream emits an `end` event, it SHOULD usually be followed by a
+ * `close` event.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will emit this event if either the remote side closes the connection or
+ * a file handle was successfully read until reaching its end (EOF).
+ *
+ * Note that this event should not be confused with the `end()` method.
+ * This event defines a successful end *reading* from a source stream, while
+ * the `end()` method defines *writing* a successful end to a destination
+ * stream.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to read from this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error or after an unexpected `data` or premature
+ * `end` event.
+ * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-readable mode, see
+ * also `close()` and `isReadable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and do not make assumption about data
+ * boundaries (such as unexpected `data` or premature `end` events).
+ * In other words, many lower-level protocols (such as TCP/IP) may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-readable mode,
+ * see also `isReadable()`.
+ *
+ * Unlike the `end` event, this event SHOULD be emitted whenever the stream
+ * closes, irrespective of whether this happens implicitly due to an
+ * unrecoverable error or explicitly when either side closes the stream.
+ * If you only want to detect a *successful* end, you should use the `end`
+ * event instead.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after reading a *successful* `end`
+ * event or after a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ */
+interface ReadableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a readable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts incoming
+ * data events or if it is ended or closed already.
+ * Once the stream is non-readable, no further `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * A successfully opened stream always MUST start in readable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-readable mode.
+ * This can happen any time, explicitly through `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-readable mode, it MUST NOT transition
+ * back to readable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements an `isWritable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isReadable();
+
+ /**
+ * Pauses reading incoming data events.
+ *
+ * Removes the data source file descriptor from the event loop. This
+ * allows you to throttle incoming data.
+ *
+ * Unless otherwise noted, a successfully opened stream SHOULD NOT start
+ * in paused state.
+ *
+ * Once the stream is paused, no futher `data` or `end` events SHOULD
+ * be emitted.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $stream->on('data', assertShouldNeverCalled());
+ * $stream->on('end', assertShouldNeverCalled());
+ * ```
+ *
+ * This method is advisory-only, though generally not recommended, the
+ * stream MAY continue emitting `data` events.
+ *
+ * You can continue processing events by calling `resume()` again.
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `pause()` more than once SHOULD NOT have any effect.
+ *
+ * @see self::resume()
+ * @return void
+ */
+ public function pause();
+
+ /**
+ * Resumes reading incoming data events.
+ *
+ * Re-attach the data source after a previous `pause()`.
+ *
+ * ```php
+ * $stream->pause();
+ *
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->resume();
+ * });
+ * ```
+ *
+ * Note that both methods can be called any number of times, in particular
+ * calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
+ *
+ * @see self::pause()
+ * @return void
+ */
+ public function resume();
+
+ /**
+ * Pipes all the data from this readable source into the given writable destination.
+ *
+ * Automatically sends all incoming data to the destination.
+ * Automatically throttles the source based on what the destination can handle.
+ *
+ * ```php
+ * $source->pipe($dest);
+ * ```
+ *
+ * Similarly, you can also pipe an instance implementing `DuplexStreamInterface`
+ * into itself in order to write back all the data that is received.
+ * This may be a useful feature for a TCP/IP echo service:
+ *
+ * ```php
+ * $connection->pipe($connection);
+ * ```
+ *
+ * This method returns the destination stream as-is, which can be used to
+ * set up chains of piped streams:
+ *
+ * ```php
+ * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest);
+ * ```
+ *
+ * By default, this will call `end()` on the destination stream once the
+ * source stream emits an `end` event. This can be disabled like this:
+ *
+ * ```php
+ * $source->pipe($dest, array('end' => false));
+ * ```
+ *
+ * Note that this only applies to the `end` event.
+ * If an `error` or explicit `close` event happens on the source stream,
+ * you'll have to manually close the destination stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $source->on('close', function () use ($dest) {
+ * $dest->end('BYE!');
+ * });
+ * ```
+ *
+ * If the source stream is not readable (closed state), then this is a NO-OP.
+ *
+ * ```php
+ * $source->close();
+ * $source->pipe($dest); // NO-OP
+ * ```
+ *
+ * If the destinantion stream is not writable (closed state), then this will simply
+ * throttle (pause) the source stream:
+ *
+ * ```php
+ * $dest->close();
+ * $source->pipe($dest); // calls $source->pause()
+ * ```
+ *
+ * Similarly, if the destination stream is closed while the pipe is still
+ * active, it will also throttle (pause) the source stream:
+ *
+ * ```php
+ * $source->pipe($dest);
+ * $dest->close(); // calls $source->pause()
+ * ```
+ *
+ * Once the pipe is set up successfully, the destination stream MUST emit
+ * a `pipe` event with this source stream an event argument.
+ *
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ */
+ public function pipe(WritableStreamInterface $dest, array $options = array());
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to (forcefully) close the stream.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-readable
+ * mode, see also `isReadable()`.
+ * This means that no further `data` or `end` events SHOULD be emitted.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isReadable() === false);
+ *
+ * $stream->on('data', assertNeverCalled());
+ * $stream->on('end', assertNeverCalled());
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the writable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isWritable()`.
+ * Note that this method should not be confused with the `end()` method.
+ *
+ * @return void
+ * @see WritableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/assets/php/vendor/react/stream/src/ThroughStream.php b/assets/php/vendor/react/stream/src/ThroughStream.php
new file mode 100644
index 0000000..da2fbb0
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/ThroughStream.php
@@ -0,0 +1,190 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+use InvalidArgumentException;
+
+/**
+ * The `ThroughStream` implements the
+ * [`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data
+ * you write to it through to its readable end.
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $through->on('data', $this->expectCallableOnceWith('hello'));
+ *
+ * $through->write('hello');
+ * ```
+ *
+ * Similarly, the [`end()` method](#end) will end the stream and emit an
+ * [`end` event](#end-event) and then [`close()`](#close-1) the stream.
+ * The [`close()` method](#close-1) will close the stream and emit a
+ * [`close` event](#close-event).
+ * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this:
+ *
+ * ```php
+ * $through = new ThroughStream();
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Optionally, its constructor accepts any callable function which will then be
+ * used to *filter* any data written to it. This function receives a single data
+ * argument as passed to the writable side and must return the data as it will be
+ * passed to its readable end:
+ *
+ * ```php
+ * $through = new ThroughStream('strtoupper');
+ * $source->pipe($through)->pipe($dest);
+ * ```
+ *
+ * Note that this class makes no assumptions about any data types. This can be
+ * used to convert data, for example for transforming any structured data into
+ * a newline-delimited JSON (NDJSON) stream like this:
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * return json_encode($data) . PHP_EOL;
+ * });
+ * $through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
+ *
+ * $through->write(array(2, true));
+ * ```
+ *
+ * The callback function is allowed to throw an `Exception`. In this case,
+ * the stream will emit an `error` event and then [`close()`](#close-1) the stream.
+ *
+ * ```php
+ * $through = new ThroughStream(function ($data) {
+ * if (!is_string($data)) {
+ * throw new \UnexpectedValueException('Only strings allowed');
+ * }
+ * return $data;
+ * });
+ * $through->on('error', $this->expectCallableOnce()));
+ * $through->on('close', $this->expectCallableOnce()));
+ * $through->on('data', $this->expectCallableNever()));
+ *
+ * $through->write(2);
+ * ```
+ *
+ * @see WritableStreamInterface::write()
+ * @see WritableStreamInterface::end()
+ * @see DuplexStreamInterface::close()
+ * @see WritableStreamInterface::pipe()
+ */
+final class ThroughStream extends EventEmitter implements DuplexStreamInterface
+{
+ private $readable = true;
+ private $writable = true;
+ private $closed = false;
+ private $paused = false;
+ private $drain = false;
+ private $callback;
+
+ public function __construct($callback = null)
+ {
+ if ($callback !== null && !is_callable($callback)) {
+ throw new InvalidArgumentException('Invalid transformation callback given');
+ }
+
+ $this->callback = $callback;
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ if ($this->drain) {
+ $this->drain = false;
+ $this->emit('drain');
+ }
+ $this->paused = false;
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ if ($this->callback !== null) {
+ try {
+ $data = call_user_func($this->callback, $data);
+ } catch (\Exception $e) {
+ $this->emit('error', array($e));
+ $this->close();
+
+ return false;
+ }
+ }
+
+ $this->emit('data', array($data));
+
+ if ($this->paused) {
+ $this->drain = true;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function end($data = null)
+ {
+ if (!$this->writable) {
+ return;
+ }
+
+ if (null !== $data) {
+ $this->write($data);
+
+ // return if write() already caused the stream to close
+ if (!$this->writable) {
+ return;
+ }
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->paused = true;
+ $this->drain = false;
+
+ $this->emit('end');
+ $this->close();
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->readable = false;
+ $this->writable = false;
+ $this->closed = true;
+ $this->paused = true;
+ $this->drain = false;
+ $this->callback = null;
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/Util.php b/assets/php/vendor/react/stream/src/Util.php
new file mode 100644
index 0000000..14ddcfc
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/Util.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace React\Stream;
+
+final class Util
+{
+ /**
+ * Pipes all the data from the given $source into the $dest
+ *
+ * @param ReadableStreamInterface $source
+ * @param WritableStreamInterface $dest
+ * @param array $options
+ * @return WritableStreamInterface $dest stream as-is
+ * @see ReadableStreamInterface::pipe() for more details
+ */
+ public static function pipe(ReadableStreamInterface $source, WritableStreamInterface $dest, array $options = array())
+ {
+ // source not readable => NO-OP
+ if (!$source->isReadable()) {
+ return $dest;
+ }
+
+ // destination not writable => just pause() source
+ if (!$dest->isWritable()) {
+ $source->pause();
+
+ return $dest;
+ }
+
+ $dest->emit('pipe', array($source));
+
+ // forward all source data events as $dest->write()
+ $source->on('data', $dataer = function ($data) use ($source, $dest) {
+ $feedMore = $dest->write($data);
+
+ if (false === $feedMore) {
+ $source->pause();
+ }
+ });
+ $dest->on('close', function () use ($source, $dataer) {
+ $source->removeListener('data', $dataer);
+ $source->pause();
+ });
+
+ // forward destination drain as $source->resume()
+ $dest->on('drain', $drainer = function () use ($source) {
+ $source->resume();
+ });
+ $source->on('close', function () use ($dest, $drainer) {
+ $dest->removeListener('drain', $drainer);
+ });
+
+ // forward end event from source as $dest->end()
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end) {
+ $source->on('end', $ender = function () use ($dest) {
+ $dest->end();
+ });
+ $dest->on('close', function () use ($source, $ender) {
+ $source->removeListener('end', $ender);
+ });
+ }
+
+ return $dest;
+ }
+
+ public static function forwardEvents($source, $target, array $events)
+ {
+ foreach ($events as $event) {
+ $source->on($event, function () use ($event, $target) {
+ $target->emit($event, func_get_args());
+ });
+ }
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/WritableResourceStream.php b/assets/php/vendor/react/stream/src/WritableResourceStream.php
new file mode 100644
index 0000000..7e04205
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/WritableResourceStream.php
@@ -0,0 +1,171 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitter;
+use React\EventLoop\LoopInterface;
+
+final class WritableResourceStream extends EventEmitter implements WritableStreamInterface
+{
+ private $stream;
+ private $loop;
+ private $softLimit;
+ private $writeChunkSize;
+
+ private $listening = false;
+ private $writable = true;
+ private $closed = false;
+ private $data = '';
+
+ public function __construct($stream, LoopInterface $loop, $writeBufferSoftLimit = null, $writeChunkSize = null)
+ {
+ if (!is_resource($stream) || get_resource_type($stream) !== "stream") {
+ throw new \InvalidArgumentException('First parameter must be a valid stream resource');
+ }
+
+ // ensure resource is opened for writing (fopen mode must contain either of "waxc+")
+ $meta = stream_get_meta_data($stream);
+ if (isset($meta['mode']) && $meta['mode'] !== '' && strtr($meta['mode'], 'waxc+', '.....') === $meta['mode']) {
+ throw new \InvalidArgumentException('Given stream resource is not opened in write mode');
+ }
+
+ // this class relies on non-blocking I/O in order to not interrupt the event loop
+ // e.g. pipes on Windows do not support this: https://bugs.php.net/bug.php?id=47918
+ if (stream_set_blocking($stream, 0) !== true) {
+ throw new \RuntimeException('Unable to set stream resource to non-blocking mode');
+ }
+
+ $this->stream = $stream;
+ $this->loop = $loop;
+ $this->softLimit = ($writeBufferSoftLimit === null) ? 65536 : (int)$writeBufferSoftLimit;
+ $this->writeChunkSize = ($writeChunkSize === null) ? -1 : (int)$writeChunkSize;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function write($data)
+ {
+ if (!$this->writable) {
+ return false;
+ }
+
+ $this->data .= $data;
+
+ if (!$this->listening && $this->data !== '') {
+ $this->listening = true;
+
+ $this->loop->addWriteStream($this->stream, array($this, 'handleWrite'));
+ }
+
+ return !isset($this->data[$this->softLimit - 1]);
+ }
+
+ public function end($data = null)
+ {
+ if (null !== $data) {
+ $this->write($data);
+ }
+
+ $this->writable = false;
+
+ // close immediately if buffer is already empty
+ // otherwise wait for buffer to flush first
+ if ($this->data === '') {
+ $this->close();
+ }
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ if ($this->listening) {
+ $this->listening = false;
+ $this->loop->removeWriteStream($this->stream);
+ }
+
+ $this->closed = true;
+ $this->writable = false;
+ $this->data = '';
+
+ $this->emit('close');
+ $this->removeAllListeners();
+
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+
+ /** @internal */
+ public function handleWrite()
+ {
+ $error = null;
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$error) {
+ $error = array(
+ 'message' => $errstr,
+ 'number' => $errno,
+ 'file' => $errfile,
+ 'line' => $errline
+ );
+ });
+
+ if ($this->writeChunkSize === -1) {
+ $sent = fwrite($this->stream, $this->data);
+ } else {
+ $sent = fwrite($this->stream, $this->data, $this->writeChunkSize);
+ }
+
+ restore_error_handler();
+
+ // Only report errors if *nothing* could be sent.
+ // Any hard (permanent) error will fail to send any data at all.
+ // Sending excessive amounts of data will only flush *some* data and then
+ // report a temporary error (EAGAIN) which we do not raise here in order
+ // to keep the stream open for further tries to write.
+ // Should this turn out to be a permanent error later, it will eventually
+ // send *nothing* and we can detect this.
+ if ($sent === 0 || $sent === false) {
+ if ($error !== null) {
+ $error = new \ErrorException(
+ $error['message'],
+ 0,
+ $error['number'],
+ $error['file'],
+ $error['line']
+ );
+ }
+
+ $this->emit('error', array(new \RuntimeException('Unable to write to stream: ' . ($error !== null ? $error->getMessage() : 'Unknown error'), 0, $error)));
+ $this->close();
+
+ return;
+ }
+
+ $exceeded = isset($this->data[$this->softLimit - 1]);
+ $this->data = (string) substr($this->data, $sent);
+
+ // buffer has been above limit and is now below limit
+ if ($exceeded && !isset($this->data[$this->softLimit - 1])) {
+ $this->emit('drain');
+ }
+
+ // buffer is now completely empty => stop trying to write
+ if ($this->data === '') {
+ // stop waiting for resource to be writable
+ if ($this->listening) {
+ $this->loop->removeWriteStream($this->stream);
+ $this->listening = false;
+ }
+
+ // buffer is end()ing and now completely empty => close buffer
+ if (!$this->writable) {
+ $this->close();
+ }
+ }
+ }
+}
diff --git a/assets/php/vendor/react/stream/src/WritableStreamInterface.php b/assets/php/vendor/react/stream/src/WritableStreamInterface.php
new file mode 100644
index 0000000..3bc932e
--- /dev/null
+++ b/assets/php/vendor/react/stream/src/WritableStreamInterface.php
@@ -0,0 +1,347 @@
+<?php
+
+namespace React\Stream;
+
+use Evenement\EventEmitterInterface;
+
+/**
+ * The `WritableStreamInterface` is responsible for providing an interface for
+ * write-only streams and the writable side of duplex streams.
+ *
+ * Besides defining a few methods, this interface also implements the
+ * `EventEmitterInterface` which allows you to react to certain events:
+ *
+ * drain event:
+ * The `drain` event will be emitted whenever the write buffer became full
+ * previously and is now ready to accept more data.
+ *
+ * ```php
+ * $stream->on('drain', function () use ($stream) {
+ * echo 'Stream is now ready to accept more data';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once every time the buffer became full
+ * previously and is now ready to accept more data.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if the buffer never became full in the first place.
+ * This event SHOULD NOT be emitted if the buffer has not become full
+ * previously.
+ *
+ * This event is mostly used internally, see also `write()` for more details.
+ *
+ * pipe event:
+ * The `pipe` event will be emitted whenever a readable stream is `pipe()`d
+ * into this stream.
+ * The event receives a single `ReadableStreamInterface` argument for the
+ * source stream.
+ *
+ * ```php
+ * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream) {
+ * echo 'Now receiving piped data';
+ *
+ * // explicitly close target if source emits an error
+ * $source->on('error', function () use ($stream) {
+ * $stream->close();
+ * });
+ * });
+ *
+ * $source->pipe($stream);
+ * ```
+ *
+ * This event MUST be emitted once for each readable stream that is
+ * successfully piped into this destination stream.
+ * In other words, this event MAY be emitted any number of times, which may
+ * be zero times if no stream is ever piped into this stream.
+ * This event MUST NOT be emitted if either the source is not readable
+ * (closed already) or this destination is not writable (closed already).
+ *
+ * This event is mostly used internally, see also `pipe()` for more details.
+ *
+ * error event:
+ * The `error` event will be emitted once a fatal error occurs, usually while
+ * trying to write to this stream.
+ * The event receives a single `Exception` argument for the error instance.
+ *
+ * ```php
+ * $stream->on('error', function (Exception $e) {
+ * echo 'Error: ' . $e->getMessage() . PHP_EOL;
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once the stream detects a fatal error, such
+ * as a fatal transmission error.
+ * It SHOULD NOT be emitted after a previous `error` or `close` event.
+ * It MUST NOT be emitted if this is not a fatal error condition, such as
+ * a temporary network issue that did not cause any data to be lost.
+ *
+ * After the stream errors, it MUST close the stream and SHOULD thus be
+ * followed by a `close` event and then switch to non-writable mode, see
+ * also `close()` and `isWritable()`.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * only deal with data transmission and may choose
+ * to only emit this for a fatal transmission error once and will then
+ * close (terminate) the stream in response.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `error` event.
+ * In other words, an error may occur while either reading or writing the
+ * stream which should result in the same error processing.
+ *
+ * close event:
+ * The `close` event will be emitted once the stream closes (terminates).
+ *
+ * ```php
+ * $stream->on('close', function () {
+ * echo 'CLOSED';
+ * });
+ * ```
+ *
+ * This event SHOULD be emitted once or never at all, depending on whether
+ * the stream ever terminates.
+ * It SHOULD NOT be emitted after a previous `close` event.
+ *
+ * After the stream is closed, it MUST switch to non-writable mode,
+ * see also `isWritable()`.
+ *
+ * This event SHOULD be emitted whenever the stream closes, irrespective of
+ * whether this happens implicitly due to an unrecoverable error or
+ * explicitly when either side closes the stream.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will likely choose to emit this event after flushing the buffer from
+ * the `end()` method, after receiving a *successful* `end` event or after
+ * a fatal transmission `error` event.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close` event.
+ * In other words, after receiving this event, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ * Note that this event should not be confused with the `end` event.
+ *
+ * The event callback functions MUST be a valid `callable` that obeys strict
+ * parameter definitions and MUST accept event parameters exactly as documented.
+ * The event callback functions MUST NOT throw an `Exception`.
+ * The return value of the event callback functions will be ignored and has no
+ * effect, so for performance reasons you're recommended to not return any
+ * excessive data structures.
+ *
+ * Every implementation of this interface MUST follow these event semantics in
+ * order to be considered a well-behaving stream.
+ *
+ * > Note that higher-level implementations of this interface may choose to
+ * define additional events with dedicated semantics not defined as part of
+ * this low-level stream specification. Conformance with these event semantics
+ * is out of scope for this interface, so you may also have to refer to the
+ * documentation of such a higher-level implementation.
+ *
+ * @see EventEmitterInterface
+ * @see DuplexStreamInterface
+ */
+interface WritableStreamInterface extends EventEmitterInterface
+{
+ /**
+ * Checks whether this stream is in a writable state (not closed already).
+ *
+ * This method can be used to check if the stream still accepts writing
+ * any data or if it is ended or closed already.
+ * Writing any data to a non-writable stream is a NO-OP:
+ *
+ * ```php
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('end'); // NO-OP
+ * $stream->end('end'); // NO-OP
+ * ```
+ *
+ * A successfully opened stream always MUST start in writable mode.
+ *
+ * Once the stream ends or closes, it MUST switch to non-writable mode.
+ * This can happen any time, explicitly through `end()` or `close()` or
+ * implicitly due to a remote close or an unrecoverable transmission error.
+ * Once a stream has switched to non-writable mode, it MUST NOT transition
+ * back to writable mode.
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements an `isReadable()`
+ * method. Unless this is a half-open duplex stream, they SHOULD usually
+ * have the same return value.
+ *
+ * @return bool
+ */
+ public function isWritable();
+
+ /**
+ * Write some data into the stream.
+ *
+ * A successful write MUST be confirmed with a boolean `true`, which means
+ * that either the data was written (flushed) immediately or is buffered and
+ * scheduled for a future write. Note that this interface gives you no
+ * control over explicitly flushing the buffered data, as finding the
+ * appropriate time for this is beyond the scope of this interface and left
+ * up to the implementation of this interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * If a stream cannot handle writing (or flushing) the data, it SHOULD emit
+ * an `error` event and MAY `close()` the stream if it can not recover from
+ * this error.
+ *
+ * If the internal buffer is full after adding `$data`, then `write()`
+ * SHOULD return `false`, indicating that the caller should stop sending
+ * data until the buffer drains.
+ * The stream SHOULD send a `drain` event once the buffer is ready to accept
+ * more data.
+ *
+ * Similarly, if the the stream is not writable (already in a closed state)
+ * it MUST NOT process the given `$data` and SHOULD return `false`,
+ * indicating that the caller should stop sending data.
+ *
+ * The given `$data` argument MAY be of mixed type, but it's usually
+ * recommended it SHOULD be a `string` value or MAY use a type that allows
+ * representation as a `string` for maximum compatibility.
+ *
+ * Many common streams (such as a TCP/IP connection or a file-based stream)
+ * will only accept the raw (binary) payload data that is transferred over
+ * the wire as chunks of `string` values.
+ *
+ * Due to the stream-based nature of this, the sender may send any number
+ * of chunks with varying sizes. There are no guarantees that these chunks
+ * will be received with the exact same framing the sender intended to send.
+ * In other words, many lower-level protocols (such as TCP/IP) transfer the
+ * data in chunks that may be anywhere between single-byte values to several
+ * dozens of kilobytes. You may want to apply a higher-level protocol to
+ * these low-level data chunks in order to achieve proper message framing.
+ *
+ * @param mixed|string $data
+ * @return bool
+ */
+ public function write($data);
+
+ /**
+ * Successfully ends the stream (after optionally sending some final data).
+ *
+ * This method can be used to successfully end the stream, i.e. close
+ * the stream after sending out all data that is currently buffered.
+ *
+ * ```php
+ * $stream->write('hello');
+ * $stream->write('world');
+ * $stream->end();
+ * ```
+ *
+ * If there's no data currently buffered and nothing to be flushed, then
+ * this method MAY `close()` the stream immediately.
+ *
+ * If there's still data in the buffer that needs to be flushed first, then
+ * this method SHOULD try to write out this data and only then `close()`
+ * the stream.
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ *
+ * Note that this interface gives you no control over explicitly flushing
+ * the buffered data, as finding the appropriate time for this is beyond the
+ * scope of this interface and left up to the implementation of this
+ * interface.
+ *
+ * Many common streams (such as a TCP/IP connection or file-based stream)
+ * may choose to buffer all given data and schedule a future flush by using
+ * an underlying EventLoop to check when the resource is actually writable.
+ *
+ * You can optionally pass some final data that is written to the stream
+ * before ending the stream. If a non-`null` value is given as `$data`, then
+ * this method will behave just like calling `write($data)` before ending
+ * with no data.
+ *
+ * ```php
+ * // shorter version
+ * $stream->end('bye');
+ *
+ * // same as longer version
+ * $stream->write('bye');
+ * $stream->end();
+ * ```
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->end();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, calling this method SHOULD
+ * also end its readable side, unless the stream supports half-open mode.
+ * In other words, after calling this method, these streams SHOULD switch
+ * into non-writable AND non-readable mode, see also `isReadable()`.
+ * This implies that in this case, the stream SHOULD NOT emit any `data`
+ * or `end` events anymore.
+ * Streams MAY choose to use the `pause()` method logic for this, but
+ * special care may have to be taken to ensure a following call to the
+ * `resume()` method SHOULD NOT continue emitting readable events.
+ *
+ * Note that this method should not be confused with the `close()` method.
+ *
+ * @param mixed|string|null $data
+ * @return void
+ */
+ public function end($data = null);
+
+ /**
+ * Closes the stream (forcefully).
+ *
+ * This method can be used to forcefully close the stream, i.e. close
+ * the stream without waiting for any buffered data to be flushed.
+ * If there's still data in the buffer, this data SHOULD be discarded.
+ *
+ * ```php
+ * $stream->close();
+ * ```
+ *
+ * Once the stream is closed, it SHOULD emit a `close` event.
+ * Note that this event SHOULD NOT be emitted more than once, in particular
+ * if this method is called multiple times.
+ *
+ * After calling this method, the stream MUST switch into a non-writable
+ * mode, see also `isWritable()`.
+ * This means that no further writes are possible, so any additional
+ * `write()` or `end()` calls have no effect.
+ *
+ * ```php
+ * $stream->close();
+ * assert($stream->isWritable() === false);
+ *
+ * $stream->write('nope'); // NO-OP
+ * $stream->end(); // NO-OP
+ * ```
+ *
+ * Note that this method should not be confused with the `end()` method.
+ * Unlike the `end()` method, this method does not take care of any existing
+ * buffers and simply discards any buffer contents.
+ * Likewise, this method may also be called after calling `end()` on a
+ * stream in order to stop waiting for the stream to flush its final data.
+ *
+ * ```php
+ * $stream->end();
+ * $loop->addTimer(1.0, function () use ($stream) {
+ * $stream->close();
+ * });
+ * ```
+ *
+ * If this stream is a `DuplexStreamInterface`, you should also notice
+ * how the readable side of the stream also implements a `close()` method.
+ * In other words, after calling this method, the stream MUST switch into
+ * non-writable AND non-readable mode, see also `isReadable()`.
+ *
+ * @return void
+ * @see ReadableStreamInterface::close()
+ */
+ public function close();
+}
diff --git a/assets/php/vendor/react/stream/tests/CallableStub.php b/assets/php/vendor/react/stream/tests/CallableStub.php
new file mode 100644
index 0000000..31cc834
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/CallableStub.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace React\Tests\Stream;
+
+class CallableStub
+{
+ public function __invoke()
+ {
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/CompositeStreamTest.php b/assets/php/vendor/react/stream/tests/CompositeStreamTest.php
new file mode 100644
index 0000000..df89c3e
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/CompositeStreamTest.php
@@ -0,0 +1,267 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use React\Stream\CompositeStream;
+use React\Stream\ThroughStream;
+
+/**
+ * @covers React\Stream\CompositeStream
+ */
+class CompositeStreamTest extends TestCase
+{
+ /** @test */
+ public function itShouldCloseReadableIfNotWritable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(false);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldCloseWritableIfNotReadable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $composite->on('close', $this->expectCallableNever());
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardWritableCallsToWritableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->write('foo');
+ $composite->isWritable();
+ }
+
+ /** @test */
+ public function itShouldForwardReadableCallsToReadableStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->exactly(2))
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+ $readable
+ ->expects($this->once())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->isReadable();
+ $composite->pause();
+ $composite->resume();
+ }
+
+ /** @test */
+ public function itShouldNotForwardResumeIfStreamIsNotWritable()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->never())
+ ->method('resume');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->exactly(2))
+ ->method('isWritable')
+ ->willReturnOnConsecutiveCalls(true, false);
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->resume();
+ }
+
+ /** @test */
+ public function endShouldDelegateToWritableWithData()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->end('foo');
+ }
+
+ /** @test */
+ public function closeShouldCloseBothStreams()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('close');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->once())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('close');
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseOnlyOnce()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $readable->close();
+ $writable->close();
+ }
+
+ /** @test */
+ public function itShouldForwardCloseAndRemoveAllListeners()
+ {
+ $in = new ThroughStream();
+
+ $composite = new CompositeStream($in, $in);
+ $composite->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($composite->isReadable());
+ $this->assertTrue($composite->isWritable());
+ $this->assertCount(1, $composite->listeners('close'));
+
+ $composite->close();
+
+ $this->assertFalse($composite->isReadable());
+ $this->assertFalse($composite->isWritable());
+ $this->assertCount(0, $composite->listeners('close'));
+ }
+
+ /** @test */
+ public function itShouldReceiveForwardedEvents()
+ {
+ $readable = new ThroughStream();
+ $writable = new ThroughStream();
+
+ $composite = new CompositeStream($readable, $writable);
+ $composite->on('data', $this->expectCallableOnce());
+ $composite->on('drain', $this->expectCallableOnce());
+
+ $readable->emit('data', array('foo'));
+ $writable->emit('drain');
+ }
+
+ /** @test */
+ public function itShouldHandlePipingCorrectly()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->once())
+ ->method('isReadable')
+ ->willReturn(true);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $input = new ThroughStream();
+ $input->pipe($composite);
+ $input->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function itShouldForwardPipeCallsToReadableStream()
+ {
+ $readable = new ThroughStream();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(True);
+
+ $composite = new CompositeStream($readable, $writable);
+
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $composite->pipe($output);
+ $readable->emit('data', array('foo'));
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php b/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
new file mode 100644
index 0000000..fb5f02a
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/DuplexResourceStreamIntegrationTest.php
@@ -0,0 +1,352 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use React\Stream\DuplexResourceStream;
+use React\Stream\ReadableResourceStream;
+use React\EventLoop\ExtEventLoop;
+use React\EventLoop\ExtLibeventLoop;
+use React\EventLoop\ExtLibevLoop;
+use React\EventLoop\LoopInterface;
+use React\EventLoop\LibEventLoop;
+use React\EventLoop\LibEvLoop;
+use React\EventLoop\StreamSelectLoop;
+
+class DuplexResourceStreamIntegrationTest extends TestCase
+{
+ public function loopProvider()
+ {
+ return array(
+ array(
+ function() {
+ return true;
+ },
+ function () {
+ return new StreamSelectLoop();
+ }
+ ),
+ array(
+ function () {
+ return function_exists('event_base_new');
+ },
+ function () {
+ return class_exists('React\EventLoop\ExtLibeventLoop') ? new ExtLibeventLoop() : new LibEventLoop();
+ }
+ ),
+ array(
+ function () {
+ return class_exists('libev\EventLoop');
+ },
+ function () {
+ return class_exists('React\EventLoop\ExtLibevLoop') ? new ExtLibevLoop() : new LibEvLoop();
+ }
+ ),
+ array(
+ function () {
+ return class_exists('EventBase') && class_exists('React\EventLoop\ExtEventLoop');
+ },
+ function () {
+ return new ExtEventLoop();
+ }
+ )
+ );
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testBufferReadsLargeChunks($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $bufferSize = 4096;
+ $streamA = new DuplexResourceStream($sockA, $loop, $bufferSize);
+ $streamB = new DuplexResourceStream($sockB, $loop, $bufferSize);
+
+ $testString = str_repeat("*", $bufferSize + 1);
+
+ $buffer = "";
+ $streamB->on('data', function ($data) use (&$buffer) {
+ $buffer .= $data;
+ });
+
+ $streamA->write($testString);
+
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+ $this->loopTick($loop);
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($testString, $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testWriteLargeChunk($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // limit seems to be 192 KiB
+ $size = 256 * 1024;
+
+ // sending side sends and expects clean close with no errors
+ $streamA->end(str_repeat('*', $size));
+ $streamA->on('close', $this->expectCallableOnce());
+ $streamA->on('error', $this->expectCallableNever());
+
+ // receiving side counts bytes and expects clean close with no errors
+ $received = 0;
+ $streamB->on('data', function ($chunk) use (&$received) {
+ $received += strlen($chunk);
+ });
+ $streamB->on('close', $this->expectCallableOnce());
+ $streamB->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+
+ $this->assertEquals($size, $received);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotEmitDataIfNothingHasBeenWritten($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->end();
+
+ // streamB should not emit any data
+ $streamB->on('data', $this->expectCallableNever());
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfRemoteSideFromPairHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ list($sockA, $sockB) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, 0);
+
+ $streamA = new DuplexResourceStream($sockA, $loop);
+ $streamB = new DuplexResourceStream($sockB, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfServerSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($client, $loop);
+ $streamB = new DuplexResourceStream($peer, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testDoesNotWriteDataIfClientSideHasBeenClosed($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $server = stream_socket_server('tcp://127.0.0.1:0');
+
+ $client = stream_socket_client(stream_socket_get_name($server, false));
+ $peer = stream_socket_accept($server);
+
+ $streamA = new DuplexResourceStream($peer, $loop);
+ $streamB = new DuplexResourceStream($client, $loop);
+
+ // end streamA without writing any data
+ $streamA->pause();
+ $streamA->write('hello');
+ $streamA->on('close', $this->expectCallableOnce());
+
+ $streamB->on('data', $this->expectCallableNever());
+ $streamB->close();
+
+ $loop->run();
+
+ $streamA->close();
+ $streamB->close();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsSingleChunkFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo test', 'r'), $loop);
+ $stream->on('data', $this->expectCallableOnceWith("test\n"));
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsMultipleChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('echo a;sleep 0.1;echo b;sleep 0.1;echo c', 'r'), $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals("a\n" . "b\n" . "c\n", $buffer);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsLongChunksFromProcessPipe($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('dd if=/dev/zero bs=12345 count=1234 2>&-', 'r'), $loop);
+
+ $bytes = 0;
+ $stream->on('data', function ($chunk) use (&$bytes) {
+ $bytes += strlen($chunk);
+ });
+
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+
+ $this->assertEquals(12345 * 1234, $bytes);
+ }
+
+ /**
+ * @dataProvider loopProvider
+ */
+ public function testReadsNothingFromProcessPipeWithNoOutput($condition, $loopFactory)
+ {
+ if (true !== $condition()) {
+ return $this->markTestSkipped('Loop implementation not available');
+ }
+
+ $loop = $loopFactory();
+
+ $stream = new ReadableResourceStream(popen('true', 'r'), $loop);
+ $stream->on('data', $this->expectCallableNever());
+ $stream->on('end', $this->expectCallableOnce());
+ $stream->on('error', $this->expectCallableNever());
+
+ $loop->run();
+ }
+
+ private function loopTick(LoopInterface $loop)
+ {
+ $loop->addTimer(0, function () use ($loop) {
+ $loop->stop();
+ });
+ $loop->run();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php b/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php
new file mode 100644
index 0000000..3212ae8
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/DuplexResourceStreamTest.php
@@ -0,0 +1,495 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use React\Stream\DuplexResourceStream;
+use Clue\StreamFilter as Filter;
+use React\Stream\WritableResourceStream;
+
+class DuplexResourceStreamTest extends TestCase
+{
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructor()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new DuplexResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream('breakme', $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @expectedException RunTimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new DuplexResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorAcceptsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ }
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+ $conn->on('end', $this->expectCallableNever());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testEndShouldEndBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->once())->method('end')->with('foo');
+
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+ $conn->end('foo');
+ }
+
+
+ public function testEndAfterCloseIsNoOp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $buffer->expects($this->never())->method('end');
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->end();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::__construct
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::write
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+
+ rewind($stream);
+ $this->assertSame("foo\n", fgets($stream));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ * @covers React\Stream\DuplexResourceStream::isReadable
+ * @covers React\Stream\DuplexResourceStream::isWritable
+ */
+ public function testEnd()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end();
+
+ $this->assertFalse(is_resource($stream));
+ $this->assertFalse($conn->isReadable());
+ $this->assertFalse($conn->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::end
+ */
+ public function testEndRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->end('bye');
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ public function testEndedStreamsShouldNotWrite()
+ {
+ $file = tempnam(sys_get_temp_dir(), 'reactphptest_');
+ $stream = fopen($file, 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->write("foo\n");
+ $conn->end();
+
+ $res = $conn->write("bar\n");
+ $stream = fopen($file, 'r');
+
+ $this->assertSame("foo\n", fgets($stream));
+ $this->assertFalse($res);
+
+ unlink($file);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ public function testBufferEventsShouldBubbleUp()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $conn = new DuplexResourceStream($stream, $loop, null, $buffer);
+
+ $conn->on('drain', $this->expectCallableOnce());
+ $conn->on('error', $this->expectCallableOnce());
+
+ $buffer->emit('drain');
+ $buffer->emit('error', array(new \RuntimeException('Whoops')));
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\DuplexResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new DuplexResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop
+ ->expects($this->once())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) {
+ call_user_func($listener, $stream);
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php b/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php
new file mode 100644
index 0000000..39c0487
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/EnforceBlockingWrapper.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace React\Tests\Stream;
+
+/**
+ * Used to test dummy stream resources that do not support setting non-blocking mode
+ *
+ * @link http://php.net/manual/de/class.streamwrapper.php
+ */
+class EnforceBlockingWrapper
+{
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ return true;
+ }
+
+ public function stream_cast($cast_as)
+ {
+ return false;
+ }
+
+ public function stream_eof()
+ {
+ return false;
+ }
+
+ public function stream_set_option($option, $arg1, $arg2)
+ {
+ if ($option === STREAM_OPTION_BLOCKING) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/FunctionalInternetTest.php b/assets/php/vendor/react/stream/tests/FunctionalInternetTest.php
new file mode 100644
index 0000000..4d31e8e
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/FunctionalInternetTest.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use React\EventLoop\Factory;
+use React\EventLoop\LoopInterface;
+use React\Stream\DuplexResourceStream;
+use React\Stream\WritableResourceStream;
+
+/**
+ * @group internet
+ */
+class FunctionalInternetTest extends TestCase
+{
+ public function testUploadKilobytePlain()
+ {
+ $size = 1000;
+ $stream = stream_socket_client('tcp://httpbin.org:80');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockPlain()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tcp://httpbin.org:80');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadKilobyteSecure()
+ {
+ $size = 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream($stream, $loop);
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ public function testUploadBiggerBlockSecureRequiresSmallerChunkSize()
+ {
+ $size = 50 * 1000;
+ $stream = stream_socket_client('tls://httpbin.org:443');
+
+ $loop = Factory::create();
+ $stream = new DuplexResourceStream(
+ $stream,
+ $loop,
+ null,
+ new WritableResourceStream($stream, $loop, null, 8192)
+ );
+
+ $buffer = '';
+ $stream->on('data', function ($chunk) use (&$buffer) {
+ $buffer .= $chunk;
+ });
+
+ $stream->on('error', $this->expectCallableNever());
+
+ $stream->write("POST /post HTTP/1.0\r\nHost: httpbin.org\r\nContent-Length: $size\r\n\r\n" . str_repeat('.', $size));
+
+ $this->awaitStreamClose($stream, $loop);
+
+ $this->assertNotEquals('', $buffer);
+ }
+
+ private function awaitStreamClose(DuplexResourceStream $stream, LoopInterface $loop, $timeout = 10.0)
+ {
+ $stream->on('close', function () use ($loop) {
+ $loop->stop();
+ });
+
+ $that = $this;
+ $loop->addTimer($timeout, function () use ($loop, $that) {
+ $loop->stop();
+ $that->fail('Timed out while waiting for stream to close');
+ });
+
+ $loop->run();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php b/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php
new file mode 100644
index 0000000..20da96f
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/ReadableResourceStreamTest.php
@@ -0,0 +1,372 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use React\Stream\ReadableResourceStream;
+use Clue\StreamFilter as Filter;
+
+class ReadableResourceStreamTest extends TestCase
+{
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructor()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'r+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new ReadableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnInvalidStream()
+ {
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(false, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStream()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('HHVM does not report fopen mode for STDOUT');
+ }
+
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream(STDOUT, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnWriteOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'weANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new ReadableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new ReadableResourceStream($stream, $loop);
+ }
+
+
+ public function testCloseShouldEmitCloseEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+
+ $this->assertFalse($conn->isReadable());
+ }
+
+ public function testCloseTwiceShouldEmitCloseEventOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('close', $this->expectCallableOnce());
+
+ $conn->close();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEvent()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobar\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkMatchingBufferSize()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, 4321);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(4321, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::__construct
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataEventDoesEmitOneChunkUntilStreamEndsWhenBufferSizeIsInfinite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop, -1);
+
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, str_repeat("a", 100000));
+ rewind($stream);
+
+ $conn->handleData($stream);
+
+ $this->assertTrue($conn->isReadable());
+ $this->assertEquals(100000, strlen($capturedData));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testEmptyStreamShouldNotEmitData()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+
+ $conn->handleData($stream);
+ }
+
+ public function testPipeShouldReturnDestination()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $dest = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $this->assertSame($dest, $conn->pipe($dest));
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testClosingStreamInDataEventShouldNotTriggerError()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('error', $this->expectCallableNever());
+ $conn->on('data', function ($data) use ($conn) {
+ $conn->close();
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testPauseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->pause();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::pause
+ */
+ public function testResumeDoesAddStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->resume();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseRemovesReadStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testCloseAfterPauseRemovesReadStreamFromLoopOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+ $loop->expects($this->once())->method('removeReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->pause();
+ $conn->close();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::close
+ */
+ public function testResumeAfterCloseDoesAddReadStreamToLoopOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addReadStream')->with($stream);
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->close();
+ $conn->resume();
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataFiltered()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which removes every 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ return str_replace('a', '', $chunk);
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $capturedData = null;
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', function ($data) use (&$capturedData) {
+ $capturedData = $data;
+ });
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ $this->assertSame("foobr\n", $capturedData);
+ }
+
+ /**
+ * @covers React\Stream\ReadableResourceStream::handleData
+ */
+ public function testDataErrorShouldEmitErrorAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+
+ // add a filter which returns an error when encountering an 'a' when reading
+ Filter\append($stream, function ($chunk) {
+ if (strpos($chunk, 'a') !== false) {
+ throw new \Exception('Invalid');
+ }
+ return $chunk;
+ }, STREAM_FILTER_READ);
+
+ $loop = $this->createLoopMock();
+
+ $conn = new ReadableResourceStream($stream, $loop);
+ $conn->on('data', $this->expectCallableNever());
+ $conn->on('error', $this->expectCallableOnce());
+ $conn->on('close', $this->expectCallableOnce());
+
+ fwrite($stream, "foobar\n");
+ rewind($stream);
+
+ $conn->handleData($stream);
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php b/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php
new file mode 100644
index 0000000..6984f24
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/Stub/ReadableStreamStub.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace React\Tests\Stream\Stub;
+
+use Evenement\EventEmitter;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\WritableStreamInterface;
+use React\Stream\Util;
+
+class ReadableStreamStub extends EventEmitter implements ReadableStreamInterface
+{
+ public $readable = true;
+ public $paused = false;
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ // trigger data event
+ public function write($data)
+ {
+ $this->emit('data', array($data));
+ }
+
+ // trigger error event
+ public function error($error)
+ {
+ $this->emit('error', array($error));
+ }
+
+ // trigger end event
+ public function end()
+ {
+ $this->emit('end', array());
+ }
+
+ public function pause()
+ {
+ $this->paused = true;
+ }
+
+ public function resume()
+ {
+ $this->paused = false;
+ }
+
+ public function close()
+ {
+ $this->readable = false;
+
+ $this->emit('close');
+ }
+
+ public function pipe(WritableStreamInterface $dest, array $options = array())
+ {
+ Util::pipe($this, $dest, $options);
+
+ return $dest;
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/TestCase.php b/assets/php/vendor/react/stream/tests/TestCase.php
new file mode 100644
index 0000000..c8fc1db
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/TestCase.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use PHPUnit\Framework\TestCase as BaseTestCase;
+
+class TestCase extends BaseTestCase
+{
+ protected function expectCallableExactly($amount)
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->exactly($amount))
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnce()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->once())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function expectCallableOnceWith($value)
+ {
+ $callback = $this->createCallableMock();
+ $callback
+ ->expects($this->once())
+ ->method('__invoke')
+ ->with($value);
+
+ return $callback;
+ }
+
+ protected function expectCallableNever()
+ {
+ $mock = $this->createCallableMock();
+ $mock
+ ->expects($this->never())
+ ->method('__invoke');
+
+ return $mock;
+ }
+
+ protected function createCallableMock()
+ {
+ return $this->getMockBuilder('React\Tests\Stream\CallableStub')->getMock();
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/ThroughStreamTest.php b/assets/php/vendor/react/stream/tests/ThroughStreamTest.php
new file mode 100644
index 0000000..a98badf
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/ThroughStreamTest.php
@@ -0,0 +1,267 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use React\Stream\ThroughStream;
+
+/**
+ * @covers React\Stream\ThroughStream
+ */
+class ThroughStreamTest extends TestCase
+{
+ /**
+ * @test
+ * @expectedException InvalidArgumentException
+ */
+ public function itShouldRejectInvalidCallback()
+ {
+ new ThroughStream(123);
+ }
+
+ /** @test */
+ public function itShouldReturnTrueForAnyDataWrittenToIt()
+ {
+ $through = new ThroughStream();
+ $ret = $through->write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToIt()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruFunction()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitAnyDataWrittenToItPassedThruCallback()
+ {
+ $through = new ThroughStream('strtoupper');
+ $through->on('data', $this->expectCallableOnceWith('FOO'));
+ $through->write('foo');
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsException()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->write('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldEmitErrorAndCloseIfCallbackThrowsExceptionOnEnd()
+ {
+ $through = new ThroughStream(function () {
+ throw new \RuntimeException();
+ });
+ $through->on('error', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableNever());
+
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function itShouldReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $ret = $through->write('foo');
+
+ $this->assertFalse($ret);
+ }
+
+ /** @test */
+ public function itShouldEmitDrainOnResumeAfterReturnFalseForAnyDataWrittenToItWhenPaused()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->write('foo');
+
+ $through->on('drain', $this->expectCallableOnce());
+ $through->resume();
+ }
+
+ /** @test */
+ public function itShouldReturnTrueForAnyDataWrittenToItWhenResumedAfterPause()
+ {
+ $through = new ThroughStream();
+ $through->on('drain', $this->expectCallableNever());
+ $through->pause();
+ $through->resume();
+ $ret = $through->write('foo');
+
+ $this->assertTrue($ret);
+ }
+
+ /** @test */
+ public function pipingStuffIntoItShouldWork()
+ {
+ $readable = new ThroughStream();
+
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+
+ $readable->pipe($through);
+ $readable->emit('data', array('foo'));
+ }
+
+ /** @test */
+ public function endShouldEmitEndAndClose()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->on('end', $this->expectCallableOnce());
+ $through->on('close', $this->expectCallableOnce());
+ $through->end();
+ }
+
+ /** @test */
+ public function endShouldCloseTheStream()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endShouldWriteDataBeforeClosing()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnceWith('foo'));
+ $through->end('foo');
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function endTwiceShouldOnlyEmitOnce()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableOnce('first'));
+ $through->end('first');
+ $through->end('ignored');
+ }
+
+ /** @test */
+ public function writeAfterEndShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', $this->expectCallableNever());
+ $through->end();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataWillCloseStreamShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->on('data', array($through, 'close'));
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToPausedShouldReturnFalse()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+
+ $this->assertFalse($through->write('foo'));
+ }
+
+ /** @test */
+ public function writeDataToResumedShouldReturnTrue()
+ {
+ $through = new ThroughStream();
+ $through->pause();
+ $through->resume();
+
+ $this->assertTrue($through->write('foo'));
+ }
+
+ /** @test */
+ public function itShouldBeReadableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isReadable());
+ }
+
+ /** @test */
+ public function itShouldBeWritableByDefault()
+ {
+ $through = new ThroughStream();
+ $this->assertTrue($through->isWritable());
+ }
+
+ /** @test */
+ public function closeShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function doubleCloseShouldCloseOnce()
+ {
+ $through = new ThroughStream();
+
+ $through->on('close', $this->expectCallableOnce());
+
+ $through->close();
+ $through->close();
+
+ $this->assertFalse($through->isReadable());
+ $this->assertFalse($through->isWritable());
+ }
+
+ /** @test */
+ public function pipeShouldPipeCorrectly()
+ {
+ $output = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $output->expects($this->any())->method('isWritable')->willReturn(True);
+ $output
+ ->expects($this->once())
+ ->method('write')
+ ->with('foo');
+
+ $through = new ThroughStream();
+ $through->pipe($output);
+ $through->write('foo');
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/UtilTest.php b/assets/php/vendor/react/stream/tests/UtilTest.php
new file mode 100644
index 0000000..3d113ab
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/UtilTest.php
@@ -0,0 +1,273 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use React\Stream\WritableResourceStream;
+use React\Stream\Util;
+use React\Stream\CompositeStream;
+use React\Stream\ThroughStream;
+
+/**
+ * @covers React\Stream\Util
+ */
+class UtilTest extends TestCase
+{
+ public function testPipeReturnsDestinationStream()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+
+ $ret = Util::pipe($readable, $writable);
+
+ $this->assertSame($writable, $ret);
+ }
+
+ public function testPipeNonReadableSourceShouldDoNothing()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(false);
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->never())
+ ->method('isWritable');
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeIntoNonWritableDestinationShouldPauseSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(false);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+ }
+
+ public function testPipeClosingDestPausesSource()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable
+ ->expects($this->any())
+ ->method('isReadable')
+ ->willReturn(true);
+ $readable
+ ->expects($this->once())
+ ->method('pause');
+
+ $writable = new ThroughStream();
+
+ Util::pipe($readable, $writable);
+
+ $writable->close();
+ }
+
+ public function testPipeWithEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('end');
+
+ Util::pipe($readable, $writable);
+
+ $readable->end();
+ }
+
+ public function testPipeWithoutEnd()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->never())
+ ->method('end');
+
+ Util::pipe($readable, $writable, array('end' => false));
+
+ $readable->end();
+ }
+
+ public function testPipeWithTooSlowWritableShouldPauseReadable()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->once())
+ ->method('write')
+ ->with('some data')
+ ->will($this->returnValue(false));
+
+ $readable->pipe($writable);
+
+ $this->assertFalse($readable->paused);
+ $readable->write('some data');
+ $this->assertTrue($readable->paused);
+ }
+
+ public function testPipeWithTooSlowWritableShouldResumeOnDrain()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $onDrain = null;
+
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable
+ ->expects($this->any())
+ ->method('isWritable')
+ ->willReturn(true);
+ $writable
+ ->expects($this->any())
+ ->method('on')
+ ->will($this->returnCallback(function ($name, $callback) use (&$onDrain) {
+ if ($name === 'drain') {
+ $onDrain = $callback;
+ }
+ }));
+
+ $readable->pipe($writable);
+ $readable->pause();
+
+ $this->assertTrue($readable->paused);
+ $this->assertNotNull($onDrain);
+ $onDrain();
+ $this->assertFalse($readable->paused);
+ }
+
+ public function testPipeWithWritableResourceStream()
+ {
+ $readable = new Stub\ReadableStreamStub();
+
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $readable->pipe($buffer);
+
+ $readable->write('hello, I am some ');
+ $readable->write('random data');
+
+ $buffer->handleWrite();
+ rewind($stream);
+ $this->assertSame('hello, I am some random data', stream_get_contents($stream));
+ }
+
+ public function testPipeSetsUpListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+
+ Util::pipe($source, $dest);
+
+ $this->assertCount(1, $source->listeners('data'));
+ $this->assertCount(1, $source->listeners('end'));
+ $this->assertCount(1, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingSourceRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $source->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeClosingDestRemovesListeners()
+ {
+ $source = new ThroughStream();
+ $dest = new ThroughStream();
+
+ Util::pipe($source, $dest);
+
+ $dest->close();
+
+ $this->assertCount(0, $source->listeners('data'));
+ $this->assertCount(0, $source->listeners('end'));
+ $this->assertCount(0, $dest->listeners('drain'));
+ }
+
+ public function testPipeDuplexIntoSelfEndsOnEnd()
+ {
+ $readable = $this->getMockBuilder('React\Stream\ReadableStreamInterface')->getMock();
+ $readable->expects($this->any())->method('isReadable')->willReturn(true);
+ $writable = $this->getMockBuilder('React\Stream\WritableStreamInterface')->getMock();
+ $writable->expects($this->any())->method('isWritable')->willReturn(true);
+ $duplex = new CompositeStream($readable, $writable);
+
+ Util::pipe($duplex, $duplex);
+
+ $writable->expects($this->once())->method('end');
+
+ $duplex->emit('end');
+ }
+
+ /** @test */
+ public function forwardEventsShouldSetupForwards()
+ {
+ $source = new ThroughStream();
+ $target = new ThroughStream();
+
+ Util::forwardEvents($source, $target, array('data'));
+ $target->on('data', $this->expectCallableOnce());
+ $target->on('foo', $this->expectCallableNever());
+
+ $source->emit('data', array('hello'));
+ $source->emit('foo', array('bar'));
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+
+ private function notEqualTo($value)
+ {
+ return new \PHPUnit_Framework_Constraint_Not($value);
+ }
+}
diff --git a/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php b/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php
new file mode 100644
index 0000000..05bce9c
--- /dev/null
+++ b/assets/php/vendor/react/stream/tests/WritableStreamResourceTest.php
@@ -0,0 +1,534 @@
+<?php
+
+namespace React\Tests\Stream;
+
+use Clue\StreamFilter as Filter;
+use React\Stream\WritableResourceStream;
+
+class WritableResourceStreamTest extends TestCase
+{
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructor()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @doesNotPerformAssertions
+ */
+ public function testConstructorWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = @fopen($name, 'w+eANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsIfNotAValidStreamResource()
+ {
+ $stream = null;
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStream()
+ {
+ $stream = fopen('php://temp', 'r');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException InvalidArgumentException
+ */
+ public function testConstructorThrowsExceptionOnReadOnlyStreamWithExcessiveMode()
+ {
+ // excessive flags are ignored for temp streams, so we have to use a file stream
+ $name = tempnam(sys_get_temp_dir(), 'test');
+ $stream = fopen($name, 'reANYTHING');
+ unlink($name);
+
+ $loop = $this->createLoopMock();
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::__construct
+ * @expectedException RuntimeException
+ */
+ public function testConstructorThrowsExceptionIfStreamDoesNotSupportNonBlocking()
+ {
+ if (!in_array('blocking', stream_get_wrappers())) {
+ stream_wrapper_register('blocking', 'React\Tests\Stream\EnforceBlockingWrapper');
+ }
+
+ $stream = fopen('blocking://test', 'r+');
+ $loop = $this->createLoopMock();
+
+ new WritableResourceStream($stream, $loop);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->write("foobar\n");
+ rewind($stream);
+ $this->assertSame("foobar\n", fread($stream, 1024));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteWithDataDoesAddResourceToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream));
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("foobar\n");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmptyWriteDoesNotAddToLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->never())->method('addWriteStream');
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $buffer->write("");
+ $buffer->write(null);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+ $loop->preventWrites = true;
+
+ $buffer = new WritableResourceStream($stream, $loop, 4);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $this->assertTrue($buffer->write("foo"));
+ $loop->preventWrites = false;
+ $this->assertFalse($buffer->write("bar\n"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ */
+ public function testWriteReturnsFalseWhenWritableResourceStreamIsExactlyFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 3);
+
+ $this->assertFalse($buffer->write("foo"));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteDetectsWhenOtherSideIsClosed()
+ {
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+
+ $loop = $this->createWriteableLoopMock();
+
+ $buffer = new WritableResourceStream($a, $loop, 4);
+ $buffer->on('error', $this->expectCallableOnce());
+
+ fclose($b);
+
+ $buffer->write("foo");
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testEmitsDrainAfterWriteWhichExceedsBuffer()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testWriteInDrain()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+ $buffer->on('error', $this->expectCallableNever());
+
+ $buffer->once('drain', function () use ($buffer) {
+ $buffer->write("bar\n");
+ $buffer->handleWrite();
+ });
+
+ $this->assertFalse($buffer->write("foo\n"));
+ $buffer->handleWrite();
+
+ fseek($stream, 0);
+ $this->assertSame("foo\nbar\n", stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWrite()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testDrainAfterWriteWillRemoveResourceFromLoopWithoutClosing()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', $this->expectCallableOnce());
+
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testClosingDuringDrainAfterWriteWillRemoveResourceFromLoopOnceAndClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer = new WritableResourceStream($stream, $loop, 2);
+
+ $buffer->on('drain', function () use ($buffer) {
+ $buffer->close();
+ });
+
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->write("foo");
+ $buffer->handleWrite();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataClosesImmediatelyIfWritableResourceStreamIsEmpty()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithoutDataDoesNotCloseIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end();
+ $this->assertFalse($buffer->isWritable());
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataClosesImmediatelyIfWritableResourceStreamFlushes()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ $buffer->handleWrite();
+ $this->assertSame('final words', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::end
+ */
+ public function testEndWithDataDoesNotCloseImmediatelyIfWritableResourceStreamIsFull()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableNever());
+
+ $buffer->write('foo');
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->end('final words');
+ $this->assertFalse($buffer->isWritable());
+
+ rewind($stream);
+ $this->assertSame('', stream_get_contents($stream));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::isWritable
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClose()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', $this->expectCallableNever());
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $this->assertTrue($buffer->isWritable());
+ $buffer->close();
+ $this->assertFalse($buffer->isWritable());
+
+ $this->assertEquals(array(), $buffer->listeners('close'));
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingAfterWriteRemovesStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->once())->method('removeWriteStream')->with($stream);
+
+ $buffer->write('foo');
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ $loop->expects($this->never())->method('removeWriteStream');
+
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testDoubleCloseWillEmitOnlyOnce()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('close', $this->expectCallableOnce());
+
+ $buffer->close();
+ $buffer->close();
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::write
+ * @covers React\Stream\WritableResourceStream::close
+ */
+ public function testWritingToClosedWritableResourceStreamShouldNotWriteToStream()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $filterBuffer = '';
+ $loop = $this->createLoopMock();
+
+ $buffer = new WritableResourceStream($stream, $loop);
+
+ Filter\append($stream, function ($chunk) use (&$filterBuffer) {
+ $filterBuffer .= $chunk;
+ return $chunk;
+ });
+
+ $buffer->close();
+
+ $buffer->write('foo');
+
+ $buffer->handleWrite();
+ $this->assertSame('', $filterBuffer);
+ }
+
+ /**
+ * @covers React\Stream\WritableResourceStream::handleWrite
+ */
+ public function testErrorWhenStreamResourceIsInvalid()
+ {
+ $stream = fopen('php://temp', 'r+');
+ $loop = $this->createWriteableLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($stream, $loop);
+ $buffer->on('error', function ($message) use (&$error) {
+ $error = $message;
+ });
+
+ // invalidate stream resource
+ fclose($stream);
+
+ $buffer->write('Attempting to write to bad stream');
+
+ $this->assertInstanceOf('Exception', $error);
+
+ // the error messages differ between PHP versions, let's just check substrings
+ $this->assertContains('Unable to write to stream: ', $error->getMessage());
+ $this->assertContains(' not a valid stream resource', $error->getMessage(), '', true);
+ }
+
+ public function testWritingToClosedStream()
+ {
+ if ('Darwin' === PHP_OS) {
+ $this->markTestSkipped('OS X issue with shutting down pair for writing');
+ }
+
+ list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+ $loop = $this->createLoopMock();
+
+ $error = null;
+
+ $buffer = new WritableResourceStream($a, $loop);
+ $buffer->on('error', function($message) use (&$error) {
+ $error = $message;
+ });
+
+ $buffer->write('foo');
+ $buffer->handleWrite();
+ stream_socket_shutdown($b, STREAM_SHUT_RD);
+ stream_socket_shutdown($a, STREAM_SHUT_RD);
+ $buffer->write('bar');
+ $buffer->handleWrite();
+
+ $this->assertInstanceOf('Exception', $error);
+ $this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());
+ }
+
+ private function createWriteableLoopMock()
+ {
+ $loop = $this->createLoopMock();
+ $loop->preventWrites = false;
+ $loop
+ ->expects($this->any())
+ ->method('addWriteStream')
+ ->will($this->returnCallback(function ($stream, $listener) use ($loop) {
+ if (!$loop->preventWrites) {
+ call_user_func($listener, $stream);
+ }
+ }));
+
+ return $loop;
+ }
+
+ private function createLoopMock()
+ {
+ return $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
+ }
+}