├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json └── src ├── CompositeStream.php ├── DuplexResourceStream.php ├── DuplexStreamInterface.php ├── ReadableResourceStream.php ├── ReadableStreamInterface.php ├── ThroughStream.php ├── Util.php ├── WritableResourceStream.php └── WritableStreamInterface.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.3.0 (2023-06-16) 4 | 5 | * Feature: Full PHP 8.1 and PHP 8.2 compatibility. 6 | (#160 by @SimonFrings, #165 by @clue and #169 by @WyriHaximus) 7 | 8 | * Feature: Avoid unneeded syscall when creating non-blocking `DuplexResourceStream`. 9 | (#164 by @clue) 10 | 11 | * Minor documentation improvements. 12 | (#161 by @mrsimonbennett, #162 by @SimonFrings and #166 by @nhedger) 13 | 14 | * Improve test suite and project setup and report failed assertions. 15 | (#168 and #170 by @clue and #163 by @SimonFrings) 16 | 17 | ## 1.2.0 (2021-07-11) 18 | 19 | A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop). 20 | 21 | * Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop). 22 | (#159 by @clue) 23 | 24 | ```php 25 | // old (still supported) 26 | $stream = new ReadableResourceStream($resource, $loop); 27 | $stream = new WritabeResourceStream($resource, $loop); 28 | $stream = new DuplexResourceStream($resource, $loop); 29 | 30 | // new (using default loop) 31 | $stream = new ReadableResourceStream($resource); 32 | $stream = new WritabeResourceStream($resource); 33 | $stream = new DuplexResourceStream($resource); 34 | ``` 35 | 36 | * Improve test suite, use GitHub actions for continuous integration (CI), 37 | update PHPUnit config, run tests on PHP 8 and add full core team to the license. 38 | (#153, #156 and #157 by @SimonFrings and #154 by @WyriHaximus) 39 | 40 | ## 1.1.1 (2020-05-04) 41 | 42 | * Fix: Fix faulty write buffer behavior when sending large data chunks over TLS (Mac OS X only). 43 | (#150 by @clue) 44 | 45 | * Minor code style improvements to fix phpstan analysis warnings and 46 | add `.gitattributes` to exclude dev files from exports. 47 | (#140 by @flow-control and #144 by @reedy) 48 | 49 | * Improve test suite to run tests on PHP 7.4 and simplify test matrix. 50 | (#147 by @clue) 51 | 52 | ## 1.1.0 (2019-01-01) 53 | 54 | * Improvement: Increase performance by optimizing global function and constant look ups. 55 | (#137 by @WyriHaximus) 56 | 57 | * Travis: Test against PHP 7.3. 58 | (#138 by @WyriHaximus) 59 | 60 | * Fix: Ignore empty reads. 61 | (#139 by @WyriHaximus) 62 | 63 | ## 1.0.0 (2018-07-11) 64 | 65 | * First stable LTS release, now following [SemVer](https://semver.org/). 66 | We'd like to emphasize that this component is production ready and battle-tested. 67 | We plan to support all long-term support (LTS) releases for at least 24 months, 68 | so you have a rock-solid foundation to build on top of. 69 | 70 | > Contains no other changes, so it's actually fully compatible with the v0.7.7 release. 71 | 72 | ## 0.7.7 (2018-01-19) 73 | 74 | * Improve test suite by fixing forward compatibility with upcoming EventLoop 75 | releases, avoid risky tests and add test group to skip integration tests 76 | relying on internet connection and apply appropriate test timeouts. 77 | (#128, #131 and #132 by @clue) 78 | 79 | ## 0.7.6 (2017-12-21) 80 | 81 | * Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12 82 | (#126 by @clue) 83 | 84 | * Improve test suite by simplifying test bootstrapping logic via Composer and 85 | test against PHP 7.2 86 | (#127 by @clue and #124 by @carusogabriel) 87 | 88 | ## 0.7.5 (2017-11-20) 89 | 90 | * Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream` 91 | (#119 by @clue) 92 | 93 | * Fix: Fix forward compatibility with upcoming EventLoop releases 94 | (#121 by @clue) 95 | 96 | * Restructure examples to ease getting started 97 | (#123 by @clue) 98 | 99 | * Improve test suite by adding forward compatibility with PHPUnit 6 and 100 | ignore Mac OS X test failures for now until Travis tests work again 101 | (#122 by @gabriel-caruso and #120 by @clue) 102 | 103 | ## 0.7.4 (2017-10-11) 104 | 105 | * Fix: Remove event listeners from `CompositeStream` once closed and 106 | remove undocumented left-over `close` event argument 107 | (#116 by @clue) 108 | 109 | * Minor documentation improvements: Fix wrong class name in example, 110 | fix typos in README and 111 | fix forward compatibility with upcoming EventLoop releases in example 112 | (#113 by @docteurklein and #114 and #115 by @clue) 113 | 114 | * Improve test suite by running against Mac OS X on Travis 115 | (#112 by @clue) 116 | 117 | ## 0.7.3 (2017-08-05) 118 | 119 | * Improvement: Support Événement 3.0 a long side 2.0 and 1.0 120 | (#108 by @WyriHaximus) 121 | 122 | * Readme: Corrected loop initialization in usage example 123 | (#109 by @pulyavin) 124 | 125 | * Travis: Lock linux distribution preventing future builds from breaking 126 | (#110 by @clue) 127 | 128 | ## 0.7.2 (2017-06-15) 129 | 130 | * Bug fix: WritableResourceStream: Close the underlying stream when closing the stream. 131 | (#107 by @WyriHaximus) 132 | 133 | ## 0.7.1 (2017-05-20) 134 | 135 | * Feature: Add optional `$writeChunkSize` parameter to limit maximum number of 136 | bytes to write at once. 137 | (#105 by @clue) 138 | 139 | ```php 140 | $stream = new WritableResourceStream(STDOUT, $loop, null, 8192); 141 | ``` 142 | 143 | * Ignore HHVM test failures for now until Travis tests work again 144 | (#106 by @clue) 145 | 146 | ## 0.7.0 (2017-05-04) 147 | 148 | * Removed / BC break: Remove deprecated and unneeded functionality 149 | (#45, #87, #90, #91 and #93 by @clue) 150 | 151 | * Remove deprecated `Stream` class, use `DuplexResourceStream` instead 152 | (#87 by @clue) 153 | 154 | * Remove public `$buffer` property, use new constructor parameters instead 155 | (#91 by @clue) 156 | 157 | * Remove public `$stream` property from all resource streams 158 | (#90 by @clue) 159 | 160 | * Remove undocumented and now unused `ReadableStream` and `WritableStream` 161 | (#93 by @clue) 162 | 163 | * Remove `BufferedSink` 164 | (#45 by @clue) 165 | 166 | * Feature / BC break: Simplify `ThroughStream` by using data callback instead of 167 | inheritance. It is now a direct implementation of `DuplexStreamInterface`. 168 | (#88 and #89 by @clue) 169 | 170 | ```php 171 | $through = new ThroughStream(function ($data) { 172 | return json_encode($data) . PHP_EOL; 173 | }); 174 | $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); 175 | 176 | $through->write(array(2, true)); 177 | ``` 178 | 179 | * Feature / BC break: The `CompositeStream` starts closed if either side is 180 | already closed and forwards pause to pipe source on first write attempt. 181 | (#96 and #103 by @clue) 182 | 183 | If either side of the composite stream closes, it will also close the other 184 | side. We now also ensure that if either side is already closed during 185 | instantiation, it will also close the other side. 186 | 187 | * BC break: Mark all classes as `final` and 188 | mark internal API as `private` to discourage inheritance 189 | (#95 and #99 by @clue) 190 | 191 | * Feature / BC break: Only emit `error` event for fatal errors 192 | (#92 by @clue) 193 | 194 | > The `error` event was previously also allowed to be emitted for non-fatal 195 | errors, but our implementations actually only ever emitted this as a fatal 196 | error and then closed the stream. 197 | 198 | * Feature: Explicitly allow custom events and exclude any semantics 199 | (#97 by @clue) 200 | 201 | * Strict definition for event callback functions 202 | (#101 by @clue) 203 | 204 | * Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation 205 | (#100 and #102 by @clue) 206 | 207 | * Actually require all dependencies so this is self-contained and improve 208 | forward compatibility with EventLoop v1.0 and v0.5 209 | (#94 and #98 by @clue) 210 | 211 | ## 0.6.0 (2017-03-26) 212 | 213 | * Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream` 214 | (#85 by @clue) 215 | 216 | ```php 217 | // old (does still work for BC reasons) 218 | $stream = new Stream($connection, $loop); 219 | 220 | // new 221 | $stream = new DuplexResourceStream($connection, $loop); 222 | ``` 223 | 224 | Note that the `DuplexResourceStream` now rejects read-only or write-only 225 | streams, so this may affect BC. If you want a read-only or write-only 226 | resource, use `ReadableResourceStream` or `WritableResourceStream` instead of 227 | `DuplexResourceStream`. 228 | 229 | > BC note: This class was previously called `Stream`. The `Stream` class still 230 | exists for BC reasons and will be removed in future versions of this package. 231 | 232 | * Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`) 233 | (#84 by @clue) 234 | 235 | ```php 236 | // old 237 | $stream = new Buffer(STDOUT, $loop); 238 | 239 | // new 240 | $stream = new WritableResourceStream(STDOUT, $loop); 241 | ``` 242 | 243 | * Feature: Add `ReadableResourceStream` 244 | (#83 by @clue) 245 | 246 | ```php 247 | $stream = new ReadableResourceStream(STDIN, $loop); 248 | ``` 249 | 250 | * Fix / BC Break: Enforce using non-blocking I/O 251 | (#46 by @clue) 252 | 253 | > BC note: This is known to affect process pipes on Windows which do not 254 | support non-blocking I/O and could thus block the whole EventLoop previously. 255 | 256 | * Feature / Fix / BC break: Consistent semantics for 257 | `DuplexStreamInterface::end()` to ensure it SHOULD also end readable side 258 | (#86 by @clue) 259 | 260 | * Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4 261 | (#80 by @clue) 262 | 263 | ## 0.5.0 (2017-03-08) 264 | 265 | * Feature / BC break: Consistent `end` event semantics (EOF) 266 | (#70 by @clue) 267 | 268 | The `end` event will now only be emitted for a *successful* end, not if the 269 | stream closes due to an unrecoverable `error` event or if you call `close()` 270 | explicitly. 271 | If you want to detect when the stream closes (terminates), use the `close` 272 | event instead. 273 | 274 | * BC break: Remove custom (undocumented) `full-drain` event from `Buffer` 275 | (#63 and #68 by @clue) 276 | 277 | > The `full-drain` event was undocumented and mostly used internally. 278 | Relying on this event has attracted some low-quality code in the past, so 279 | we've removed this from the public API in order to work out a better 280 | solution instead. 281 | If you want to detect when the buffer finishes flushing data to the stream, 282 | you may want to look into its `end()` method or the `close` event instead. 283 | 284 | * Feature / BC break: Consistent event semantics and documentation, 285 | explicitly state *when* events will be emitted and *which* arguments they 286 | receive. 287 | (#73 and #69 by @clue) 288 | 289 | The documentation now explicitly defines each event and its arguments. 290 | Custom events and event arguments are still supported. 291 | Most notably, all defined events only receive inherently required event 292 | arguments and no longer transmit the instance they are emitted on for 293 | consistency and performance reasons. 294 | 295 | ```php 296 | // old (inconsistent and not supported by all implementations) 297 | $stream->on('data', function ($data, $stream) { 298 | // process $data 299 | }); 300 | 301 | // new (consistent throughout the whole ecosystem) 302 | $stream->on('data', function ($data) use ($stream) { 303 | // process $data 304 | }); 305 | ``` 306 | 307 | > This mostly adds documentation (and thus some stricter, consistent 308 | definitions) for the existing behavior, it does NOT define any major 309 | changes otherwise. 310 | Most existing code should be compatible with these changes, unless 311 | it relied on some undocumented/unintended semantics. 312 | 313 | * Feature / BC break: Consistent method semantics and documentation 314 | (#72 by @clue) 315 | 316 | > This mostly adds documentation (and thus some stricter, consistent 317 | definitions) for the existing behavior, it does NOT define any major 318 | changes otherwise. 319 | Most existing code should be compatible with these changes, unless 320 | it relied on some undocumented/unintended semantics. 321 | 322 | * Feature: Consistent `pipe()` semantics for closed and closing streams 323 | (#71 from @clue) 324 | 325 | The source stream will now always be paused via `pause()` when the 326 | destination stream closes. Also, properly stop piping if the source 327 | stream closes and remove all event forwarding. 328 | 329 | * Improve test suite by adding PHPUnit to `require-dev` and improving coverage. 330 | (#74 and #75 by @clue, #66 by @nawarian) 331 | 332 | ## 0.4.6 (2017-01-25) 333 | 334 | * Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone) 335 | (#62 by @clue) 336 | 337 | * Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream` 338 | (#60 by @clue) 339 | 340 | * Fix: Consistent `close` event behavior for `Buffer` 341 | (#61 by @clue) 342 | 343 | ## 0.4.5 (2016-11-13) 344 | 345 | * Feature: Support setting read buffer size to `null` (infinite) 346 | (#42 by @clue) 347 | 348 | * Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event 349 | (#55 by @clue) 350 | 351 | * Vastly improved performance by factor of 10x to 20x. 352 | Raise default buffer sizes to 64 KiB and simplify and improve error handling 353 | and unneeded function calls. 354 | (#53, #55, #56 by @clue) 355 | 356 | ## 0.4.4 (2016-08-22) 357 | 358 | * Bug fix: Emit `error` event and close `Stream` when accessing the underlying 359 | stream resource fails with a permanent error. 360 | (#52 and #40 by @clue, #25 by @lysenkobv) 361 | 362 | * Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF) 363 | (#39 by @clue) 364 | 365 | * Bug fix: Ignore empty writes to `Buffer` 366 | (#51 by @clue) 367 | 368 | * Add benchmarking script to measure throughput in CI 369 | (#41 by @clue) 370 | 371 | ## 0.4.3 (2015-10-07) 372 | 373 | * Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau) 374 | * Bug fix: No double-write during drain call (@arnaud-lb) 375 | * Bug fix: Support HHVM (@clue) 376 | * Adjust compatibility to 5.3 (@clue) 377 | 378 | ## 0.4.2 (2014-09-09) 379 | 380 | * Added DuplexStreamInterface 381 | * Stream sets stream resources to non-blocking 382 | * Fixed potential race condition in pipe 383 | 384 | ## 0.4.1 (2014-04-13) 385 | 386 | * Bug fix: v0.3.4 changes merged for v0.4.1 387 | 388 | ## 0.3.4 (2014-03-30) 389 | 390 | * Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream 391 | 392 | ## 0.4.0 (2014-02-02) 393 | 394 | * BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks 395 | * BC break: Update to Evenement 2.0 396 | * Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0 397 | 398 | ## 0.3.3 (2013-07-08) 399 | 400 | * Bug fix: [Stream] Correctly detect closed connections 401 | 402 | ## 0.3.2 (2013-05-10) 403 | 404 | * Bug fix: [Stream] Make sure CompositeStream is closed properly 405 | 406 | ## 0.3.1 (2013-04-21) 407 | 408 | * Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()` 409 | 410 | ## 0.3.0 (2013-04-14) 411 | 412 | * Feature: [Stream] Factory method for BufferedSink 413 | 414 | ## 0.2.6 (2012-12-26) 415 | 416 | * Version bump 417 | 418 | ## 0.2.5 (2012-11-26) 419 | 420 | * Feature: Make BufferedSink trigger progress events on the promise (@jsor) 421 | 422 | ## 0.2.4 (2012-11-18) 423 | 424 | * Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream 425 | * Feature: Added BufferedSink 426 | 427 | ## 0.2.3 (2012-11-14) 428 | 429 | * Version bump 430 | 431 | ## 0.2.2 (2012-10-28) 432 | 433 | * Version bump 434 | 435 | ## 0.2.1 (2012-10-14) 436 | 437 | * Bug fix: Check for EOF in `Buffer::write()` 438 | 439 | ## 0.2.0 (2012-09-10) 440 | 441 | * Version bump 442 | 443 | ## 0.1.1 (2012-07-12) 444 | 445 | * Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8 446 | 447 | ## 0.1.0 (2012-07-11) 448 | 449 | * First tagged release 450 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stream 2 | 3 | [![CI status](https://github.com/reactphp/stream/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/stream/actions) 4 | [![installs on Packagist](https://img.shields.io/packagist/dt/react/stream?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/stream) 5 | 6 | Event-driven readable and writable streams for non-blocking I/O in [ReactPHP](https://reactphp.org/). 7 | 8 | > **Development version:** This branch contains the code for the upcoming v3 9 | > release. For the code of the current stable v1 release, check out the 10 | > [`1.x` branch](https://github.com/reactphp/stream/tree/1.x). 11 | > 12 | > The upcoming v3 release will be the way forward for this package. However, 13 | > we will still actively support v1 for those not yet on the latest version. 14 | > See also [installation instructions](#install) for more details. 15 | 16 | In order to make the [EventLoop](https://github.com/reactphp/event-loop) 17 | easier to use, this component introduces the powerful concept of "streams". 18 | Streams allow you to efficiently process huge amounts of data (such as a multi 19 | Gigabyte file download) in small chunks without having to store everything in 20 | memory at once. 21 | They are very similar to the streams found in PHP itself, 22 | but have an interface more suited for async, non-blocking I/O. 23 | 24 | **Table of contents** 25 | 26 | * [Stream usage](#stream-usage) 27 | * [ReadableStreamInterface](#readablestreaminterface) 28 | * [data event](#data-event) 29 | * [end event](#end-event) 30 | * [error event](#error-event) 31 | * [close event](#close-event) 32 | * [isReadable()](#isreadable) 33 | * [pause()](#pause) 34 | * [resume()](#resume) 35 | * [pipe()](#pipe) 36 | * [close()](#close) 37 | * [WritableStreamInterface](#writablestreaminterface) 38 | * [drain event](#drain-event) 39 | * [pipe event](#pipe-event) 40 | * [error event](#error-event-1) 41 | * [close event](#close-event-1) 42 | * [isWritable()](#iswritable) 43 | * [write()](#write) 44 | * [end()](#end) 45 | * [close()](#close-1) 46 | * [DuplexStreamInterface](#duplexstreaminterface) 47 | * [Creating streams](#creating-streams) 48 | * [ReadableResourceStream](#readableresourcestream) 49 | * [WritableResourceStream](#writableresourcestream) 50 | * [DuplexResourceStream](#duplexresourcestream) 51 | * [ThroughStream](#throughstream) 52 | * [CompositeStream](#compositestream) 53 | * [Usage](#usage) 54 | * [Install](#install) 55 | * [Tests](#tests) 56 | * [License](#license) 57 | * [More](#more) 58 | 59 | ## Stream usage 60 | 61 | ReactPHP uses the concept of "streams" throughout its ecosystem to provide a 62 | consistent higher-level abstraction for processing streams of arbitrary data 63 | contents and size. 64 | While a stream itself is a quite low-level concept, it can be used as a powerful 65 | abstraction to build higher-level components and protocols on top. 66 | 67 | If you're new to this concept, it helps to think of them as a water pipe: 68 | You can consume water from a source or you can produce water and forward (pipe) 69 | it to any destination (sink). 70 | 71 | Similarly, streams can either be 72 | 73 | * readable (such as `STDIN` terminal input) or 74 | * writable (such as `STDOUT` terminal output) or 75 | * duplex (both readable *and* writable, such as a TCP/IP connection) 76 | 77 | Accordingly, this package defines the following three interfaces 78 | 79 | * [`ReadableStreamInterface`](#readablestreaminterface) 80 | * [`WritableStreamInterface`](#writablestreaminterface) 81 | * [`DuplexStreamInterface`](#duplexstreaminterface) 82 | 83 | ### ReadableStreamInterface 84 | 85 | The `ReadableStreamInterface` is responsible for providing an interface for 86 | read-only streams and the readable side of duplex streams. 87 | 88 | Besides defining a few methods, this interface also implements the 89 | `EventEmitterInterface` which allows you to react to certain events. 90 | 91 | The event callback functions MUST be a valid `callable` that obeys strict 92 | parameter definitions and MUST accept event parameters exactly as documented. 93 | The event callback functions MUST NOT throw an `Exception`. 94 | The return value of the event callback functions will be ignored and has no 95 | effect, so for performance reasons you're recommended to not return any 96 | excessive data structures. 97 | 98 | Every implementation of this interface MUST follow these event semantics in 99 | order to be considered a well-behaving stream. 100 | 101 | > Note that higher-level implementations of this interface may choose to 102 | define additional events with dedicated semantics not defined as part of 103 | this low-level stream specification. Conformance with these event semantics 104 | is out of scope for this interface, so you may also have to refer to the 105 | documentation of such a higher-level implementation. 106 | 107 | #### data event 108 | 109 | The `data` event will be emitted whenever some data was read/received 110 | from this source stream. 111 | The event receives a single mixed argument for incoming data. 112 | 113 | ```php 114 | $stream->on('data', function (mixed $data): void { 115 | echo $data; 116 | }); 117 | ``` 118 | 119 | This event MAY be emitted any number of times, which may be zero times if 120 | this stream does not send any data at all. 121 | It SHOULD not be emitted after an `end` or `close` event. 122 | 123 | The given `$data` argument may be of mixed type, but it's usually 124 | recommended it SHOULD be a `string` value or MAY use a type that allows 125 | representation as a `string` for maximum compatibility. 126 | 127 | Many common streams (such as a TCP/IP connection or a file-based stream) 128 | will emit the raw (binary) payload data that is received over the wire as 129 | chunks of `string` values. 130 | 131 | Due to the stream-based nature of this, the sender may send any number 132 | of chunks with varying sizes. There are no guarantees that these chunks 133 | will be received with the exact same framing the sender intended to send. 134 | In other words, many lower-level protocols (such as TCP/IP) transfer the 135 | data in chunks that may be anywhere between single-byte values to several 136 | dozens of kilobytes. You may want to apply a higher-level protocol to 137 | these low-level data chunks in order to achieve proper message framing. 138 | 139 | #### end event 140 | 141 | The `end` event will be emitted once the source stream has successfully 142 | reached the end of the stream (EOF). 143 | 144 | ```php 145 | $stream->on('end', function (): void { 146 | echo 'END'; 147 | }); 148 | ``` 149 | 150 | This event SHOULD be emitted once or never at all, depending on whether 151 | a successful end was detected. 152 | It SHOULD NOT be emitted after a previous `end` or `close` event. 153 | It MUST NOT be emitted if the stream closes due to a non-successful 154 | end, such as after a previous `error` event. 155 | 156 | After the stream is ended, it MUST switch to non-readable mode, 157 | see also `isReadable()`. 158 | 159 | This event will only be emitted if the *end* was reached successfully, 160 | not if the stream was interrupted by an unrecoverable error or explicitly 161 | closed. Not all streams know this concept of a "successful end". 162 | Many use-cases involve detecting when the stream closes (terminates) 163 | instead, in this case you should use the `close` event. 164 | After the stream emits an `end` event, it SHOULD usually be followed by a 165 | `close` event. 166 | 167 | Many common streams (such as a TCP/IP connection or a file-based stream) 168 | will emit this event if either the remote side closes the connection or 169 | a file handle was successfully read until reaching its end (EOF). 170 | 171 | Note that this event should not be confused with the `end()` method. 172 | This event defines a successful end *reading* from a source stream, while 173 | the `end()` method defines *writing* a successful end to a destination 174 | stream. 175 | 176 | #### error event 177 | 178 | The `error` event will be emitted once a fatal error occurs, usually while 179 | trying to read from this stream. 180 | The event receives a single `Exception` argument for the error instance. 181 | 182 | ```php 183 | $server->on('error', function (Exception $e): void { 184 | echo 'Error: ' . $e->getMessage() . PHP_EOL; 185 | }); 186 | ``` 187 | 188 | This event SHOULD be emitted once the stream detects a fatal error, such 189 | as a fatal transmission error or after an unexpected `data` or premature 190 | `end` event. 191 | It SHOULD NOT be emitted after a previous `error`, `end` or `close` event. 192 | It MUST NOT be emitted if this is not a fatal error condition, such as 193 | a temporary network issue that did not cause any data to be lost. 194 | 195 | After the stream errors, it MUST close the stream and SHOULD thus be 196 | followed by a `close` event and then switch to non-readable mode, see 197 | also `close()` and `isReadable()`. 198 | 199 | Many common streams (such as a TCP/IP connection or a file-based stream) 200 | only deal with data transmission and do not make assumption about data 201 | boundaries (such as unexpected `data` or premature `end` events). 202 | In other words, many lower-level protocols (such as TCP/IP) may choose 203 | to only emit this for a fatal transmission error once and will then 204 | close (terminate) the stream in response. 205 | 206 | If this stream is a `DuplexStreamInterface`, you should also notice 207 | how the writable side of the stream also implements an `error` event. 208 | In other words, an error may occur while either reading or writing the 209 | stream which should result in the same error processing. 210 | 211 | #### close event 212 | 213 | The `close` event will be emitted once the stream closes (terminates). 214 | 215 | ```php 216 | $stream->on('close', function (): void { 217 | echo 'CLOSED'; 218 | }); 219 | ``` 220 | 221 | This event SHOULD be emitted once or never at all, depending on whether 222 | the stream ever terminates. 223 | It SHOULD NOT be emitted after a previous `close` event. 224 | 225 | After the stream is closed, it MUST switch to non-readable mode, 226 | see also `isReadable()`. 227 | 228 | Unlike the `end` event, this event SHOULD be emitted whenever the stream 229 | closes, irrespective of whether this happens implicitly due to an 230 | unrecoverable error or explicitly when either side closes the stream. 231 | If you only want to detect a *successful* end, you should use the `end` 232 | event instead. 233 | 234 | Many common streams (such as a TCP/IP connection or a file-based stream) 235 | will likely choose to emit this event after reading a *successful* `end` 236 | event or after a fatal transmission `error` event. 237 | 238 | If this stream is a `DuplexStreamInterface`, you should also notice 239 | how the writable side of the stream also implements a `close` event. 240 | In other words, after receiving this event, the stream MUST switch into 241 | non-writable AND non-readable mode, see also `isWritable()`. 242 | Note that this event should not be confused with the `end` event. 243 | 244 | #### isReadable() 245 | 246 | The `isReadable(): bool` method can be used to 247 | check whether this stream is in a readable state (not closed already). 248 | 249 | This method can be used to check if the stream still accepts incoming 250 | data events or if it is ended or closed already. 251 | Once the stream is non-readable, no further `data` or `end` events SHOULD 252 | be emitted. 253 | 254 | ```php 255 | assert($stream->isReadable() === false); 256 | 257 | $stream->on('data', assertNeverCalled()); 258 | $stream->on('end', assertNeverCalled()); 259 | ``` 260 | 261 | A successfully opened stream always MUST start in readable mode. 262 | 263 | Once the stream ends or closes, it MUST switch to non-readable mode. 264 | This can happen any time, explicitly through `close()` or 265 | implicitly due to a remote close or an unrecoverable transmission error. 266 | Once a stream has switched to non-readable mode, it MUST NOT transition 267 | back to readable mode. 268 | 269 | If this stream is a `DuplexStreamInterface`, you should also notice 270 | how the writable side of the stream also implements an `isWritable()` 271 | method. Unless this is a half-open duplex stream, they SHOULD usually 272 | have the same return value. 273 | 274 | #### pause() 275 | 276 | The `pause(): void` method can be used to 277 | pause reading incoming data events. 278 | 279 | Removes the data source file descriptor from the event loop. This 280 | allows you to throttle incoming data. 281 | 282 | Unless otherwise noted, a successfully opened stream SHOULD NOT start 283 | in paused state. 284 | 285 | Once the stream is paused, no futher `data` or `end` events SHOULD 286 | be emitted. 287 | 288 | ```php 289 | $stream->pause(); 290 | 291 | $stream->on('data', assertShouldNeverCalled()); 292 | $stream->on('end', assertShouldNeverCalled()); 293 | ``` 294 | 295 | This method is advisory-only, though generally not recommended, the 296 | stream MAY continue emitting `data` events. 297 | 298 | You can continue processing events by calling `resume()` again. 299 | 300 | Note that both methods can be called any number of times, in particular 301 | calling `pause()` more than once SHOULD NOT have any effect. 302 | 303 | See also `resume()`. 304 | 305 | #### resume() 306 | 307 | The `resume(): void` method can be used to 308 | resume reading incoming data events. 309 | 310 | Re-attach the data source after a previous `pause()`. 311 | 312 | ```php 313 | $stream->pause(); 314 | 315 | Loop::addTimer(1.0, function () use ($stream): void { 316 | $stream->resume(); 317 | }); 318 | ``` 319 | 320 | Note that both methods can be called any number of times, in particular 321 | calling `resume()` without a prior `pause()` SHOULD NOT have any effect. 322 | 323 | See also `pause()`. 324 | 325 | #### pipe() 326 | 327 | The `pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface` method can be used to 328 | pipe all the data from this readable source into the given writable destination. 329 | 330 | Automatically sends all incoming data to the destination. 331 | Automatically throttles the source based on what the destination can handle. 332 | 333 | ```php 334 | $source->pipe($dest); 335 | ``` 336 | 337 | Similarly, you can also pipe an instance implementing `DuplexStreamInterface` 338 | into itself in order to write back all the data that is received. 339 | This may be a useful feature for a TCP/IP echo service: 340 | 341 | ```php 342 | $connection->pipe($connection); 343 | ``` 344 | 345 | This method returns the destination stream as-is, which can be used to 346 | set up chains of piped streams: 347 | 348 | ```php 349 | $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest); 350 | ``` 351 | 352 | By default, this will call `end()` on the destination stream once the 353 | source stream emits an `end` event. This can be disabled like this: 354 | 355 | ```php 356 | $source->pipe($dest, ['end' => false]); 357 | ``` 358 | 359 | Note that this only applies to the `end` event. 360 | If an `error` or explicit `close` event happens on the source stream, 361 | you'll have to manually close the destination stream: 362 | 363 | ```php 364 | $source->pipe($dest); 365 | $source->on('close', function () use ($dest): void { 366 | $dest->end('BYE!'); 367 | }); 368 | ``` 369 | 370 | If the source stream is not readable (closed state), then this is a NO-OP. 371 | 372 | ```php 373 | $source->close(); 374 | $source->pipe($dest); // NO-OP 375 | ``` 376 | 377 | If the destinantion stream is not writable (closed state), then this will simply 378 | throttle (pause) the source stream: 379 | 380 | ```php 381 | $dest->close(); 382 | $source->pipe($dest); // calls $source->pause() 383 | ``` 384 | 385 | Similarly, if the destination stream is closed while the pipe is still 386 | active, it will also throttle (pause) the source stream: 387 | 388 | ```php 389 | $source->pipe($dest); 390 | $dest->close(); // calls $source->pause() 391 | ``` 392 | 393 | Once the pipe is set up successfully, the destination stream MUST emit 394 | a `pipe` event with this source stream an event argument. 395 | 396 | #### close() 397 | 398 | The `close(): void` method can be used to 399 | close the stream (forcefully). 400 | 401 | This method can be used to (forcefully) close the stream. 402 | 403 | ```php 404 | $stream->close(); 405 | ``` 406 | 407 | Once the stream is closed, it SHOULD emit a `close` event. 408 | Note that this event SHOULD NOT be emitted more than once, in particular 409 | if this method is called multiple times. 410 | 411 | After calling this method, the stream MUST switch into a non-readable 412 | mode, see also `isReadable()`. 413 | This means that no further `data` or `end` events SHOULD be emitted. 414 | 415 | ```php 416 | $stream->close(); 417 | assert($stream->isReadable() === false); 418 | 419 | $stream->on('data', assertNeverCalled()); 420 | $stream->on('end', assertNeverCalled()); 421 | ``` 422 | 423 | If this stream is a `DuplexStreamInterface`, you should also notice 424 | how the writable side of the stream also implements a `close()` method. 425 | In other words, after calling this method, the stream MUST switch into 426 | non-writable AND non-readable mode, see also `isWritable()`. 427 | Note that this method should not be confused with the `end()` method. 428 | 429 | ### WritableStreamInterface 430 | 431 | The `WritableStreamInterface` is responsible for providing an interface for 432 | write-only streams and the writable side of duplex streams. 433 | 434 | Besides defining a few methods, this interface also implements the 435 | `EventEmitterInterface` which allows you to react to certain events. 436 | 437 | The event callback functions MUST be a valid `callable` that obeys strict 438 | parameter definitions and MUST accept event parameters exactly as documented. 439 | The event callback functions MUST NOT throw an `Exception`. 440 | The return value of the event callback functions will be ignored and has no 441 | effect, so for performance reasons you're recommended to not return any 442 | excessive data structures. 443 | 444 | Every implementation of this interface MUST follow these event semantics in 445 | order to be considered a well-behaving stream. 446 | 447 | > Note that higher-level implementations of this interface may choose to 448 | define additional events with dedicated semantics not defined as part of 449 | this low-level stream specification. Conformance with these event semantics 450 | is out of scope for this interface, so you may also have to refer to the 451 | documentation of such a higher-level implementation. 452 | 453 | #### drain event 454 | 455 | The `drain` event will be emitted whenever the write buffer became full 456 | previously and is now ready to accept more data. 457 | 458 | ```php 459 | $stream->on('drain', function () use ($stream): void { 460 | echo 'Stream is now ready to accept more data'; 461 | }); 462 | ``` 463 | 464 | This event SHOULD be emitted once every time the buffer became full 465 | previously and is now ready to accept more data. 466 | In other words, this event MAY be emitted any number of times, which may 467 | be zero times if the buffer never became full in the first place. 468 | This event SHOULD NOT be emitted if the buffer has not become full 469 | previously. 470 | 471 | This event is mostly used internally, see also `write()` for more details. 472 | 473 | #### pipe event 474 | 475 | The `pipe` event will be emitted whenever a readable stream is `pipe()`d 476 | into this stream. 477 | The event receives a single `ReadableStreamInterface` argument for the 478 | source stream. 479 | 480 | ```php 481 | $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream): void { 482 | echo 'Now receiving piped data'; 483 | 484 | // explicitly close target if source emits an error 485 | $source->on('error', function () use ($stream): void { 486 | $stream->close(); 487 | }); 488 | }); 489 | 490 | $source->pipe($stream); 491 | ``` 492 | 493 | This event MUST be emitted once for each readable stream that is 494 | successfully piped into this destination stream. 495 | In other words, this event MAY be emitted any number of times, which may 496 | be zero times if no stream is ever piped into this stream. 497 | This event MUST NOT be emitted if either the source is not readable 498 | (closed already) or this destination is not writable (closed already). 499 | 500 | This event is mostly used internally, see also `pipe()` for more details. 501 | 502 | #### error event 503 | 504 | The `error` event will be emitted once a fatal error occurs, usually while 505 | trying to write to this stream. 506 | The event receives a single `Exception` argument for the error instance. 507 | 508 | ```php 509 | $stream->on('error', function (Exception $e): void { 510 | echo 'Error: ' . $e->getMessage() . PHP_EOL; 511 | }); 512 | ``` 513 | 514 | This event SHOULD be emitted once the stream detects a fatal error, such 515 | as a fatal transmission error. 516 | It SHOULD NOT be emitted after a previous `error` or `close` event. 517 | It MUST NOT be emitted if this is not a fatal error condition, such as 518 | a temporary network issue that did not cause any data to be lost. 519 | 520 | After the stream errors, it MUST close the stream and SHOULD thus be 521 | followed by a `close` event and then switch to non-writable mode, see 522 | also `close()` and `isWritable()`. 523 | 524 | Many common streams (such as a TCP/IP connection or a file-based stream) 525 | only deal with data transmission and may choose 526 | to only emit this for a fatal transmission error once and will then 527 | close (terminate) the stream in response. 528 | 529 | If this stream is a `DuplexStreamInterface`, you should also notice 530 | how the readable side of the stream also implements an `error` event. 531 | In other words, an error may occur while either reading or writing the 532 | stream which should result in the same error processing. 533 | 534 | #### close event 535 | 536 | The `close` event will be emitted once the stream closes (terminates). 537 | 538 | ```php 539 | $stream->on('close', function (): void { 540 | echo 'CLOSED'; 541 | }); 542 | ``` 543 | 544 | This event SHOULD be emitted once or never at all, depending on whether 545 | the stream ever terminates. 546 | It SHOULD NOT be emitted after a previous `close` event. 547 | 548 | After the stream is closed, it MUST switch to non-writable mode, 549 | see also `isWritable()`. 550 | 551 | This event SHOULD be emitted whenever the stream closes, irrespective of 552 | whether this happens implicitly due to an unrecoverable error or 553 | explicitly when either side closes the stream. 554 | 555 | Many common streams (such as a TCP/IP connection or a file-based stream) 556 | will likely choose to emit this event after flushing the buffer from 557 | the `end()` method, after receiving a *successful* `end` event or after 558 | a fatal transmission `error` event. 559 | 560 | If this stream is a `DuplexStreamInterface`, you should also notice 561 | how the readable side of the stream also implements a `close` event. 562 | In other words, after receiving this event, the stream MUST switch into 563 | non-writable AND non-readable mode, see also `isReadable()`. 564 | Note that this event should not be confused with the `end` event. 565 | 566 | #### isWritable() 567 | 568 | The `isWritable(): bool` method can be used to 569 | check whether this stream is in a writable state (not closed already). 570 | 571 | This method can be used to check if the stream still accepts writing 572 | any data or if it is ended or closed already. 573 | Writing any data to a non-writable stream is a NO-OP: 574 | 575 | ```php 576 | assert($stream->isWritable() === false); 577 | 578 | $stream->write('end'); // NO-OP 579 | $stream->end('end'); // NO-OP 580 | ``` 581 | 582 | A successfully opened stream always MUST start in writable mode. 583 | 584 | Once the stream ends or closes, it MUST switch to non-writable mode. 585 | This can happen any time, explicitly through `end()` or `close()` or 586 | implicitly due to a remote close or an unrecoverable transmission error. 587 | Once a stream has switched to non-writable mode, it MUST NOT transition 588 | back to writable mode. 589 | 590 | If this stream is a `DuplexStreamInterface`, you should also notice 591 | how the readable side of the stream also implements an `isReadable()` 592 | method. Unless this is a half-open duplex stream, they SHOULD usually 593 | have the same return value. 594 | 595 | #### write() 596 | 597 | The `write(mixed $data): bool` method can be used to 598 | write some data into the stream. 599 | 600 | A successful write MUST be confirmed with a boolean `true`, which means 601 | that either the data was written (flushed) immediately or is buffered and 602 | scheduled for a future write. Note that this interface gives you no 603 | control over explicitly flushing the buffered data, as finding the 604 | appropriate time for this is beyond the scope of this interface and left 605 | up to the implementation of this interface. 606 | 607 | Many common streams (such as a TCP/IP connection or file-based stream) 608 | may choose to buffer all given data and schedule a future flush by using 609 | an underlying EventLoop to check when the resource is actually writable. 610 | 611 | If a stream cannot handle writing (or flushing) the data, it SHOULD emit 612 | an `error` event and MAY `close()` the stream if it can not recover from 613 | this error. 614 | 615 | If the internal buffer is full after adding `$data`, then `write()` 616 | SHOULD return `false`, indicating that the caller should stop sending 617 | data until the buffer drains. 618 | The stream SHOULD send a `drain` event once the buffer is ready to accept 619 | more data. 620 | 621 | Similarly, if the stream is not writable (already in a closed state) 622 | it MUST NOT process the given `$data` and SHOULD return `false`, 623 | indicating that the caller should stop sending data. 624 | 625 | The given `$data` argument MAY be of mixed type, but it's usually 626 | recommended it SHOULD be a `string` value or MAY use a type that allows 627 | representation as a `string` for maximum compatibility. 628 | 629 | Many common streams (such as a TCP/IP connection or a file-based stream) 630 | will only accept the raw (binary) payload data that is transferred over 631 | the wire as chunks of `string` values. 632 | 633 | Due to the stream-based nature of this, the sender may send any number 634 | of chunks with varying sizes. There are no guarantees that these chunks 635 | will be received with the exact same framing the sender intended to send. 636 | In other words, many lower-level protocols (such as TCP/IP) transfer the 637 | data in chunks that may be anywhere between single-byte values to several 638 | dozens of kilobytes. You may want to apply a higher-level protocol to 639 | these low-level data chunks in order to achieve proper message framing. 640 | 641 | #### end() 642 | 643 | The `end(mixed $data = null): void` method can be used to 644 | successfully end the stream (after optionally sending some final data). 645 | 646 | This method can be used to successfully end the stream, i.e. close 647 | the stream after sending out all data that is currently buffered. 648 | 649 | ```php 650 | $stream->write('hello'); 651 | $stream->write('world'); 652 | $stream->end(); 653 | ``` 654 | 655 | If there's no data currently buffered and nothing to be flushed, then 656 | this method MAY `close()` the stream immediately. 657 | 658 | If there's still data in the buffer that needs to be flushed first, then 659 | this method SHOULD try to write out this data and only then `close()` 660 | the stream. 661 | Once the stream is closed, it SHOULD emit a `close` event. 662 | 663 | Note that this interface gives you no control over explicitly flushing 664 | the buffered data, as finding the appropriate time for this is beyond the 665 | scope of this interface and left up to the implementation of this 666 | interface. 667 | 668 | Many common streams (such as a TCP/IP connection or file-based stream) 669 | may choose to buffer all given data and schedule a future flush by using 670 | an underlying EventLoop to check when the resource is actually writable. 671 | 672 | You can optionally pass some final data that is written to the stream 673 | before ending the stream. If a non-`null` value is given as `$data`, then 674 | this method will behave just like calling `write($data)` before ending 675 | with no data. 676 | 677 | ```php 678 | // shorter version 679 | $stream->end('bye'); 680 | 681 | // same as longer version 682 | $stream->write('bye'); 683 | $stream->end(); 684 | ``` 685 | 686 | After calling this method, the stream MUST switch into a non-writable 687 | mode, see also `isWritable()`. 688 | This means that no further writes are possible, so any additional 689 | `write()` or `end()` calls have no effect. 690 | 691 | ```php 692 | $stream->end(); 693 | assert($stream->isWritable() === false); 694 | 695 | $stream->write('nope'); // NO-OP 696 | $stream->end(); // NO-OP 697 | ``` 698 | 699 | If this stream is a `DuplexStreamInterface`, calling this method SHOULD 700 | also end its readable side, unless the stream supports half-open mode. 701 | In other words, after calling this method, these streams SHOULD switch 702 | into non-writable AND non-readable mode, see also `isReadable()`. 703 | This implies that in this case, the stream SHOULD NOT emit any `data` 704 | or `end` events anymore. 705 | Streams MAY choose to use the `pause()` method logic for this, but 706 | special care may have to be taken to ensure a following call to the 707 | `resume()` method SHOULD NOT continue emitting readable events. 708 | 709 | Note that this method should not be confused with the `close()` method. 710 | 711 | #### close() 712 | 713 | The `close(): void` method can be used to 714 | close the stream (forcefully). 715 | 716 | This method can be used to forcefully close the stream, i.e. close 717 | the stream without waiting for any buffered data to be flushed. 718 | If there's still data in the buffer, this data SHOULD be discarded. 719 | 720 | ```php 721 | $stream->close(); 722 | ``` 723 | 724 | Once the stream is closed, it SHOULD emit a `close` event. 725 | Note that this event SHOULD NOT be emitted more than once, in particular 726 | if this method is called multiple times. 727 | 728 | After calling this method, the stream MUST switch into a non-writable 729 | mode, see also `isWritable()`. 730 | This means that no further writes are possible, so any additional 731 | `write()` or `end()` calls have no effect. 732 | 733 | ```php 734 | $stream->close(); 735 | assert($stream->isWritable() === false); 736 | 737 | $stream->write('nope'); // NO-OP 738 | $stream->end(); // NO-OP 739 | ``` 740 | 741 | Note that this method should not be confused with the `end()` method. 742 | Unlike the `end()` method, this method does not take care of any existing 743 | buffers and simply discards any buffer contents. 744 | Likewise, this method may also be called after calling `end()` on a 745 | stream in order to stop waiting for the stream to flush its final data. 746 | 747 | ```php 748 | $stream->end(); 749 | Loop::addTimer(1.0, function () use ($stream): void { 750 | $stream->close(); 751 | }); 752 | ``` 753 | 754 | If this stream is a `DuplexStreamInterface`, you should also notice 755 | how the readable side of the stream also implements a `close()` method. 756 | In other words, after calling this method, the stream MUST switch into 757 | non-writable AND non-readable mode, see also `isReadable()`. 758 | 759 | ### DuplexStreamInterface 760 | 761 | The `DuplexStreamInterface` is responsible for providing an interface for 762 | duplex streams (both readable and writable). 763 | 764 | It builds on top of the existing interfaces for readable and writable streams 765 | and follows the exact same method and event semantics. 766 | If you're new to this concept, you should look into the 767 | `ReadableStreamInterface` and `WritableStreamInterface` first. 768 | 769 | Besides defining a few methods, this interface also implements the 770 | `EventEmitterInterface` which allows you to react to the same events defined 771 | on the `ReadbleStreamInterface` and `WritableStreamInterface`. 772 | 773 | The event callback functions MUST be a valid `callable` that obeys strict 774 | parameter definitions and MUST accept event parameters exactly as documented. 775 | The event callback functions MUST NOT throw an `Exception`. 776 | The return value of the event callback functions will be ignored and has no 777 | effect, so for performance reasons you're recommended to not return any 778 | excessive data structures. 779 | 780 | Every implementation of this interface MUST follow these event semantics in 781 | order to be considered a well-behaving stream. 782 | 783 | > Note that higher-level implementations of this interface may choose to 784 | define additional events with dedicated semantics not defined as part of 785 | this low-level stream specification. Conformance with these event semantics 786 | is out of scope for this interface, so you may also have to refer to the 787 | documentation of such a higher-level implementation. 788 | 789 | See also [`ReadableStreamInterface`](#readablestreaminterface) and 790 | [`WritableStreamInterface`](#writablestreaminterface) for more details. 791 | 792 | ## Creating streams 793 | 794 | ReactPHP uses the concept of "streams" throughout its ecosystem, so that 795 | many higher-level consumers of this package only deal with 796 | [stream usage](#stream-usage). 797 | This implies that stream instances are most often created within some 798 | higher-level components and many consumers never actually have to deal with 799 | creating a stream instance. 800 | 801 | * Use [react/socket](https://github.com/reactphp/socket) 802 | if you want to accept incoming or establish outgoing plaintext TCP/IP or 803 | secure TLS socket connection streams. 804 | * Use [react/http](https://github.com/reactphp/http) 805 | if you want to receive an incoming HTTP request body streams. 806 | * Use [react/child-process](https://github.com/reactphp/child-process) 807 | if you want to communicate with child processes via process pipes such as 808 | STDIN, STDOUT, STDERR etc. 809 | * Use experimental [react/filesystem](https://github.com/reactphp/filesystem) 810 | if you want to read from / write to the filesystem. 811 | * See also the last chapter for [more real-world applications](#more). 812 | 813 | However, if you are writing a lower-level component or want to create a stream 814 | instance from a stream resource, then the following chapter is for you. 815 | 816 | > Note that the following examples use `fopen()` and `stream_socket_client()` 817 | for illustration purposes only. 818 | These functions SHOULD NOT be used in a truly async program because each call 819 | may take several seconds to complete and would block the EventLoop otherwise. 820 | Additionally, the `fopen()` call will return a file handle on some platforms 821 | which may or may not be supported by all EventLoop implementations. 822 | As an alternative, you may want to use higher-level libraries listed above. 823 | 824 | ### ReadableResourceStream 825 | 826 | The `ReadableResourceStream` is a concrete implementation of the 827 | [`ReadableStreamInterface`](#readablestreaminterface) for PHP's stream resources. 828 | 829 | This can be used to represent a read-only resource like a file stream opened in 830 | readable mode or a stream such as `STDIN`: 831 | 832 | ```php 833 | $stream = new ReadableResourceStream(STDIN); 834 | $stream->on('data', function (string $chunk): void { 835 | echo $chunk; 836 | }); 837 | $stream->on('end', function (): void { 838 | echo 'END'; 839 | }); 840 | ``` 841 | 842 | See also [`ReadableStreamInterface`](#readablestreaminterface) for more details. 843 | 844 | The first parameter given to the constructor MUST be a valid stream resource 845 | that is opened in reading mode (e.g. `fopen()` mode `r`). 846 | Otherwise, it will throw an `InvalidArgumentException`: 847 | 848 | ```php 849 | // throws InvalidArgumentException 850 | $stream = new ReadableResourceStream(false); 851 | ``` 852 | 853 | See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write 854 | stream resources otherwise. 855 | 856 | Internally, this class tries to enable non-blocking mode on the stream resource 857 | which may not be supported for all stream resources. 858 | Most notably, this is not supported by pipes on Windows (STDIN etc.). 859 | If this fails, it will throw a `RuntimeException`: 860 | 861 | ```php 862 | // throws RuntimeException on Windows 863 | $stream = new ReadableResourceStream(STDIN); 864 | ``` 865 | 866 | Once the constructor is called with a valid stream resource, this class will 867 | take care of the underlying stream resource. 868 | You SHOULD only use its public API and SHOULD NOT interfere with the underlying 869 | stream resource manually. 870 | 871 | This class takes an optional `LoopInterface|null $loop` parameter that can be used to 872 | pass the event loop instance to use for this object. You can use a `null` value 873 | here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). 874 | This value SHOULD NOT be given unless you're sure you want to explicitly use a 875 | given event loop instance. 876 | 877 | This class takes an optional `int|null $readChunkSize` parameter that controls 878 | the maximum buffer size in bytes to read at once from the stream. 879 | You can use a `null` value here in order to apply its default value. 880 | This value SHOULD NOT be changed unless you know what you're doing. 881 | This can be a positive number which means that up to X bytes will be read 882 | at once from the underlying stream resource. Note that the actual number 883 | of bytes read may be lower if the stream resource has less than X bytes 884 | currently available. 885 | This can be `-1` which means "read everything available" from the 886 | underlying stream resource. 887 | This should read until the stream resource is not readable anymore 888 | (i.e. underlying buffer drained), note that this does not neccessarily 889 | mean it reached EOF. 890 | 891 | ```php 892 | $stream = new ReadableResourceStream(STDIN, null, 8192); 893 | ``` 894 | 895 | > PHP bug warning: If the PHP process has explicitly been started without a 896 | `STDIN` stream, then trying to read from `STDIN` may return data from 897 | another stream resource. This does not happen if you start this with an empty 898 | stream like `php test.php < /dev/null` instead of `php test.php <&-`. 899 | See [#81](https://github.com/reactphp/stream/issues/81) for more details. 900 | 901 | > Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a 902 | `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). 903 | 904 | ### WritableResourceStream 905 | 906 | The `WritableResourceStream` is a concrete implementation of the 907 | [`WritableStreamInterface`](#writablestreaminterface) for PHP's stream resources. 908 | 909 | This can be used to represent a write-only resource like a file stream opened in 910 | writable mode or a stream such as `STDOUT` or `STDERR`: 911 | 912 | ```php 913 | $stream = new WritableResourceStream(STDOUT); 914 | $stream->write('hello!'); 915 | $stream->end(); 916 | ``` 917 | 918 | See also [`WritableStreamInterface`](#writablestreaminterface) for more details. 919 | 920 | The first parameter given to the constructor MUST be a valid stream resource 921 | that is opened for writing. 922 | Otherwise, it will throw an `InvalidArgumentException`: 923 | 924 | ```php 925 | // throws InvalidArgumentException 926 | $stream = new WritableResourceStream(false); 927 | ``` 928 | 929 | See also the [`DuplexResourceStream`](#readableresourcestream) for read-and-write 930 | stream resources otherwise. 931 | 932 | Internally, this class tries to enable non-blocking mode on the stream resource 933 | which may not be supported for all stream resources. 934 | Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.). 935 | If this fails, it will throw a `RuntimeException`: 936 | 937 | ```php 938 | // throws RuntimeException on Windows 939 | $stream = new WritableResourceStream(STDOUT); 940 | ``` 941 | 942 | Once the constructor is called with a valid stream resource, this class will 943 | take care of the underlying stream resource. 944 | You SHOULD only use its public API and SHOULD NOT interfere with the underlying 945 | stream resource manually. 946 | 947 | Any `write()` calls to this class will not be performed instantly, but will 948 | be performed asynchronously, once the EventLoop reports the stream resource is 949 | ready to accept data. 950 | For this, it uses an in-memory buffer string to collect all outstanding writes. 951 | This buffer has a soft-limit applied which defines how much data it is willing 952 | to accept before the caller SHOULD stop sending further data. 953 | 954 | This class takes an optional `LoopInterface|null $loop` parameter that can be used to 955 | pass the event loop instance to use for this object. You can use a `null` value 956 | here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). 957 | This value SHOULD NOT be given unless you're sure you want to explicitly use a 958 | given event loop instance. 959 | 960 | This class takes an optional `int|null $writeBufferSoftLimit` parameter that controls 961 | this maximum buffer size in bytes. 962 | You can use a `null` value here in order to apply its default value. 963 | This value SHOULD NOT be changed unless you know what you're doing. 964 | 965 | ```php 966 | $stream = new WritableResourceStream(STDOUT, null, 8192); 967 | ``` 968 | 969 | This class takes an optional `int|null $writeChunkSize` parameter that controls 970 | this maximum buffer size in bytes to write at once to the stream. 971 | You can use a `null` value here in order to apply its default value. 972 | This value SHOULD NOT be changed unless you know what you're doing. 973 | This can be a positive number which means that up to X bytes will be written 974 | at once to the underlying stream resource. Note that the actual number 975 | of bytes written may be lower if the stream resource has less than X bytes 976 | currently available. 977 | This can be `-1` which means "write everything available" to the 978 | underlying stream resource. 979 | 980 | ```php 981 | $stream = new WritableResourceStream(STDOUT, null, null, 8192); 982 | ``` 983 | 984 | See also [`write()`](#write) for more details. 985 | 986 | > Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a 987 | `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). 988 | 989 | ### DuplexResourceStream 990 | 991 | The `DuplexResourceStream` is a concrete implementation of the 992 | [`DuplexStreamInterface`](#duplexstreaminterface) for PHP's stream resources. 993 | 994 | This can be used to represent a read-and-write resource like a file stream opened 995 | in read and write mode mode or a stream such as a TCP/IP connection: 996 | 997 | ```php 998 | $conn = stream_socket_client('tcp://google.com:80'); 999 | $stream = new DuplexResourceStream($conn); 1000 | $stream->write('hello!'); 1001 | $stream->end(); 1002 | ``` 1003 | 1004 | See also [`DuplexStreamInterface`](#duplexstreaminterface) for more details. 1005 | 1006 | The first parameter given to the constructor MUST be a valid stream resource 1007 | that is opened for reading *and* writing. 1008 | Otherwise, it will throw an `InvalidArgumentException`: 1009 | 1010 | ```php 1011 | // throws InvalidArgumentException 1012 | $stream = new DuplexResourceStream(false); 1013 | ``` 1014 | 1015 | See also the [`ReadableResourceStream`](#readableresourcestream) for read-only 1016 | and the [`WritableResourceStream`](#writableresourcestream) for write-only 1017 | stream resources otherwise. 1018 | 1019 | Internally, this class tries to enable non-blocking mode on the stream resource 1020 | which may not be supported for all stream resources. 1021 | Most notably, this is not supported by pipes on Windows (STDOUT, STDERR etc.). 1022 | If this fails, it will throw a `RuntimeException`: 1023 | 1024 | ```php 1025 | // throws RuntimeException on Windows 1026 | $stream = new DuplexResourceStream(STDOUT); 1027 | ``` 1028 | 1029 | Once the constructor is called with a valid stream resource, this class will 1030 | take care of the underlying stream resource. 1031 | You SHOULD only use its public API and SHOULD NOT interfere with the underlying 1032 | stream resource manually. 1033 | 1034 | This class takes an optional `LoopInterface|null $loop` parameter that can be used to 1035 | pass the event loop instance to use for this object. You can use a `null` value 1036 | here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). 1037 | This value SHOULD NOT be given unless you're sure you want to explicitly use a 1038 | given event loop instance. 1039 | 1040 | This class takes an optional `int|null $readChunkSize` parameter that controls 1041 | the maximum buffer size in bytes to read at once from the stream. 1042 | You can use a `null` value here in order to apply its default value. 1043 | This value SHOULD NOT be changed unless you know what you're doing. 1044 | This can be a positive number which means that up to X bytes will be read 1045 | at once from the underlying stream resource. Note that the actual number 1046 | of bytes read may be lower if the stream resource has less than X bytes 1047 | currently available. 1048 | This can be `-1` which means "read everything available" from the 1049 | underlying stream resource. 1050 | This should read until the stream resource is not readable anymore 1051 | (i.e. underlying buffer drained), note that this does not neccessarily 1052 | mean it reached EOF. 1053 | 1054 | ```php 1055 | $conn = stream_socket_client('tcp://google.com:80'); 1056 | $stream = new DuplexResourceStream($conn, null, 8192); 1057 | ``` 1058 | 1059 | Any `write()` calls to this class will not be performed instantly, but will 1060 | be performed asynchronously, once the EventLoop reports the stream resource is 1061 | ready to accept data. 1062 | For this, it uses an in-memory buffer string to collect all outstanding writes. 1063 | This buffer has a soft-limit applied which defines how much data it is willing 1064 | to accept before the caller SHOULD stop sending further data. 1065 | 1066 | This class takes another optional `WritableStreamInterface|null $buffer` parameter 1067 | that controls this write behavior of this stream. 1068 | You can use a `null` value here in order to apply its default value. 1069 | This value SHOULD NOT be changed unless you know what you're doing. 1070 | 1071 | If you want to change the write buffer soft limit, you can pass an instance of 1072 | [`WritableResourceStream`](#writableresourcestream) like this: 1073 | 1074 | ```php 1075 | $conn = stream_socket_client('tcp://google.com:80'); 1076 | $buffer = new WritableResourceStream($conn, null, 8192); 1077 | $stream = new DuplexResourceStream($conn, null, null, $buffer); 1078 | ``` 1079 | 1080 | See also [`WritableResourceStream`](#writableresourcestream) for more details. 1081 | 1082 | > Changelog: As of v1.2.0 the `$loop` parameter can be omitted (or skipped with a 1083 | `null` value) to use the [default loop](https://github.com/reactphp/event-loop#loop). 1084 | 1085 | ### ThroughStream 1086 | 1087 | The `ThroughStream` implements the 1088 | [`DuplexStreamInterface`](#duplexstreaminterface) and will simply pass any data 1089 | you write to it through to its readable end. 1090 | 1091 | ```php 1092 | $through = new ThroughStream(); 1093 | $through->on('data', $this->expectCallableOnceWith('hello')); 1094 | 1095 | $through->write('hello'); 1096 | ``` 1097 | 1098 | Similarly, the [`end()` method](#end) will end the stream and emit an 1099 | [`end` event](#end-event) and then [`close()`](#close-1) the stream. 1100 | The [`close()` method](#close-1) will close the stream and emit a 1101 | [`close` event](#close-event). 1102 | Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this: 1103 | 1104 | ```php 1105 | $through = new ThroughStream(); 1106 | $source->pipe($through)->pipe($dest); 1107 | ``` 1108 | 1109 | Optionally, its constructor accepts any callable function which will then be 1110 | used to *filter* any data written to it. This function receives a single data 1111 | argument as passed to the writable side and must return the data as it will be 1112 | passed to its readable end: 1113 | 1114 | ```php 1115 | $through = new ThroughStream('strtoupper'); 1116 | $source->pipe($through)->pipe($dest); 1117 | ``` 1118 | 1119 | Note that this class makes no assumptions about any data types. This can be 1120 | used to convert data, for example for transforming any structured data into 1121 | a newline-delimited JSON (NDJSON) stream like this: 1122 | 1123 | ```php 1124 | $through = new ThroughStream(function (mixed $data): string { 1125 | return json_encode($data) . PHP_EOL; 1126 | }); 1127 | $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); 1128 | 1129 | $through->write([2, true]); 1130 | ``` 1131 | 1132 | The callback function is allowed to throw an `Exception`. In this case, 1133 | the stream will emit an `error` event and then [`close()`](#close-1) the stream. 1134 | 1135 | ```php 1136 | $through = new ThroughStream(function (mixed $data): string { 1137 | if (!is_string($data)) { 1138 | throw new \UnexpectedValueException('Only strings allowed'); 1139 | } 1140 | return $data; 1141 | }); 1142 | $through->on('error', $this->expectCallableOnce())); 1143 | $through->on('close', $this->expectCallableOnce())); 1144 | $through->on('data', $this->expectCallableNever())); 1145 | 1146 | $through->write(2); 1147 | ``` 1148 | 1149 | ### CompositeStream 1150 | 1151 | The `CompositeStream` implements the 1152 | [`DuplexStreamInterface`](#duplexstreaminterface) and can be used to create a 1153 | single duplex stream from two individual streams implementing 1154 | [`ReadableStreamInterface`](#readablestreaminterface) and 1155 | [`WritableStreamInterface`](#writablestreaminterface) respectively. 1156 | 1157 | This is useful for some APIs which may require a single 1158 | [`DuplexStreamInterface`](#duplexstreaminterface) or simply because it's often 1159 | more convenient to work with a single stream instance like this: 1160 | 1161 | ```php 1162 | $stdin = new ReadableResourceStream(STDIN); 1163 | $stdout = new WritableResourceStream(STDOUT); 1164 | 1165 | $stdio = new CompositeStream($stdin, $stdout); 1166 | 1167 | $stdio->on('data', function (string $chunk) use ($stdio): void { 1168 | $stdio->write('You said: ' . $chunk); 1169 | }); 1170 | ``` 1171 | 1172 | This is a well-behaving stream which forwards all stream events from the 1173 | underlying streams and forwards all streams calls to the underlying streams. 1174 | 1175 | If you `write()` to the duplex stream, it will simply `write()` to the 1176 | writable side and return its status. 1177 | 1178 | If you `end()` the duplex stream, it will `end()` the writable side and will 1179 | `pause()` the readable side. 1180 | 1181 | If you `close()` the duplex stream, both input streams will be closed. 1182 | If either of the two input streams emits a `close` event, the duplex stream 1183 | will also close. 1184 | If either of the two input streams is already closed while constructing the 1185 | duplex stream, it will `close()` the other side and return a closed stream. 1186 | 1187 | ## Usage 1188 | 1189 | The following example can be used to pipe the contents of a source file into 1190 | a destination file without having to ever read the whole file into memory: 1191 | 1192 | ```php 1193 | $source = new React\Stream\ReadableResourceStream(fopen('source.txt', 'r')); 1194 | $dest = new React\Stream\WritableResourceStream(fopen('destination.txt', 'w')); 1195 | 1196 | $source->pipe($dest); 1197 | ``` 1198 | 1199 | > Note that this example uses `fopen()` for illustration purposes only. 1200 | This should not be used in a truly async program because the filesystem is 1201 | inherently blocking and each call could potentially take several seconds. 1202 | See also [creating streams](#creating-streams) for more sophisticated 1203 | examples. 1204 | 1205 | ## Install 1206 | 1207 | The recommended way to install this library is [through Composer](https://getcomposer.org). 1208 | [New to Composer?](https://getcomposer.org/doc/00-intro.md) 1209 | 1210 | Once released, this project will follow [SemVer](https://semver.org/). 1211 | At the moment, this will install the latest development version: 1212 | 1213 | ```bash 1214 | composer require react/stream:^3@dev 1215 | ``` 1216 | 1217 | See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. 1218 | 1219 | This project aims to run on any platform and thus does not require any PHP 1220 | extensions and supports running on PHP 7.1 through current PHP 8+. 1221 | It's *highly recommended to use the latest supported PHP version* for this project. 1222 | 1223 | ## Tests 1224 | 1225 | To run the test suite, you first need to clone this repo and then install all 1226 | dependencies [through Composer](https://getcomposer.org): 1227 | 1228 | ```bash 1229 | composer install 1230 | ``` 1231 | 1232 | To run the test suite, go to the project root and run: 1233 | 1234 | ```bash 1235 | vendor/bin/phpunit 1236 | ``` 1237 | 1238 | The test suite also contains a number of functional integration tests that rely 1239 | on a stable internet connection. 1240 | If you do not want to run these, they can simply be skipped like this: 1241 | 1242 | ```bash 1243 | vendor/bin/phpunit --exclude-group internet 1244 | ``` 1245 | 1246 | On top of this, we use PHPStan on max level to ensure type safety across the project: 1247 | 1248 | ```bash 1249 | vendor/bin/phpstan 1250 | ``` 1251 | 1252 | ## License 1253 | 1254 | MIT, see [LICENSE file](LICENSE). 1255 | 1256 | ## More 1257 | 1258 | * See [creating streams](#creating-streams) for more information on how streams 1259 | are created in real-world applications. 1260 | * See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the 1261 | [dependents on Packagist](https://packagist.org/packages/react/stream/dependents) 1262 | for a list of packages that use streams in real-world applications. 1263 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react/stream", 3 | "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", 4 | "keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Christian Lück", 9 | "homepage": "https://clue.engineering/", 10 | "email": "christian@clue.engineering" 11 | }, 12 | { 13 | "name": "Cees-Jan Kiewiet", 14 | "homepage": "https://wyrihaximus.net/", 15 | "email": "reactphp@ceesjankiewiet.nl" 16 | }, 17 | { 18 | "name": "Jan Sorgalla", 19 | "homepage": "https://sorgalla.com/", 20 | "email": "jsorgalla@gmail.com" 21 | }, 22 | { 23 | "name": "Chris Boden", 24 | "homepage": "https://cboden.dev/", 25 | "email": "cboden@gmail.com" 26 | } 27 | ], 28 | "require": { 29 | "php": ">=7.1", 30 | "react/event-loop": "^1.2", 31 | "evenement/evenement": "^3.0 || ^2.0 || ^1.0" 32 | }, 33 | "require-dev": { 34 | "clue/stream-filter": "^1.2", 35 | "phpstan/phpstan": "1.12.19 || 1.4.10", 36 | "phpunit/phpunit": "^9.6 || ^7.5" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "React\\Stream\\": "src/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "React\\Tests\\Stream\\": "tests/" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/CompositeStream.php: -------------------------------------------------------------------------------- 1 | readable = $readable; 21 | $this->writable = $writable; 22 | 23 | if (!$readable->isReadable() || !$writable->isWritable()) { 24 | $this->close(); 25 | return; 26 | } 27 | 28 | Util::forwardEvents($this->readable, $this, ['data', 'end', 'error']); 29 | Util::forwardEvents($this->writable, $this, ['drain', 'error', 'pipe']); 30 | 31 | $this->readable->on('close', [$this, 'close']); 32 | $this->writable->on('close', [$this, 'close']); 33 | } 34 | 35 | public function isReadable(): bool 36 | { 37 | return $this->readable->isReadable(); 38 | } 39 | 40 | public function pause(): void 41 | { 42 | $this->readable->pause(); 43 | } 44 | 45 | public function resume(): void 46 | { 47 | if (!$this->writable->isWritable()) { 48 | return; 49 | } 50 | 51 | $this->readable->resume(); 52 | } 53 | 54 | public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface 55 | { 56 | return Util::pipe($this, $dest, $options); 57 | } 58 | 59 | public function isWritable(): bool 60 | { 61 | return $this->writable->isWritable(); 62 | } 63 | 64 | public function write($data): bool 65 | { 66 | return $this->writable->write($data); 67 | } 68 | 69 | public function end($data = null): void 70 | { 71 | $this->readable->pause(); 72 | $this->writable->end($data); 73 | } 74 | 75 | public function close(): void 76 | { 77 | if ($this->closed) { 78 | return; 79 | } 80 | 81 | $this->closed = true; 82 | $this->readable->close(); 83 | $this->writable->close(); 84 | 85 | $this->emit('close'); 86 | $this->removeAllListeners(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/DuplexResourceStream.php: -------------------------------------------------------------------------------- 1 | stream = $stream; 87 | $this->loop = $loop ?: Loop::get(); 88 | $this->bufferSize = $readChunkSize ?? 65536; 89 | $this->buffer = $buffer; 90 | 91 | $this->buffer->on('error', function (\Exception $error): void { 92 | $this->emit('error', [$error]); 93 | }); 94 | 95 | $this->buffer->on('close', [$this, 'close']); 96 | 97 | $this->buffer->on('drain', function (): void { 98 | $this->emit('drain'); 99 | }); 100 | 101 | $this->resume(); 102 | } 103 | 104 | public function isReadable(): bool 105 | { 106 | return $this->readable; 107 | } 108 | 109 | public function isWritable(): bool 110 | { 111 | return $this->writable; 112 | } 113 | 114 | public function pause(): void 115 | { 116 | if ($this->listening) { 117 | $this->loop->removeReadStream($this->stream); 118 | $this->listening = false; 119 | } 120 | } 121 | 122 | public function resume(): void 123 | { 124 | if (!$this->listening && $this->readable) { 125 | $this->loop->addReadStream($this->stream, [$this, 'handleData']); 126 | $this->listening = true; 127 | } 128 | } 129 | 130 | public function write($data): bool 131 | { 132 | if (!$this->writable) { 133 | return false; 134 | } 135 | 136 | return $this->buffer->write($data); 137 | } 138 | 139 | public function close(): void 140 | { 141 | if (!$this->writable && !$this->closing) { 142 | return; 143 | } 144 | 145 | $this->closing = false; 146 | 147 | $this->readable = false; 148 | $this->writable = false; 149 | 150 | $this->emit('close'); 151 | $this->pause(); 152 | $this->buffer->close(); 153 | $this->removeAllListeners(); 154 | 155 | if (\is_resource($this->stream)) { 156 | \fclose($this->stream); 157 | } 158 | } 159 | 160 | public function end($data = null): void 161 | { 162 | if (!$this->writable) { 163 | return; 164 | } 165 | 166 | $this->closing = true; 167 | 168 | $this->readable = false; 169 | $this->writable = false; 170 | $this->pause(); 171 | 172 | $this->buffer->end($data); 173 | } 174 | 175 | public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface 176 | { 177 | return Util::pipe($this, $dest, $options); 178 | } 179 | 180 | /** 181 | * @internal 182 | * @param resource $stream 183 | */ 184 | public function handleData($stream): void 185 | { 186 | $error = null; 187 | \set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use (&$error): bool { 188 | $error = new \ErrorException( 189 | $errstr, 190 | 0, 191 | $errno, 192 | $errfile, 193 | $errline 194 | ); 195 | return true; 196 | }); 197 | 198 | $data = \stream_get_contents($stream, $this->bufferSize); 199 | 200 | \restore_error_handler(); 201 | 202 | if ($error !== null) { 203 | $this->emit('error', [new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)]); 204 | $this->close(); 205 | return; 206 | } 207 | 208 | if ($data !== '') { 209 | $this->emit('data', [$data]); 210 | } elseif (\feof($this->stream)) { 211 | // no data read => we reached the end and close the stream 212 | $this->emit('end'); 213 | $this->close(); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/DuplexStreamInterface.php: -------------------------------------------------------------------------------- 1 | Note that higher-level implementations of this interface may choose to 29 | * define additional events with dedicated semantics not defined as part of 30 | * this low-level stream specification. Conformance with these event semantics 31 | * is out of scope for this interface, so you may also have to refer to the 32 | * documentation of such a higher-level implementation. 33 | * 34 | * @see ReadableStreamInterface 35 | * @see WritableStreamInterface 36 | */ 37 | interface DuplexStreamInterface extends ReadableStreamInterface, WritableStreamInterface 38 | { 39 | } 40 | -------------------------------------------------------------------------------- /src/ReadableResourceStream.php: -------------------------------------------------------------------------------- 1 | stream = $stream; 77 | $this->loop = $loop ?: Loop::get(); 78 | $this->bufferSize = $readChunkSize ?? 65536; 79 | 80 | $this->resume(); 81 | } 82 | 83 | public function isReadable(): bool 84 | { 85 | return !$this->closed; 86 | } 87 | 88 | public function pause(): void 89 | { 90 | if ($this->listening) { 91 | $this->loop->removeReadStream($this->stream); 92 | $this->listening = false; 93 | } 94 | } 95 | 96 | public function resume(): void 97 | { 98 | if (!$this->listening && !$this->closed) { 99 | $this->loop->addReadStream($this->stream, [$this, 'handleData']); 100 | $this->listening = true; 101 | } 102 | } 103 | 104 | public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface 105 | { 106 | return Util::pipe($this, $dest, $options); 107 | } 108 | 109 | public function close(): void 110 | { 111 | if ($this->closed) { 112 | return; 113 | } 114 | 115 | $this->closed = true; 116 | 117 | $this->emit('close'); 118 | $this->pause(); 119 | $this->removeAllListeners(); 120 | 121 | if (\is_resource($this->stream)) { 122 | \fclose($this->stream); 123 | } 124 | } 125 | 126 | /** @internal */ 127 | public function handleData(): void 128 | { 129 | $error = null; 130 | \set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use (&$error): bool { 131 | $error = new \ErrorException( 132 | $errstr, 133 | 0, 134 | $errno, 135 | $errfile, 136 | $errline 137 | ); 138 | return true; 139 | }); 140 | 141 | $data = \stream_get_contents($this->stream, $this->bufferSize); 142 | 143 | \restore_error_handler(); 144 | 145 | if ($error !== null) { 146 | $this->emit('error', [new \RuntimeException('Unable to read from stream: ' . $error->getMessage(), 0, $error)]); 147 | $this->close(); 148 | return; 149 | } 150 | 151 | if ($data !== '') { 152 | $this->emit('data', [$data]); 153 | } elseif (\feof($this->stream)) { 154 | // no data read => we reached the end and close the stream 155 | $this->emit('end'); 156 | $this->close(); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/ReadableStreamInterface.php: -------------------------------------------------------------------------------- 1 | on('data', function (mixed $data): void { 21 | * echo $data; 22 | * }); 23 | * ``` 24 | * 25 | * This event MAY be emitted any number of times, which may be zero times if 26 | * this stream does not send any data at all. 27 | * It SHOULD not be emitted after an `end` or `close` event. 28 | * 29 | * The given `$data` argument may be of mixed type, but it's usually 30 | * recommended it SHOULD be a `string` value or MAY use a type that allows 31 | * representation as a `string` for maximum compatibility. 32 | * 33 | * Many common streams (such as a TCP/IP connection or a file-based stream) 34 | * will emit the raw (binary) payload data that is received over the wire as 35 | * chunks of `string` values. 36 | * 37 | * Due to the stream-based nature of this, the sender may send any number 38 | * of chunks with varying sizes. There are no guarantees that these chunks 39 | * will be received with the exact same framing the sender intended to send. 40 | * In other words, many lower-level protocols (such as TCP/IP) transfer the 41 | * data in chunks that may be anywhere between single-byte values to several 42 | * dozens of kilobytes. You may want to apply a higher-level protocol to 43 | * these low-level data chunks in order to achieve proper message framing. 44 | * 45 | * end event: 46 | * The `end` event will be emitted once the source stream has successfully 47 | * reached the end of the stream (EOF). 48 | * 49 | * ```php 50 | * $stream->on('end', function (): void { 51 | * echo 'END'; 52 | * }); 53 | * ``` 54 | * 55 | * This event SHOULD be emitted once or never at all, depending on whether 56 | * a successful end was detected. 57 | * It SHOULD NOT be emitted after a previous `end` or `close` event. 58 | * It MUST NOT be emitted if the stream closes due to a non-successful 59 | * end, such as after a previous `error` event. 60 | * 61 | * After the stream is ended, it MUST switch to non-readable mode, 62 | * see also `isReadable()`. 63 | * 64 | * This event will only be emitted if the *end* was reached successfully, 65 | * not if the stream was interrupted by an unrecoverable error or explicitly 66 | * closed. Not all streams know this concept of a "successful end". 67 | * Many use-cases involve detecting when the stream closes (terminates) 68 | * instead, in this case you should use the `close` event. 69 | * After the stream emits an `end` event, it SHOULD usually be followed by a 70 | * `close` event. 71 | * 72 | * Many common streams (such as a TCP/IP connection or a file-based stream) 73 | * will emit this event if either the remote side closes the connection or 74 | * a file handle was successfully read until reaching its end (EOF). 75 | * 76 | * Note that this event should not be confused with the `end()` method. 77 | * This event defines a successful end *reading* from a source stream, while 78 | * the `end()` method defines *writing* a successful end to a destination 79 | * stream. 80 | * 81 | * error event: 82 | * The `error` event will be emitted once a fatal error occurs, usually while 83 | * trying to read from this stream. 84 | * The event receives a single `Exception` argument for the error instance. 85 | * 86 | * ```php 87 | * $stream->on('error', function (Exception $e): void { 88 | * echo 'Error: ' . $e->getMessage() . PHP_EOL; 89 | * }); 90 | * ``` 91 | * 92 | * This event SHOULD be emitted once the stream detects a fatal error, such 93 | * as a fatal transmission error or after an unexpected `data` or premature 94 | * `end` event. 95 | * It SHOULD NOT be emitted after a previous `error`, `end` or `close` event. 96 | * It MUST NOT be emitted if this is not a fatal error condition, such as 97 | * a temporary network issue that did not cause any data to be lost. 98 | * 99 | * After the stream errors, it MUST close the stream and SHOULD thus be 100 | * followed by a `close` event and then switch to non-readable mode, see 101 | * also `close()` and `isReadable()`. 102 | * 103 | * Many common streams (such as a TCP/IP connection or a file-based stream) 104 | * only deal with data transmission and do not make assumption about data 105 | * boundaries (such as unexpected `data` or premature `end` events). 106 | * In other words, many lower-level protocols (such as TCP/IP) may choose 107 | * to only emit this for a fatal transmission error once and will then 108 | * close (terminate) the stream in response. 109 | * 110 | * If this stream is a `DuplexStreamInterface`, you should also notice 111 | * how the writable side of the stream also implements an `error` event. 112 | * In other words, an error may occur while either reading or writing the 113 | * stream which should result in the same error processing. 114 | * 115 | * close event: 116 | * The `close` event will be emitted once the stream closes (terminates). 117 | * 118 | * ```php 119 | * $stream->on('close', function (): void { 120 | * echo 'CLOSED'; 121 | * }); 122 | * ``` 123 | * 124 | * This event SHOULD be emitted once or never at all, depending on whether 125 | * the stream ever terminates. 126 | * It SHOULD NOT be emitted after a previous `close` event. 127 | * 128 | * After the stream is closed, it MUST switch to non-readable mode, 129 | * see also `isReadable()`. 130 | * 131 | * Unlike the `end` event, this event SHOULD be emitted whenever the stream 132 | * closes, irrespective of whether this happens implicitly due to an 133 | * unrecoverable error or explicitly when either side closes the stream. 134 | * If you only want to detect a *successful* end, you should use the `end` 135 | * event instead. 136 | * 137 | * Many common streams (such as a TCP/IP connection or a file-based stream) 138 | * will likely choose to emit this event after reading a *successful* `end` 139 | * event or after a fatal transmission `error` event. 140 | * 141 | * If this stream is a `DuplexStreamInterface`, you should also notice 142 | * how the writable side of the stream also implements a `close` event. 143 | * In other words, after receiving this event, the stream MUST switch into 144 | * non-writable AND non-readable mode, see also `isWritable()`. 145 | * Note that this event should not be confused with the `end` event. 146 | * 147 | * The event callback functions MUST be a valid `callable` that obeys strict 148 | * parameter definitions and MUST accept event parameters exactly as documented. 149 | * The event callback functions MUST NOT throw an `Exception`. 150 | * The return value of the event callback functions will be ignored and has no 151 | * effect, so for performance reasons you're recommended to not return any 152 | * excessive data structures. 153 | * 154 | * Every implementation of this interface MUST follow these event semantics in 155 | * order to be considered a well-behaving stream. 156 | * 157 | * > Note that higher-level implementations of this interface may choose to 158 | * define additional events with dedicated semantics not defined as part of 159 | * this low-level stream specification. Conformance with these event semantics 160 | * is out of scope for this interface, so you may also have to refer to the 161 | * documentation of such a higher-level implementation. 162 | * 163 | * @see EventEmitterInterface 164 | */ 165 | interface ReadableStreamInterface extends EventEmitterInterface 166 | { 167 | /** 168 | * Checks whether this stream is in a readable state (not closed already). 169 | * 170 | * This method can be used to check if the stream still accepts incoming 171 | * data events or if it is ended or closed already. 172 | * Once the stream is non-readable, no further `data` or `end` events SHOULD 173 | * be emitted. 174 | * 175 | * ```php 176 | * assert($stream->isReadable() === false); 177 | * 178 | * $stream->on('data', assertNeverCalled()); 179 | * $stream->on('end', assertNeverCalled()); 180 | * ``` 181 | * 182 | * A successfully opened stream always MUST start in readable mode. 183 | * 184 | * Once the stream ends or closes, it MUST switch to non-readable mode. 185 | * This can happen any time, explicitly through `close()` or 186 | * implicitly due to a remote close or an unrecoverable transmission error. 187 | * Once a stream has switched to non-readable mode, it MUST NOT transition 188 | * back to readable mode. 189 | * 190 | * If this stream is a `DuplexStreamInterface`, you should also notice 191 | * how the writable side of the stream also implements an `isWritable()` 192 | * method. Unless this is a half-open duplex stream, they SHOULD usually 193 | * have the same return value. 194 | * 195 | * @return bool 196 | */ 197 | public function isReadable(): bool; 198 | 199 | /** 200 | * Pauses reading incoming data events. 201 | * 202 | * Removes the data source file descriptor from the event loop. This 203 | * allows you to throttle incoming data. 204 | * 205 | * Unless otherwise noted, a successfully opened stream SHOULD NOT start 206 | * in paused state. 207 | * 208 | * Once the stream is paused, no futher `data` or `end` events SHOULD 209 | * be emitted. 210 | * 211 | * ```php 212 | * $stream->pause(); 213 | * 214 | * $stream->on('data', assertShouldNeverCalled()); 215 | * $stream->on('end', assertShouldNeverCalled()); 216 | * ``` 217 | * 218 | * This method is advisory-only, though generally not recommended, the 219 | * stream MAY continue emitting `data` events. 220 | * 221 | * You can continue processing events by calling `resume()` again. 222 | * 223 | * Note that both methods can be called any number of times, in particular 224 | * calling `pause()` more than once SHOULD NOT have any effect. 225 | * 226 | * @see self::resume() 227 | * @return void 228 | */ 229 | public function pause(): void; 230 | 231 | /** 232 | * Resumes reading incoming data events. 233 | * 234 | * Re-attach the data source after a previous `pause()`. 235 | * 236 | * ```php 237 | * $stream->pause(); 238 | * 239 | * Loop::addTimer(1.0, function () use ($stream): void { 240 | * $stream->resume(); 241 | * }); 242 | * ``` 243 | * 244 | * Note that both methods can be called any number of times, in particular 245 | * calling `resume()` without a prior `pause()` SHOULD NOT have any effect. 246 | * 247 | * @see self::pause() 248 | * @return void 249 | */ 250 | public function resume(): void; 251 | 252 | /** 253 | * Pipes all the data from this readable source into the given writable destination. 254 | * 255 | * Automatically sends all incoming data to the destination. 256 | * Automatically throttles the source based on what the destination can handle. 257 | * 258 | * ```php 259 | * $source->pipe($dest); 260 | * ``` 261 | * 262 | * Similarly, you can also pipe an instance implementing `DuplexStreamInterface` 263 | * into itself in order to write back all the data that is received. 264 | * This may be a useful feature for a TCP/IP echo service: 265 | * 266 | * ```php 267 | * $connection->pipe($connection); 268 | * ``` 269 | * 270 | * This method returns the destination stream as-is, which can be used to 271 | * set up chains of piped streams: 272 | * 273 | * ```php 274 | * $source->pipe($decodeGzip)->pipe($filterBadWords)->pipe($dest); 275 | * ``` 276 | * 277 | * By default, this will call `end()` on the destination stream once the 278 | * source stream emits an `end` event. This can be disabled like this: 279 | * 280 | * ```php 281 | * $source->pipe($dest, ['end' => false]); 282 | * ``` 283 | * 284 | * Note that this only applies to the `end` event. 285 | * If an `error` or explicit `close` event happens on the source stream, 286 | * you'll have to manually close the destination stream: 287 | * 288 | * ```php 289 | * $source->pipe($dest); 290 | * $source->on('close', function () use ($dest): void { 291 | * $dest->end('BYE!'); 292 | * }); 293 | * ``` 294 | * 295 | * If the source stream is not readable (closed state), then this is a NO-OP. 296 | * 297 | * ```php 298 | * $source->close(); 299 | * $source->pipe($dest); // NO-OP 300 | * ``` 301 | * 302 | * If the destinantion stream is not writable (closed state), then this will simply 303 | * throttle (pause) the source stream: 304 | * 305 | * ```php 306 | * $dest->close(); 307 | * $source->pipe($dest); // calls $source->pause() 308 | * ``` 309 | * 310 | * Similarly, if the destination stream is closed while the pipe is still 311 | * active, it will also throttle (pause) the source stream: 312 | * 313 | * ```php 314 | * $source->pipe($dest); 315 | * $dest->close(); // calls $source->pause() 316 | * ``` 317 | * 318 | * Once the pipe is set up successfully, the destination stream MUST emit 319 | * a `pipe` event with this source stream an event argument. 320 | * 321 | * @param WritableStreamInterface $dest 322 | * @param array{end?:bool} $options 323 | * @return WritableStreamInterface $dest stream as-is 324 | */ 325 | public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface; 326 | 327 | /** 328 | * Closes the stream (forcefully). 329 | * 330 | * This method can be used to (forcefully) close the stream. 331 | * 332 | * ```php 333 | * $stream->close(); 334 | * ``` 335 | * 336 | * Once the stream is closed, it SHOULD emit a `close` event. 337 | * Note that this event SHOULD NOT be emitted more than once, in particular 338 | * if this method is called multiple times. 339 | * 340 | * After calling this method, the stream MUST switch into a non-readable 341 | * mode, see also `isReadable()`. 342 | * This means that no further `data` or `end` events SHOULD be emitted. 343 | * 344 | * ```php 345 | * $stream->close(); 346 | * assert($stream->isReadable() === false); 347 | * 348 | * $stream->on('data', assertNeverCalled()); 349 | * $stream->on('end', assertNeverCalled()); 350 | * ``` 351 | * 352 | * If this stream is a `DuplexStreamInterface`, you should also notice 353 | * how the writable side of the stream also implements a `close()` method. 354 | * In other words, after calling this method, the stream MUST switch into 355 | * non-writable AND non-readable mode, see also `isWritable()`. 356 | * Note that this method should not be confused with the `end()` method. 357 | * 358 | * @return void 359 | * @see WritableStreamInterface::close() 360 | */ 361 | public function close(): void; 362 | } 363 | -------------------------------------------------------------------------------- /src/ThroughStream.php: -------------------------------------------------------------------------------- 1 | on('data', $this->expectCallableOnceWith('hello')); 15 | * 16 | * $through->write('hello'); 17 | * ``` 18 | * 19 | * Similarly, the [`end()` method](#end) will end the stream and emit an 20 | * [`end` event](#end-event) and then [`close()`](#close-1) the stream. 21 | * The [`close()` method](#close-1) will close the stream and emit a 22 | * [`close` event](#close-event). 23 | * Accordingly, this is can also be used in a [`pipe()`](#pipe) context like this: 24 | * 25 | * ```php 26 | * $through = new ThroughStream(); 27 | * $source->pipe($through)->pipe($dest); 28 | * ``` 29 | * 30 | * Optionally, its constructor accepts any callable function which will then be 31 | * used to *filter* any data written to it. This function receives a single data 32 | * argument as passed to the writable side and must return the data as it will be 33 | * passed to its readable end: 34 | * 35 | * ```php 36 | * $through = new ThroughStream('strtoupper'); 37 | * $source->pipe($through)->pipe($dest); 38 | * ``` 39 | * 40 | * Note that this class makes no assumptions about any data types. This can be 41 | * used to convert data, for example for transforming any structured data into 42 | * a newline-delimited JSON (NDJSON) stream like this: 43 | * 44 | * ```php 45 | * $through = new ThroughStream(function (mixed $data): string { 46 | * return json_encode($data) . PHP_EOL; 47 | * }); 48 | * $through->on('data', $this->expectCallableOnceWith("[2, true]\n")); 49 | * 50 | * $through->write([2, true]); 51 | * ``` 52 | * 53 | * The callback function is allowed to throw an `Exception`. In this case, 54 | * the stream will emit an `error` event and then [`close()`](#close-1) the stream. 55 | * 56 | * ```php 57 | * $through = new ThroughStream(function (mixed $data): string { 58 | * if (!is_string($data)) { 59 | * throw new \UnexpectedValueException('Only strings allowed'); 60 | * } 61 | * return $data; 62 | * }); 63 | * $through->on('error', $this->expectCallableOnce())); 64 | * $through->on('close', $this->expectCallableOnce())); 65 | * $through->on('data', $this->expectCallableNever())); 66 | * 67 | * $through->write(2); 68 | * ``` 69 | * 70 | * @see WritableStreamInterface::write() 71 | * @see WritableStreamInterface::end() 72 | * @see DuplexStreamInterface::close() 73 | * @see WritableStreamInterface::pipe() 74 | */ 75 | final class ThroughStream extends EventEmitter implements DuplexStreamInterface 76 | { 77 | /** @var bool */ 78 | private $readable = true; 79 | 80 | /** @var bool */ 81 | private $writable = true; 82 | 83 | /** @var bool */ 84 | private $closed = false; 85 | 86 | /** @var bool */ 87 | private $paused = false; 88 | 89 | /** @var bool */ 90 | private $drain = false; 91 | 92 | /** @var ?callable */ 93 | private $callback; 94 | 95 | /** 96 | * @param ?callable $callback 97 | */ 98 | public function __construct(?callable $callback = null) 99 | { 100 | $this->callback = $callback; 101 | } 102 | 103 | public function pause(): void 104 | { 105 | // only allow pause if still readable, false otherwise 106 | $this->paused = $this->readable; 107 | } 108 | 109 | public function resume(): void 110 | { 111 | $this->paused = false; 112 | 113 | // emit drain event if previous write was paused (throttled) 114 | if ($this->drain) { 115 | $this->drain = false; 116 | $this->emit('drain'); 117 | } 118 | } 119 | 120 | public function pipe(WritableStreamInterface $dest, array $options = []): WritableStreamInterface 121 | { 122 | return Util::pipe($this, $dest, $options); 123 | } 124 | 125 | public function isReadable(): bool 126 | { 127 | return $this->readable; 128 | } 129 | 130 | public function isWritable(): bool 131 | { 132 | return $this->writable; 133 | } 134 | 135 | public function write($data): bool 136 | { 137 | if (!$this->writable) { 138 | return false; 139 | } 140 | 141 | if ($this->callback !== null) { 142 | try { 143 | $data = \call_user_func($this->callback, $data); 144 | } catch (\Exception $e) { 145 | $this->emit('error', [$e]); 146 | $this->close(); 147 | 148 | return false; 149 | } 150 | } 151 | 152 | $this->emit('data', [$data]); 153 | 154 | // emit drain event on next resume if currently paused (throttled) 155 | if ($this->paused) { 156 | $this->drain = true; 157 | } 158 | 159 | // continue writing if still writable and not paused (throttled), false otherwise 160 | // @phpstan-ignore-next-line (may be false when write() causes stream to close) 161 | return $this->writable && !$this->paused; 162 | } 163 | 164 | public function end($data = null): void 165 | { 166 | if (!$this->writable) { 167 | return; 168 | } 169 | 170 | if (null !== $data) { 171 | $this->write($data); 172 | 173 | // return if write() already caused the stream to close 174 | // @phpstan-ignore-next-line (may be false when write() causes stream to close) 175 | if (!$this->writable) { 176 | return; 177 | } 178 | } 179 | 180 | $this->readable = false; 181 | $this->writable = false; 182 | $this->paused = false; 183 | $this->drain = false; 184 | 185 | $this->emit('end'); 186 | $this->close(); 187 | } 188 | 189 | public function close(): void 190 | { 191 | if ($this->closed) { 192 | return; 193 | } 194 | 195 | $this->readable = false; 196 | $this->writable = false; 197 | $this->paused = false; 198 | $this->drain = false; 199 | 200 | $this->closed = true; 201 | $this->callback = null; 202 | 203 | $this->emit('close'); 204 | $this->removeAllListeners(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/Util.php: -------------------------------------------------------------------------------- 1 | NO-OP 19 | if (!$source->isReadable()) { 20 | return $dest; 21 | } 22 | 23 | // destination not writable => just pause() source 24 | if (!$dest->isWritable()) { 25 | $source->pause(); 26 | 27 | return $dest; 28 | } 29 | 30 | $dest->emit('pipe', [$source]); 31 | 32 | // forward all source data events as $dest->write() 33 | $source->on('data', $dataer = function ($data) use ($source, $dest): void { 34 | $feedMore = $dest->write($data); 35 | 36 | if (false === $feedMore) { 37 | $source->pause(); 38 | } 39 | }); 40 | $dest->on('close', function () use ($source, $dataer): void { 41 | $source->removeListener('data', $dataer); 42 | $source->pause(); 43 | }); 44 | 45 | // forward destination drain as $source->resume() 46 | $dest->on('drain', $drainer = function () use ($source): void { 47 | $source->resume(); 48 | }); 49 | $source->on('close', function () use ($dest, $drainer): void { 50 | $dest->removeListener('drain', $drainer); 51 | }); 52 | 53 | // forward end event from source as $dest->end() 54 | $end = isset($options['end']) ? $options['end'] : true; 55 | if ($end) { 56 | $source->on('end', $ender = function () use ($dest): void { 57 | $dest->end(); 58 | }); 59 | $dest->on('close', function () use ($source, $ender): void { 60 | $source->removeListener('end', $ender); 61 | }); 62 | } 63 | 64 | return $dest; 65 | } 66 | 67 | /** 68 | * @param ReadableStreamInterface|WritableStreamInterface $source 69 | * @param ReadableStreamInterface|WritableStreamInterface $target 70 | * @param string[] $events 71 | * @return void 72 | */ 73 | public static function forwardEvents($source, $target, array $events): void 74 | { 75 | foreach ($events as $event) { 76 | $source->on($event, function () use ($event, $target): void { 77 | $target->emit($event, \func_get_args()); 78 | }); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/WritableResourceStream.php: -------------------------------------------------------------------------------- 1 | stream = $stream; 60 | $this->loop = $loop ?: Loop::get(); 61 | $this->softLimit = $writeBufferSoftLimit ?? 65536; 62 | $this->writeChunkSize = $writeChunkSize ?? -1; 63 | } 64 | 65 | public function isWritable(): bool 66 | { 67 | return $this->writable; 68 | } 69 | 70 | public function write($data): bool 71 | { 72 | if (!$this->writable) { 73 | return false; 74 | } 75 | 76 | $this->data .= $data; 77 | 78 | if (!$this->listening && $this->data !== '') { 79 | $this->listening = true; 80 | 81 | $this->loop->addWriteStream($this->stream, [$this, 'handleWrite']); 82 | } 83 | 84 | return !isset($this->data[$this->softLimit - 1]); 85 | } 86 | 87 | public function end($data = null): void 88 | { 89 | if (null !== $data) { 90 | $this->write($data); 91 | } 92 | 93 | $this->writable = false; 94 | 95 | // close immediately if buffer is already empty 96 | // otherwise wait for buffer to flush first 97 | if ($this->data === '') { 98 | $this->close(); 99 | } 100 | } 101 | 102 | public function close(): void 103 | { 104 | if ($this->closed) { 105 | return; 106 | } 107 | 108 | if ($this->listening) { 109 | $this->listening = false; 110 | $this->loop->removeWriteStream($this->stream); 111 | } 112 | 113 | $this->closed = true; 114 | $this->writable = false; 115 | $this->data = ''; 116 | 117 | $this->emit('close'); 118 | $this->removeAllListeners(); 119 | 120 | if (\is_resource($this->stream)) { 121 | \fclose($this->stream); 122 | } 123 | } 124 | 125 | /** @internal */ 126 | public function handleWrite(): void 127 | { 128 | $error = null; 129 | \set_error_handler(function (int $_, string $errstr) use (&$error): bool { 130 | $error = $errstr; 131 | return true; 132 | }); 133 | 134 | if ($this->writeChunkSize === -1) { 135 | $sent = \fwrite($this->stream, $this->data); 136 | } else { 137 | \assert($this->writeChunkSize >= -1); 138 | $sent = \fwrite($this->stream, $this->data, $this->writeChunkSize); 139 | } 140 | 141 | \restore_error_handler(); 142 | 143 | // Only report errors if *nothing* could be sent and an error has been raised. 144 | // Ignore non-fatal warnings if *some* data could be sent. 145 | // Any hard (permanent) error will fail to send any data at all. 146 | // Sending excessive amounts of data will only flush *some* data and then 147 | // report a temporary error (EAGAIN) which we do not raise here in order 148 | // to keep the stream open for further tries to write. 149 | // Should this turn out to be a permanent error later, it will eventually 150 | // send *nothing* and we can detect this. 151 | if (($sent === 0 || $sent === false) && $error !== null) { 152 | $this->emit('error', [new \RuntimeException('Unable to write to stream: ' . $error)]); 153 | $this->close(); 154 | 155 | return; 156 | } 157 | 158 | $exceeded = isset($this->data[$this->softLimit - 1]); 159 | $this->data = (string) \substr($this->data, (int) $sent); 160 | 161 | // buffer has been above limit and is now below limit 162 | if ($exceeded && !isset($this->data[$this->softLimit - 1])) { 163 | $this->emit('drain'); 164 | } 165 | 166 | // buffer is now completely empty => stop trying to write 167 | if ($this->data === '') { 168 | // stop waiting for resource to be writable 169 | if ($this->listening) { 170 | $this->loop->removeWriteStream($this->stream); 171 | $this->listening = false; 172 | } 173 | 174 | // buffer is end()ing and now completely empty => close buffer 175 | if (!$this->writable) { 176 | $this->close(); 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/WritableStreamInterface.php: -------------------------------------------------------------------------------- 1 | on('drain', function () use ($stream): void { 20 | * echo 'Stream is now ready to accept more data'; 21 | * }); 22 | * ``` 23 | * 24 | * This event SHOULD be emitted once every time the buffer became full 25 | * previously and is now ready to accept more data. 26 | * In other words, this event MAY be emitted any number of times, which may 27 | * be zero times if the buffer never became full in the first place. 28 | * This event SHOULD NOT be emitted if the buffer has not become full 29 | * previously. 30 | * 31 | * This event is mostly used internally, see also `write()` for more details. 32 | * 33 | * pipe event: 34 | * The `pipe` event will be emitted whenever a readable stream is `pipe()`d 35 | * into this stream. 36 | * The event receives a single `ReadableStreamInterface` argument for the 37 | * source stream. 38 | * 39 | * ```php 40 | * $stream->on('pipe', function (ReadableStreamInterface $source) use ($stream): void { 41 | * echo 'Now receiving piped data'; 42 | * 43 | * // explicitly close target if source emits an error 44 | * $source->on('error', function () use ($stream): void { 45 | * $stream->close(); 46 | * }); 47 | * }); 48 | * 49 | * $source->pipe($stream); 50 | * ``` 51 | * 52 | * This event MUST be emitted once for each readable stream that is 53 | * successfully piped into this destination stream. 54 | * In other words, this event MAY be emitted any number of times, which may 55 | * be zero times if no stream is ever piped into this stream. 56 | * This event MUST NOT be emitted if either the source is not readable 57 | * (closed already) or this destination is not writable (closed already). 58 | * 59 | * This event is mostly used internally, see also `pipe()` for more details. 60 | * 61 | * error event: 62 | * The `error` event will be emitted once a fatal error occurs, usually while 63 | * trying to write to this stream. 64 | * The event receives a single `Exception` argument for the error instance. 65 | * 66 | * ```php 67 | * $stream->on('error', function (Exception $e): void { 68 | * echo 'Error: ' . $e->getMessage() . PHP_EOL; 69 | * }); 70 | * ``` 71 | * 72 | * This event SHOULD be emitted once the stream detects a fatal error, such 73 | * as a fatal transmission error. 74 | * It SHOULD NOT be emitted after a previous `error` or `close` event. 75 | * It MUST NOT be emitted if this is not a fatal error condition, such as 76 | * a temporary network issue that did not cause any data to be lost. 77 | * 78 | * After the stream errors, it MUST close the stream and SHOULD thus be 79 | * followed by a `close` event and then switch to non-writable mode, see 80 | * also `close()` and `isWritable()`. 81 | * 82 | * Many common streams (such as a TCP/IP connection or a file-based stream) 83 | * only deal with data transmission and may choose 84 | * to only emit this for a fatal transmission error once and will then 85 | * close (terminate) the stream in response. 86 | * 87 | * If this stream is a `DuplexStreamInterface`, you should also notice 88 | * how the readable side of the stream also implements an `error` event. 89 | * In other words, an error may occur while either reading or writing the 90 | * stream which should result in the same error processing. 91 | * 92 | * close event: 93 | * The `close` event will be emitted once the stream closes (terminates). 94 | * 95 | * ```php 96 | * $stream->on('close', function (): void { 97 | * echo 'CLOSED'; 98 | * }); 99 | * ``` 100 | * 101 | * This event SHOULD be emitted once or never at all, depending on whether 102 | * the stream ever terminates. 103 | * It SHOULD NOT be emitted after a previous `close` event. 104 | * 105 | * After the stream is closed, it MUST switch to non-writable mode, 106 | * see also `isWritable()`. 107 | * 108 | * This event SHOULD be emitted whenever the stream closes, irrespective of 109 | * whether this happens implicitly due to an unrecoverable error or 110 | * explicitly when either side closes the stream. 111 | * 112 | * Many common streams (such as a TCP/IP connection or a file-based stream) 113 | * will likely choose to emit this event after flushing the buffer from 114 | * the `end()` method, after receiving a *successful* `end` event or after 115 | * a fatal transmission `error` event. 116 | * 117 | * If this stream is a `DuplexStreamInterface`, you should also notice 118 | * how the readable side of the stream also implements a `close` event. 119 | * In other words, after receiving this event, the stream MUST switch into 120 | * non-writable AND non-readable mode, see also `isReadable()`. 121 | * Note that this event should not be confused with the `end` event. 122 | * 123 | * The event callback functions MUST be a valid `callable` that obeys strict 124 | * parameter definitions and MUST accept event parameters exactly as documented. 125 | * The event callback functions MUST NOT throw an `Exception`. 126 | * The return value of the event callback functions will be ignored and has no 127 | * effect, so for performance reasons you're recommended to not return any 128 | * excessive data structures. 129 | * 130 | * Every implementation of this interface MUST follow these event semantics in 131 | * order to be considered a well-behaving stream. 132 | * 133 | * > Note that higher-level implementations of this interface may choose to 134 | * define additional events with dedicated semantics not defined as part of 135 | * this low-level stream specification. Conformance with these event semantics 136 | * is out of scope for this interface, so you may also have to refer to the 137 | * documentation of such a higher-level implementation. 138 | * 139 | * @see EventEmitterInterface 140 | * @see DuplexStreamInterface 141 | */ 142 | interface WritableStreamInterface extends EventEmitterInterface 143 | { 144 | /** 145 | * Checks whether this stream is in a writable state (not closed already). 146 | * 147 | * This method can be used to check if the stream still accepts writing 148 | * any data or if it is ended or closed already. 149 | * Writing any data to a non-writable stream is a NO-OP: 150 | * 151 | * ```php 152 | * assert($stream->isWritable() === false); 153 | * 154 | * $stream->write('end'); // NO-OP 155 | * $stream->end('end'); // NO-OP 156 | * ``` 157 | * 158 | * A successfully opened stream always MUST start in writable mode. 159 | * 160 | * Once the stream ends or closes, it MUST switch to non-writable mode. 161 | * This can happen any time, explicitly through `end()` or `close()` or 162 | * implicitly due to a remote close or an unrecoverable transmission error. 163 | * Once a stream has switched to non-writable mode, it MUST NOT transition 164 | * back to writable mode. 165 | * 166 | * If this stream is a `DuplexStreamInterface`, you should also notice 167 | * how the readable side of the stream also implements an `isReadable()` 168 | * method. Unless this is a half-open duplex stream, they SHOULD usually 169 | * have the same return value. 170 | * 171 | * @return bool 172 | */ 173 | public function isWritable(): bool; 174 | 175 | /** 176 | * Write some data into the stream. 177 | * 178 | * A successful write MUST be confirmed with a boolean `true`, which means 179 | * that either the data was written (flushed) immediately or is buffered and 180 | * scheduled for a future write. Note that this interface gives you no 181 | * control over explicitly flushing the buffered data, as finding the 182 | * appropriate time for this is beyond the scope of this interface and left 183 | * up to the implementation of this interface. 184 | * 185 | * Many common streams (such as a TCP/IP connection or file-based stream) 186 | * may choose to buffer all given data and schedule a future flush by using 187 | * an underlying EventLoop to check when the resource is actually writable. 188 | * 189 | * If a stream cannot handle writing (or flushing) the data, it SHOULD emit 190 | * an `error` event and MAY `close()` the stream if it can not recover from 191 | * this error. 192 | * 193 | * If the internal buffer is full after adding `$data`, then `write()` 194 | * SHOULD return `false`, indicating that the caller should stop sending 195 | * data until the buffer drains. 196 | * The stream SHOULD send a `drain` event once the buffer is ready to accept 197 | * more data. 198 | * 199 | * Similarly, if the stream is not writable (already in a closed state) 200 | * it MUST NOT process the given `$data` and SHOULD return `false`, 201 | * indicating that the caller should stop sending data. 202 | * 203 | * The given `$data` argument MAY be of mixed type, but it's usually 204 | * recommended it SHOULD be a `string` value or MAY use a type that allows 205 | * representation as a `string` for maximum compatibility. 206 | * 207 | * Many common streams (such as a TCP/IP connection or a file-based stream) 208 | * will only accept the raw (binary) payload data that is transferred over 209 | * the wire as chunks of `string` values. 210 | * 211 | * Due to the stream-based nature of this, the sender may send any number 212 | * of chunks with varying sizes. There are no guarantees that these chunks 213 | * will be received with the exact same framing the sender intended to send. 214 | * In other words, many lower-level protocols (such as TCP/IP) transfer the 215 | * data in chunks that may be anywhere between single-byte values to several 216 | * dozens of kilobytes. You may want to apply a higher-level protocol to 217 | * these low-level data chunks in order to achieve proper message framing. 218 | * 219 | * @param mixed|string $data 220 | * @return bool 221 | */ 222 | public function write($data): bool; 223 | 224 | /** 225 | * Successfully ends the stream (after optionally sending some final data). 226 | * 227 | * This method can be used to successfully end the stream, i.e. close 228 | * the stream after sending out all data that is currently buffered. 229 | * 230 | * ```php 231 | * $stream->write('hello'); 232 | * $stream->write('world'); 233 | * $stream->end(); 234 | * ``` 235 | * 236 | * If there's no data currently buffered and nothing to be flushed, then 237 | * this method MAY `close()` the stream immediately. 238 | * 239 | * If there's still data in the buffer that needs to be flushed first, then 240 | * this method SHOULD try to write out this data and only then `close()` 241 | * the stream. 242 | * Once the stream is closed, it SHOULD emit a `close` event. 243 | * 244 | * Note that this interface gives you no control over explicitly flushing 245 | * the buffered data, as finding the appropriate time for this is beyond the 246 | * scope of this interface and left up to the implementation of this 247 | * interface. 248 | * 249 | * Many common streams (such as a TCP/IP connection or file-based stream) 250 | * may choose to buffer all given data and schedule a future flush by using 251 | * an underlying EventLoop to check when the resource is actually writable. 252 | * 253 | * You can optionally pass some final data that is written to the stream 254 | * before ending the stream. If a non-`null` value is given as `$data`, then 255 | * this method will behave just like calling `write($data)` before ending 256 | * with no data. 257 | * 258 | * ```php 259 | * // shorter version 260 | * $stream->end('bye'); 261 | * 262 | * // same as longer version 263 | * $stream->write('bye'); 264 | * $stream->end(); 265 | * ``` 266 | * 267 | * After calling this method, the stream MUST switch into a non-writable 268 | * mode, see also `isWritable()`. 269 | * This means that no further writes are possible, so any additional 270 | * `write()` or `end()` calls have no effect. 271 | * 272 | * ```php 273 | * $stream->end(); 274 | * assert($stream->isWritable() === false); 275 | * 276 | * $stream->write('nope'); // NO-OP 277 | * $stream->end(); // NO-OP 278 | * ``` 279 | * 280 | * If this stream is a `DuplexStreamInterface`, calling this method SHOULD 281 | * also end its readable side, unless the stream supports half-open mode. 282 | * In other words, after calling this method, these streams SHOULD switch 283 | * into non-writable AND non-readable mode, see also `isReadable()`. 284 | * This implies that in this case, the stream SHOULD NOT emit any `data` 285 | * or `end` events anymore. 286 | * Streams MAY choose to use the `pause()` method logic for this, but 287 | * special care may have to be taken to ensure a following call to the 288 | * `resume()` method SHOULD NOT continue emitting readable events. 289 | * 290 | * Note that this method should not be confused with the `close()` method. 291 | * 292 | * @param mixed|string|null $data 293 | * @return void 294 | */ 295 | public function end($data = null): void; 296 | 297 | /** 298 | * Closes the stream (forcefully). 299 | * 300 | * This method can be used to forcefully close the stream, i.e. close 301 | * the stream without waiting for any buffered data to be flushed. 302 | * If there's still data in the buffer, this data SHOULD be discarded. 303 | * 304 | * ```php 305 | * $stream->close(); 306 | * ``` 307 | * 308 | * Once the stream is closed, it SHOULD emit a `close` event. 309 | * Note that this event SHOULD NOT be emitted more than once, in particular 310 | * if this method is called multiple times. 311 | * 312 | * After calling this method, the stream MUST switch into a non-writable 313 | * mode, see also `isWritable()`. 314 | * This means that no further writes are possible, so any additional 315 | * `write()` or `end()` calls have no effect. 316 | * 317 | * ```php 318 | * $stream->close(); 319 | * assert($stream->isWritable() === false); 320 | * 321 | * $stream->write('nope'); // NO-OP 322 | * $stream->end(); // NO-OP 323 | * ``` 324 | * 325 | * Note that this method should not be confused with the `end()` method. 326 | * Unlike the `end()` method, this method does not take care of any existing 327 | * buffers and simply discards any buffer contents. 328 | * Likewise, this method may also be called after calling `end()` on a 329 | * stream in order to stop waiting for the stream to flush its final data. 330 | * 331 | * ```php 332 | * $stream->end(); 333 | * Loop::addTimer(1.0, function () use ($stream): void { 334 | * $stream->close(); 335 | * }); 336 | * ``` 337 | * 338 | * If this stream is a `DuplexStreamInterface`, you should also notice 339 | * how the readable side of the stream also implements a `close()` method. 340 | * In other words, after calling this method, the stream MUST switch into 341 | * non-writable AND non-readable mode, see also `isReadable()`. 342 | * 343 | * @return void 344 | * @see ReadableStreamInterface::close() 345 | */ 346 | public function close(): void; 347 | } 348 | --------------------------------------------------------------------------------