diff --git a/.php_cs b/.php_cs index fe026e5..d6da9fb 100644 --- a/.php_cs +++ b/.php_cs @@ -1,16 +1,66 @@ in(__DIR__); + ->in(__DIR__) + ->exclude( + array( + 'artifacts', + 'assets', + 'bower_components', + 'build', + 'node_modules', + 'src-web', + 'src-generated', + 'vendor', + ) + ); return Symfony\CS\Config\Config::create() - ->fixers(array( - '-concat_without_spaces', - '-empty_return', - '-new_with_braces', - 'align_double_arrow', - 'align_equals', - 'ordered_use', - 'short_array_syntax', - )) + ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) + ->fixers( + array( + // symfony + 'blankline_after_open_tag', + 'duplicate_semicolon', + 'extra_empty_lines', + 'include', + 'join_function', + 'list_commas', + 'multiline_array_trailing_comma', + 'namespace_no_leading_whitespace', + 'new_with_braces', + 'no_blank_lines_after_class_opening', + 'no_empty_lines_after_phpdocs', + 'object_operator', + 'operators_spaces', + 'phpdoc_indent', + 'phpdoc_params', + 'phpdoc_short_description', + 'phpdoc_to_comment', + 'phpdoc_trim', + 'phpdoc_type_to_var', + 'phpdoc_var_without_name', + 'pre_increment', + 'remove_leading_slash_use', + 'remove_lines_between_uses', + 'return', + 'self_accessor', + 'single_array_no_trailing_comma', + 'single_blank_line_before_namespace', + 'single_quote', + 'spaces_before_semicolon', + 'spaces_cast', + 'standardize_not_equal', + 'ternary_spaces', + 'trim_array_spaces', + 'unary_operators_spaces', + 'unused_use', + 'whitespacy_lines', + + // contrib + 'concat_with_spaces', + 'multiline_spaces_before_semicolon', + 'ordered_use', + ) + ) ->finder($finder); diff --git a/.travis.env b/.travis.env index c55602f..9d6d70a 100644 --- a/.travis.env +++ b/.travis.env @@ -1 +1 @@ -eNkdkCDTTQDrKETgHn9u2Kbx1vvZFDkMxFgmwl6keoe9X4XtEsw/LSgHlE4dEscGVwEidzMJuH2ZapgB9KbqSJImKmtvJYjF1700zrWdXSqy5NeLiexwBwoQbkrnO27AwqpRnEFwJrBlEggsBcUYj8s9hRYlDKb3juEEKgdafxc= \ No newline at end of file +g0uexghpfxjO1pVYCBqj/P957zCZQT1v5naWgzDtu5n2Sh1WoW00K4+jOYJ4bbIm+1O8wbzzlyOuYZMMz0xXLVzuOdoMwMhfcnI+pE/dpa4J/vs1RFqoLh4HuNx9oPErNODdmWIkeC1SpcuBeYZfYxEL6UJIWbZZq3a9BIslx88= \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index d984493..abe6853 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ matrix: env: global: - ARCHER_PUBLISH_VERSION=5.6 - - secure: "eNkdkCDTTQDrKETgHn9u2Kbx1vvZFDkMxFgmwl6keoe9X4XtEsw/LSgHlE4dEscGVwEidzMJuH2ZapgB9KbqSJImKmtvJYjF1700zrWdXSqy5NeLiexwBwoQbkrnO27AwqpRnEFwJrBlEggsBcUYj8s9hRYlDKb3juEEKgdafxc=" + - secure: "g0uexghpfxjO1pVYCBqj/P957zCZQT1v5naWgzDtu5n2Sh1WoW00K4+jOYJ4bbIm+1O8wbzzlyOuYZMMz0xXLVzuOdoMwMhfcnI+pE/dpa4J/vs1RFqoLh4HuNx9oPErNODdmWIkeC1SpcuBeYZfYxEL6UJIWbZZq3a9BIslx88=" install: - ./.travis.install diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d41aa7..f81b360 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,37 @@ # Recoil Changelog -### 0.3.0 (2015-06-26) +## 0.4.0 (2016-03-06) -* **[BC]** Removed `StrandInterface::resume()` -* **[NEW]** `return` statement can be used to return a value inside a coroutine (requires PHP 7) -* **[IMPROVED]** Improved method documentation on `Recoil` facade (thanks @rjkip) +This is the final release that will operate with PHP 5. In an effort to work +towards a production ready 1.0 release, future releases will require PHP 7. -### 0.2.1 (2014-10-16) +- **[BC]** Dropped `Interface` suffix from interfaces +- **[BC]** Renamed `ReadableStream` to `ReadablePhpStream` +- **[BC]** Renamed `WritableStream` to `WritablePhpStream` +- **[BC]** Renamed `CoroutineAdaptor` to `StandardCoroutineAdaptor` +- **[BC]** Renamed `KernelApi` to `StandardKernelApi` +- **[BC]** Renamed `Strand` to `StandardStrand` +- **[BC]** Renamed `StrandFactory` to `StandardStrandFactory` +- **[NEW]** Added support for Guzzle promises +- **[IMPROVED]** The callback given to `Recoil::suspend` is now optional -* **[IMPROVED]** Added support for cancellable promises +## 0.3.0 (2015-06-26) -### 0.2.0 (2014-09-23) +- **[BC]** Removed `StrandInterface::resume()` +- **[NEW]** `return` statement can be used to return a value inside a coroutine (requires PHP 7) +- **[IMPROVED]** Improved method documentation on `Recoil` facade (thanks @rjkip) + +## 0.2.1 (2014-10-16) + +- **[IMPROVED]** Added support for cancellable promises + +## 0.2.0 (2014-09-23) To faciliate several performance improvements the following backwards compatibility breaking changes have been introduced: -* **[BC]** `CoroutineInterface` no longer implements `EventEmitterInterface` - several unused events were fired every time a coroutine was called -* **[BC]** `Recoil::finalize()` now only works with generated based coroutines - this was previously implemented using the aforementioned events +- **[BC]** `CoroutineInterface` no longer implements `EventEmitterInterface` - several unused events were fired every time a coroutine was called +- **[BC]** `Recoil::finalize()` now only works with generator based coroutines - this was previously implemented using the aforementioned events -### 0.1.0 (2014-02-04) +## 0.1.0 (2014-02-04) -* Initial release +- Initial release diff --git a/README.md b/README.md index 8c1f918..0491849 100644 --- a/README.md +++ b/README.md @@ -96,13 +96,13 @@ Internally, the kernel uses a [React event-loop](https://github.com/reactphp/eve applications to execute coroutine based code alongside "conventional" React code by sharing an event-loop instance. Coroutine control flow, the current strand, and the kernel itself can be manipulated using the *kernel API*. The -supported operations are defined in [KernelApiInterface](src/Kernel/Api/KernelApiInterface.php) (though custom kernel -implementations may provide additional operations). Inside an executing coroutine, the kernel API for the current kernel -is accessed via the [Recoil facade](src/Recoil.php). +supported operations are defined in [KernelApi](src/Kernel/Api/KernelApi.php) (though custom kernel implementations may +provide additional operations). Inside an executing coroutine, the kernel API for the current kernel is accessed via the +[Recoil facade](src/Recoil.php). ### Streams -*Streams* provide a coroutine based abstraction for [readable](src/Stream/ReadableStreamInterface.php) and [writable](src/Stream/WritableStreamInterface.php) +*Streams* provide a coroutine based abstraction for [readable](src/Stream/ReadableStream.php) and [writable](src/Stream/WritableStream.php) data streams. The interfaces are somewhat similar to the built-in PHP stream API. Stream operations are cooperative, that is, when reading or writing to a stream, execution of the coroutine is suspended @@ -115,7 +115,7 @@ The [stream-file example](examples/stream-file) demonstrates using a readable st *Channels* are stream-like objects that produce and consume PHP values rather than byte streams. Channels are intended as the primary method for communication between strands. -Like streams there are [readable](src/Channel/ReadableChannelInterface.php) and [writable](src/Channel/WritableChannelInterface.php) +Like streams there are [readable](src/Channel/ReadableChannel.php) and [writable](src/Channel/WritableChannel.php) variants. Some channel implementations allow for multiple concurrent read and write operations. Both in-memory and stream-based channels are provided. Stream-based channels use a serialization protocol to encode and @@ -212,7 +212,7 @@ function multiply($a, $b) ### Throwing and catching exceptions One of the major advantages made available by coroutines is that errors can be reported using familiar exception -handling techniques. Unlike `return`, the `throw` keyword can be used in the standard way inside PHP generators. +handling techniques. The `throw` keyword can be used in the standard way inside PHP generators in both PHP version 5 and 7. ```php function multiply($a, $b) @@ -268,6 +268,30 @@ The [promise-dns example](examples/promise-dns) demonstrates using the [React DN a promised-based API, to resolve several domain names concurrently. [This example](examples/promise-dns-react) shows the same functionality implemented without **Recoil**. +### Callback and Events + +Conventional asynchronous code uses callback functions to inform a caller when a result is available or an event occurs. +The kernel API provides `Recoil::callback()` to create a callback that executes a coroutine on its own strand. + +```php +use Evenement\EventEmitter; + +Recoil::run( + function () { + $eventEmitter = new EventEmitter(); + $eventEmitter->on( + 'hello' + (yield Recoil::callback( + function ($name) { + echo 'Hello, ' . $name . '!' . PHP_EOL; + yield Recoil::noop(); + } + )) + ); + } +); +``` + ### Using an existing event-loop In all of the examples above, the `Recoil::run()` convenience function is used to start the kernel. Internally this @@ -299,7 +323,7 @@ manually. ```php $eventLoop = new React\EventLoop\StreamSelectLoop; -$kernel = new Recoil\Kernel\Kernel($eventLoop); +$kernel = new Recoil\Kernel\StandardKernel($eventLoop); $coroutine = function () { echo 'Hello, world!' . PHP_EOL; @@ -320,4 +344,4 @@ $eventLoop->run(); [Build Status]: http://img.shields.io/travis/recoilphp/recoil/develop.svg?style=flat-square [Test Coverage]: http://img.shields.io/coveralls/recoilphp/recoil/develop.svg?style=flat-square -[SemVer]: http://img.shields.io/:semver-0.3.0-yellow.svg?style=flat-square +[SemVer]: http://img.shields.io/:semver-0.4.0-yellow.svg?style=flat-square diff --git a/composer.json b/composer.json index 26e35b4..30b55dc 100644 --- a/composer.json +++ b/composer.json @@ -24,14 +24,17 @@ ], "require": { "php": ">=5.5", - "evenement/evenement": "~2", - "icecave/repr": "~1", - "react/react": "0.4.0|~0.4.2", - "react/promise": "~2" + "evenement/evenement": "^2", + "icecave/repr": "^1", + "react/event-loop": "^0.4" }, "require-dev": { - "icecave/archer": "~1", - "phake/phake": "2.0.0-alpha4 as v1.0.3" + "icecave/archer": "^1", + "guzzlehttp/promises": "^1", + "phake/phake": "2.0.0-alpha4 as v1.0.3", + "react/dns": "^0.4", + "react/promise": "^2", + "react/stream": "^0.4" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 3c40350..c569455 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "6c46870c8c4b6ec529f2e4a3c08df6aa", + "hash": "a5f1d709bed00602f6ca357f84dee616", + "content-hash": "9dc9274e2f97339ecf3dbbfde09a0710", "packages": [ { "name": "evenement/evenement", @@ -52,159 +53,6 @@ ], "time": "2012-11-02 14:49:47" }, - { - "name": "guzzle/guzzle", - "version": "v3.9.3", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle3.git", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", - "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "php": ">=5.3.3", - "symfony/event-dispatcher": "~2.1" - }, - "replace": { - "guzzle/batch": "self.version", - "guzzle/cache": "self.version", - "guzzle/common": "self.version", - "guzzle/http": "self.version", - "guzzle/inflection": "self.version", - "guzzle/iterator": "self.version", - "guzzle/log": "self.version", - "guzzle/parser": "self.version", - "guzzle/plugin": "self.version", - "guzzle/plugin-async": "self.version", - "guzzle/plugin-backoff": "self.version", - "guzzle/plugin-cache": "self.version", - "guzzle/plugin-cookie": "self.version", - "guzzle/plugin-curlauth": "self.version", - "guzzle/plugin-error-response": "self.version", - "guzzle/plugin-history": "self.version", - "guzzle/plugin-log": "self.version", - "guzzle/plugin-md5": "self.version", - "guzzle/plugin-mock": "self.version", - "guzzle/plugin-oauth": "self.version", - "guzzle/service": "self.version", - "guzzle/stream": "self.version" - }, - "require-dev": { - "doctrine/cache": "~1.3", - "monolog/monolog": "~1.0", - "phpunit/phpunit": "3.7.*", - "psr/log": "~1.0", - "symfony/class-loader": "~2.1", - "zendframework/zend-cache": "2.*,<2.3", - "zendframework/zend-log": "2.*,<2.3" - }, - "suggest": { - "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.9-dev" - } - }, - "autoload": { - "psr-0": { - "Guzzle": "src/", - "Guzzle\\Tests": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Guzzle Community", - "homepage": "https://github.com/guzzle/guzzle/contributors" - } - ], - "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ], - "time": "2015-03-18 18:23:50" - }, - { - "name": "guzzlehttp/psr7", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "af0e1758de355eb113917ad79c3c0e3604bce4bd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/af0e1758de355eb113917ad79c3c0e3604bce4bd", - "reference": "af0e1758de355eb113917ad79c3c0e3604bce4bd", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "PSR-7 message implementation", - "keywords": [ - "http", - "message", - "stream", - "uri" - ], - "time": "2015-06-24 19:55:15" - }, { "name": "icecave/repr", "version": "1.0.1", @@ -257,572 +105,178 @@ "time": "2014-07-25 05:44:41" }, { - "name": "psr/http-message", - "version": "1.0", + "name": "react/event-loop", + "version": "v0.4.1", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" + "url": "https://github.com/reactphp/event-loop.git", + "reference": "18c5297087ca01de85518e2b55078f444144aa1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", - "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/18c5297087ca01de85518e2b55078f444144aa1b", + "reference": "18c5297087ca01de85518e2b55078f444144aa1b", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.4.0" + }, + "suggest": { + "ext-event": "~1.0", + "ext-libev": "*", + "ext-libevent": ">=0.1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "0.4-dev" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "React\\EventLoop\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", + "description": "Event loop abstraction layer that libraries can use for evented I/O.", "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" + "event-loop" ], - "time": "2015-05-04 20:22:00" - }, + "time": "2014-02-26 17:36:58" + } + ], + "packages-dev": [ { - "name": "react/cache", - "version": "v0.4.0", + "name": "guzzle/guzzle", + "version": "v3.9.3", "source": { "type": "git", - "url": "https://github.com/reactphp/cache.git", - "reference": "9882ab5d8b00617baae83c6996f5a34668c11beb" + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/9882ab5d8b00617baae83c6996f5a34668c11beb", - "reference": "9882ab5d8b00617baae83c6996f5a34668c11beb", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", "shasum": "" }, "require": { - "php": ">=5.4.0", - "react/promise": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "React\\Cache\\": "" - } + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Async caching.", - "keywords": [ - "cache" - ], - "time": "2014-02-02 01:11:26" - }, - { - "name": "react/child-process", - "version": "v0.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/child-process.git", - "reference": "8bf211533bcbb2034e00528a47400367570dc3d7" + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/8bf211533bcbb2034e00528a47400367570dc3d7", - "reference": "8bf211533bcbb2034e00528a47400367570dc3d7", - "shasum": "" + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" }, - "require": { - "evenement/evenement": "~2.0", - "php": ">=5.4.0", - "react/event-loop": "0.4.*", - "react/stream": "0.4.*" + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.4-dev" + "dev-master": "3.9-dev" } }, "autoload": { - "psr-4": { - "React\\ChildProcess\\": "" + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Library for executing child processes.", - "keywords": [ - "process" - ], - "time": "2014-02-02 01:11:26" - }, - { - "name": "react/dns", - "version": "v0.4.1", - "source": { - "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "8c5ccc35dcb4b06b70eb9201842363fac7b0f3cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/8c5ccc35dcb4b06b70eb9201842363fac7b0f3cf", - "reference": "8c5ccc35dcb4b06b70eb9201842363fac7b0f3cf", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "react/cache": "0.4.*", - "react/promise": "~2.0", - "react/socket": "0.4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "React\\Dns\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Async DNS resolver.", - "keywords": [ - "dns", - "dns-resolver" - ], - "time": "2014-04-12 14:09:10" - }, - { - "name": "react/event-loop", - "version": "v0.4.1", - "source": { - "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "18c5297087ca01de85518e2b55078f444144aa1b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/18c5297087ca01de85518e2b55078f444144aa1b", - "reference": "18c5297087ca01de85518e2b55078f444144aa1b", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "suggest": { - "ext-event": "~1.0", - "ext-libev": "*", - "ext-libevent": ">=0.1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "React\\EventLoop\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Event loop abstraction layer that libraries can use for evented I/O.", - "keywords": [ - "event-loop" - ], - "time": "2014-02-26 17:36:58" - }, - { - "name": "react/http", - "version": "v0.4.1", - "source": { - "type": "git", - "url": "https://github.com/reactphp/http.git", - "reference": "f575989d67b7db0a65f5dd7e431d8f47af6c2f7b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/http/zipball/f575989d67b7db0a65f5dd7e431d8f47af6c2f7b", - "reference": "f575989d67b7db0a65f5dd7e431d8f47af6c2f7b", - "shasum": "" - }, - "require": { - "evenement/evenement": "^2.0", - "guzzlehttp/psr7": "^1.0", - "php": ">=5.4.0", - "react/socket": "^0.4", - "react/stream": "^0.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.5-dev" - } - }, - "autoload": { - "psr-4": { - "React\\Http\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Library for building an evented http server.", - "keywords": [ - "http" - ], - "time": "2015-05-21 20:12:09" - }, - { - "name": "react/http-client", - "version": "v0.4.4", - "source": { - "type": "git", - "url": "https://github.com/reactphp/http-client.git", - "reference": "391e1e771c2255954226204edcdc5a2764938e6a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/http-client/zipball/391e1e771c2255954226204edcdc5a2764938e6a", - "reference": "391e1e771c2255954226204edcdc5a2764938e6a", - "shasum": "" - }, - "require": { - "evenement/evenement": "~2.0", - "guzzle/parser": "~3.0", - "php": ">=5.4.0", - "react/dns": "0.4.*", - "react/event-loop": "0.4.*", - "react/socket-client": "0.4.*", - "react/stream": "0.4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.5-dev" - } - }, - "autoload": { - "psr-4": { - "React\\HttpClient\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Asynchronous HTTP client library.", - "keywords": [ - "http" - ], - "time": "2015-06-16 20:58:52" - }, - { - "name": "react/promise", - "version": "v2.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/365fcee430dfa4ace1fbc75737ca60ceea7eeeef", - "reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "React\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], "authors": [ { - "name": "Jan Sorgalla", - "email": "jsorgalla@googlemail.com" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "time": "2014-12-30 13:32:42" - }, - { - "name": "react/react", - "version": "v0.4.2", - "source": { - "type": "git", - "url": "https://github.com/reactphp/react.git", - "reference": "457b6b8a16a37c11278cac0870d6d2ff911c5765" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/react/zipball/457b6b8a16a37c11278cac0870d6d2ff911c5765", - "reference": "457b6b8a16a37c11278cac0870d6d2ff911c5765", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "react/cache": "0.4.*", - "react/child-process": "0.4.*", - "react/dns": "0.4.*", - "react/event-loop": "0.4.*", - "react/http": "0.4.*", - "react/http-client": "0.4.*", - "react/promise": "~2.1", - "react/socket": "0.4.*", - "react/socket-client": "0.4.*", - "react/stream": "0.4.*" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-event": "Allows for use of a more performant event-loop implementation.", - "ext-libev": "Allows for use of a more performant event-loop implementation.", - "ext-libevent": "Allows for use of a more performant event-loop implementation." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.5-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Nuclear Reactor written in PHP.", - "keywords": [ - "asynchronous", - "event-loop", - "reactor" - ], - "time": "2014-12-11 02:06:55" - }, - { - "name": "react/socket", - "version": "v0.4.2", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "a6acf405ca53fc6cfbfe7c77778ededff46aa7cc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/a6acf405ca53fc6cfbfe7c77778ededff46aa7cc", - "reference": "a6acf405ca53fc6cfbfe7c77778ededff46aa7cc", - "shasum": "" - }, - "require": { - "evenement/evenement": "~2.0", - "php": ">=5.4.0", - "react/event-loop": "0.4.*", - "react/stream": "0.4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "React\\Socket\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Library for building an evented socket server.", - "keywords": [ - "Socket" - ], - "time": "2014-05-25 17:02:16" - }, - { - "name": "react/socket-client", - "version": "v0.4.3", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket-client.git", - "reference": "1375dac21b1881cba31c2b38f412dc039566673f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket-client/zipball/1375dac21b1881cba31c2b38f412dc039566673f", - "reference": "1375dac21b1881cba31c2b38f412dc039566673f", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "react/dns": "0.4.*", - "react/event-loop": "0.4.*", - "react/promise": "~2.0", - "react/stream": "0.4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-4": { - "React\\SocketClient\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Async connector to open TCP/IP and SSL/TLS based connections.", - "keywords": [ - "Socket" - ], - "time": "2015-03-20 15:24:06" - }, - { - "name": "react/stream", - "version": "v0.4.2", - "source": { - "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "acc7a5fec02e0aea674560e1d13c40ed0c8c5465" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/acc7a5fec02e0aea674560e1d13c40ed0c8c5465", - "reference": "acc7a5fec02e0aea674560e1d13c40ed0c8c5465", - "shasum": "" - }, - "require": { - "evenement/evenement": "~2.0", - "php": ">=5.4.0" - }, - "require-dev": { - "react/event-loop": "0.4.*", - "react/promise": "~2.0" - }, - "suggest": { - "react/event-loop": "0.4.*", - "react/promise": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.5-dev" - } - }, - "autoload": { - "psr-4": { - "React\\Stream\\": "src" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" ], - "description": "Basic readable and writable stream interfaces that support piping.", + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", "keywords": [ - "pipe", - "stream" + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" ], - "time": "2014-09-10 03:32:31" + "time": "2015-03-18 18:23:50" }, { - "name": "symfony/event-dispatcher", - "version": "v2.7.1", + "name": "guzzlehttp/promises", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/symfony/EventDispatcher.git", - "reference": "be3c5ff8d503c46768aeb78ce6333051aa6f26d9" + "url": "https://github.com/guzzle/promises.git", + "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/be3c5ff8d503c46768aeb78ce6333051aa6f26d9", - "reference": "be3c5ff8d503c46768aeb78ce6333051aa6f26d9", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b1e1c0d55f8083c71eda2c28c12a228d708294ea", + "reference": "b1e1c0d55f8083c71eda2c28c12a228d708294ea", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.5.0" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.0,>=2.0.5", - "symfony/dependency-injection": "~2.6", - "symfony/expression-language": "~2.6", - "symfony/phpunit-bridge": "~2.7", - "symfony/stopwatch": "~2.3" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "phpunit/phpunit": "~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "1.0-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - } + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -830,20 +284,17 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" } ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2015-06-08 09:37:21" - } - ], - "packages-dev": [ + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2015-10-15 22:28:00" + }, { "name": "icecave/archer", "version": "1.3.2", @@ -975,16 +426,16 @@ }, { "name": "nikic/php-parser", - "version": "v1.3.0", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dff239267fd1befa1cd40430c9ed12591aa720ca" + "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dff239267fd1befa1cd40430c9ed12591aa720ca", - "reference": "dff239267fd1befa1cd40430c9ed12591aa720ca", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", + "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", "shasum": "" }, "require": { @@ -994,7 +445,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -1016,7 +467,7 @@ "parser", "php" ], - "time": "2015-05-02 15:40:40" + "time": "2015-09-19 14:15:08" }, { "name": "phake/phake", @@ -1066,50 +517,258 @@ "email": "m@digitalsandwich.com" } ], - "description": "The Phake mock testing library", - "homepage": "https://github.com/mlively/Phake", + "description": "The Phake mock testing library", + "homepage": "https://github.com/mlively/Phake", + "keywords": [ + "mock", + "testing" + ], + "time": "2013-08-06 22:31:04" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "pimple/pimple", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2015-09-11 15:10:35" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "react/cache", + "version": "v0.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "9882ab5d8b00617baae83c6996f5a34668c11beb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/9882ab5d8b00617baae83c6996f5a34668c11beb", + "reference": "9882ab5d8b00617baae83c6996f5a34668c11beb", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "React\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async caching.", + "keywords": [ + "cache" + ], + "time": "2014-02-02 01:11:26" + }, + { + "name": "react/dns", + "version": "v0.4.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "8c5ccc35dcb4b06b70eb9201842363fac7b0f3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/8c5ccc35dcb4b06b70eb9201842363fac7b0f3cf", + "reference": "8c5ccc35dcb4b06b70eb9201842363fac7b0f3cf", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "react/cache": "0.4.*", + "react/promise": "~2.0", + "react/socket": "0.4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-4": { + "React\\Dns\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async DNS resolver.", "keywords": [ - "mock", - "testing" + "dns", + "dns-resolver" ], - "time": "2013-08-06 22:31:04" + "time": "2014-04-12 14:09:10" }, { - "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", + "name": "react/promise", + "version": "v2.2.1", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + "url": "https://github.com/reactphp/promise.git", + "reference": "3b6fca09c7d56321057fa8867c8dbe1abf648627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "url": "https://api.github.com/repos/reactphp/promise/zipball/3b6fca09c7d56321057fa8867c8dbe1abf648627", + "reference": "3b6fca09c7d56321057fa8867c8dbe1abf648627", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" + "php": ">=5.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { - "psr-0": { - "phpDocumentor": [ - "src/" - ] - } + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1117,114 +776,120 @@ ], "authors": [ { - "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" } ], - "time": "2015-02-03 12:10:50" + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "time": "2015-07-03 13:48:55" }, { - "name": "pimple/pimple", - "version": "v3.0.0", + "name": "react/socket", + "version": "v0.4.2", "source": { "type": "git", - "url": "https://github.com/silexphp/Pimple.git", - "reference": "876bf0899d01feacd2a2e83f04641e51350099ef" + "url": "https://github.com/reactphp/socket.git", + "reference": "a6acf405ca53fc6cfbfe7c77778ededff46aa7cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/876bf0899d01feacd2a2e83f04641e51350099ef", - "reference": "876bf0899d01feacd2a2e83f04641e51350099ef", + "url": "https://api.github.com/repos/reactphp/socket/zipball/a6acf405ca53fc6cfbfe7c77778ededff46aa7cc", + "reference": "a6acf405ca53fc6cfbfe7c77778ededff46aa7cc", "shasum": "" }, "require": { - "php": ">=5.3.0" + "evenement/evenement": "~2.0", + "php": ">=5.4.0", + "react/event-loop": "0.4.*", + "react/stream": "0.4.*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "0.4-dev" } }, "autoload": { - "psr-0": { - "Pimple": "src/" + "psr-4": { + "React\\Socket\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", - "homepage": "http://pimple.sensiolabs.org", + "description": "Library for building an evented socket server.", "keywords": [ - "container", - "dependency injection" + "Socket" ], - "time": "2014-07-24 09:48:15" + "time": "2014-05-25 17:02:16" }, { - "name": "psr/log", - "version": "1.0.0", + "name": "react/stream", + "version": "v0.4.3", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + "url": "https://github.com/reactphp/stream.git", + "reference": "305b2328d2a2e157bc13b61a0f5c6e41b666b188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", - "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "url": "https://api.github.com/repos/reactphp/stream/zipball/305b2328d2a2e157bc13b61a0f5c6e41b666b188", + "reference": "305b2328d2a2e157bc13b61a0f5c6e41b666b188", "shasum": "" }, + "require": { + "evenement/evenement": "^2.0|^1.0", + "php": ">=5.3.8" + }, + "require-dev": { + "react/event-loop": "^0.4|^0.3", + "react/promise": "^2.0|^1.0" + }, + "suggest": { + "react/event-loop": "^0.4", + "react/promise": "^2.0" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5-dev" + } + }, "autoload": { - "psr-0": { - "Psr\\Log\\": "" + "psr-4": { + "React\\Stream\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", + "description": "Basic readable and writable stream interfaces that support piping.", "keywords": [ - "log", - "psr", - "psr-3" + "pipe", + "stream" ], - "time": "2012-12-21 11:40:51" + "time": "2015-10-07 18:32:58" }, { "name": "sami/sami", - "version": "v3.0.5", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/Sami.git", - "reference": "0828c3700604eee9ec3f6f4affa6a9224f4ce76a" + "reference": "504f99a1783e2e0e8817a57be946f7c699b83163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/Sami/zipball/0828c3700604eee9ec3f6f4affa6a9224f4ce76a", - "reference": "0828c3700604eee9ec3f6f4affa6a9224f4ce76a", + "url": "https://api.github.com/repos/FriendsOfPHP/Sami/zipball/504f99a1783e2e0e8817a57be946f7c699b83163", + "reference": "504f99a1783e2e0e8817a57be946f7c699b83163", "shasum": "" }, "require": { "michelf/php-markdown": "~1.3", "nikic/php-parser": "~1.0", - "php": ">=5.3.3", + "php": ">=5.3.9", "phpdocumentor/reflection-docblock": "~2.0", "pimple/pimple": "~3.0", "symfony/console": "~2.1", @@ -1232,7 +897,7 @@ "symfony/finder": "~2.1", "symfony/process": "~2.1", "symfony/yaml": "~2.1", - "twig/twig": "~1.13" + "twig/twig": "~1.20|~2.0" }, "bin": [ "sami.php" @@ -1263,7 +928,7 @@ "keywords": [ "phpdoc" ], - "time": "2015-05-30 16:31:24" + "time": "2015-08-30 14:15:00" }, { "name": "satooshi/php-coveralls", @@ -1335,35 +1000,35 @@ }, { "name": "symfony/config", - "version": "v2.7.1", + "version": "v3.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/Config.git", - "reference": "58ded81f1f582a87c528ef3dae9a859f78b5f374" + "url": "https://github.com/symfony/config.git", + "reference": "40bae8658dbbb500ebc19aa9fde22dc4295fc290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Config/zipball/58ded81f1f582a87c528ef3dae9a859f78b5f374", - "reference": "58ded81f1f582a87c528ef3dae9a859f78b5f374", + "url": "https://api.github.com/repos/symfony/config/zipball/40bae8658dbbb500ebc19aa9fde22dc4295fc290", + "reference": "40bae8658dbbb500ebc19aa9fde22dc4295fc290", "shasum": "" }, "require": { - "php": ">=5.3.9", - "symfony/filesystem": "~2.3" - }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\Config\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1381,30 +1046,30 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2015-06-11 14:06:56" + "time": "2015-11-02 20:34:04" }, { "name": "symfony/console", - "version": "v2.7.1", + "version": "v2.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/Console.git", - "reference": "564398bc1f33faf92fc2ec86859983d30eb81806" + "url": "https://github.com/symfony/console.git", + "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/564398bc1f33faf92fc2ec86859983d30eb81806", - "reference": "564398bc1f33faf92fc2ec86859983d30eb81806", + "url": "https://api.github.com/repos/symfony/console/zipball/d232bfc100dfd32b18ccbcab4bcc8f28697b7e41", + "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/event-dispatcher": "~2.1", - "symfony/phpunit-bridge": "~2.7", - "symfony/process": "~2.1" + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" }, "suggest": { "psr/log": "For using the console logger", @@ -1414,13 +1079,16 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\Console\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1438,39 +1106,99 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2015-06-10 15:30:22" + "time": "2015-11-30 12:35:10" }, { - "name": "symfony/filesystem", - "version": "v2.7.1", + "name": "symfony/event-dispatcher", + "version": "v2.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/Filesystem.git", - "reference": "a0d43eb3e17d4f4c6990289805a488a0482a07f3" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Filesystem/zipball/a0d43eb3e17d4f4c6990289805a488a0482a07f3", - "reference": "a0d43eb3e17d4f4c6990289805a488a0482a07f3", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc", + "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { - "symfony/phpunit-bridge": "~2.7" + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2015-10-30 20:15:42" + }, + { + "name": "symfony/filesystem", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "3e661a0d521ac67496515fa6e6704bd61bcfff60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3e661a0d521ac67496515fa6e6704bd61bcfff60", + "reference": "3e661a0d521ac67496515fa6e6704bd61bcfff60", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" } }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -1487,38 +1215,38 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2015-06-08 09:37:21" + "time": "2015-11-23 10:19:46" }, { "name": "symfony/finder", - "version": "v2.7.1", + "version": "v2.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/Finder.git", - "reference": "c13a40d638aeede1e8400f8c956c7f9246c05f75" + "url": "https://github.com/symfony/finder.git", + "reference": "ead9b07af4ba77b6507bee697396a5c79e633f08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/c13a40d638aeede1e8400f8c956c7f9246c05f75", - "reference": "c13a40d638aeede1e8400f8c956c7f9246c05f75", + "url": "https://api.github.com/repos/symfony/finder/zipball/ead9b07af4ba77b6507bee697396a5c79e633f08", + "reference": "ead9b07af4ba77b6507bee697396a5c79e633f08", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\Finder\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1536,38 +1264,94 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2015-06-04 20:11:48" + "time": "2015-10-30 20:15:42" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "0b6a8940385311a24e060ec1fe35680e17c74497" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0b6a8940385311a24e060ec1fe35680e17c74497", + "reference": "0b6a8940385311a24e060ec1fe35680e17c74497", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2015-11-04 20:28:58" }, { "name": "symfony/process", - "version": "v2.7.1", + "version": "v2.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/Process.git", - "reference": "552d8efdc80980cbcca50b28d626ac8e36e3cdd1" + "url": "https://github.com/symfony/process.git", + "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/552d8efdc80980cbcca50b28d626ac8e36e3cdd1", - "reference": "552d8efdc80980cbcca50b28d626ac8e36e3cdd1", + "url": "https://api.github.com/repos/symfony/process/zipball/1b988a88e3551102f3c2d9e1d47a18c3a78d6312", + "reference": "1b988a88e3551102f3c2d9e1d47a18c3a78d6312", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\Process\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1585,38 +1369,38 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2015-06-08 09:37:21" + "time": "2015-11-30 12:35:10" }, { "name": "symfony/stopwatch", - "version": "v2.7.1", + "version": "v3.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/Stopwatch.git", - "reference": "c653f1985f6c2b7dbffd04d48b9c0a96aaef814b" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "6aeac8907e3e1340a0033b0a9ec075f8e6524800" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/c653f1985f6c2b7dbffd04d48b9c0a96aaef814b", - "reference": "c653f1985f6c2b7dbffd04d48b9c0a96aaef814b", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6aeac8907e3e1340a0033b0a9ec075f8e6524800", + "reference": "6aeac8907e3e1340a0033b0a9ec075f8e6524800", "shasum": "" }, "require": { - "php": ">=5.3.9" - }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" + "php": ">=5.5.9" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\Stopwatch\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1634,38 +1418,38 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2015-06-04 20:11:48" + "time": "2015-10-30 23:35:59" }, { "name": "symfony/yaml", - "version": "v2.7.1", + "version": "v2.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "9808e75c609a14f6db02f70fccf4ca4aab53c160" + "url": "https://github.com/symfony/yaml.git", + "reference": "f79824187de95064a2f5038904c4d7f0227fedb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/9808e75c609a14f6db02f70fccf4ca4aab53c160", - "reference": "9808e75c609a14f6db02f70fccf4ca4aab53c160", + "url": "https://api.github.com/repos/symfony/yaml/zipball/f79824187de95064a2f5038904c4d7f0227fedb5", + "reference": "f79824187de95064a2f5038904c4d7f0227fedb5", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "require-dev": { - "symfony/phpunit-bridge": "~2.7" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" - } + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1683,29 +1467,33 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2015-06-10 15:30:22" + "time": "2015-11-30 12:35:10" }, { "name": "twig/twig", - "version": "v1.18.2", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "e8e6575abf6102af53ec283f7f14b89e304fa602" + "reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/e8e6575abf6102af53ec283f7f14b89e304fa602", - "reference": "e8e6575abf6102af53ec283f7f14b89e304fa602", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/d9b6333ae8dd2c8e3fd256e127548def0bc614c6", + "reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6", "shasum": "" }, "require": { "php": ">=5.2.7" }, + "require-dev": { + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~2.7" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-master": "1.23-dev" } }, "autoload": { @@ -1740,7 +1528,7 @@ "keywords": [ "templating" ], - "time": "2015-06-06 23:31:24" + "time": "2015-11-05 12:49:06" } ], "aliases": [ diff --git a/examples/channel-ipc b/examples/channel-ipc index 5b7889b..e6e7f75 100755 --- a/examples/channel-ipc +++ b/examples/channel-ipc @@ -42,7 +42,7 @@ function childProcess() { // Create a channel for writing serialized values to STDOUT ... $channel = new \Recoil\Channel\WritableStreamChannel( - new \Recoil\Stream\WritableStream( + new \Recoil\Stream\WritablePhpStream( fopen('php://stdout', 'w') ) ); diff --git a/examples/strand-suspend-resume b/examples/strand-suspend-resume index f420488..1c3d292 100755 --- a/examples/strand-suspend-resume +++ b/examples/strand-suspend-resume @@ -9,7 +9,7 @@ require dirname(__DIR__) . '/vendor/autoload.php'; * coroutine based. */ -use Recoil\Kernel\Strand\StrandInterface; +use Recoil\Kernel\Strand\Strand; use Recoil\Recoil; Recoil::run( @@ -20,7 +20,7 @@ Recoil::run( // manually resumed. It accepts a callback that is passed the suspended // strand object. $value = (yield Recoil::suspend( - function (StrandInterface $strand) { + function (Strand $strand) { // Add a timer that resumes the strand after one second. // // A value can be returned to the suspended strand using diff --git a/examples/stream-file b/examples/stream-file index 5da0c4d..ebb58ea 100755 --- a/examples/stream-file +++ b/examples/stream-file @@ -12,7 +12,7 @@ use Recoil\Recoil; Recoil::run( function () { - $stream = new \Recoil\Stream\ReadableStream(fopen(__FILE__, 'r')); + $stream = new \Recoil\Stream\ReadablePhpStream(fopen(__FILE__, 'r')); while (!$stream->isClosed()) { echo (yield $stream->read(512)); diff --git a/src/Channel/BidirectionalChannel.php b/src/Channel/BidirectionalChannel.php new file mode 100644 index 0000000..4f0f591 --- /dev/null +++ b/src/Channel/BidirectionalChannel.php @@ -0,0 +1,10 @@ +readChannel = $readChannel; $this->writeChannel = $writeChannel; diff --git a/src/Channel/BidirectionalChannelInterface.php b/src/Channel/BidirectionalChannelInterface.php deleted file mode 100644 index 33f6c0b..0000000 --- a/src/Channel/BidirectionalChannelInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -coroutine($strand); - } - - if ($value instanceof CoroutineInterface) { - return $value; - } elseif ($value instanceof Generator) { - return new GeneratorCoroutine($value); - } elseif ($value instanceof PromiseInterface) { - return new PromiseCoroutine($value); - } elseif (is_array($value)) { - return Recoil::all($value); - } elseif (null === $value) { - return Recoil::cooperate(); - } - - throw new InvalidArgumentException( - 'Unable to adapt ' . Repr::repr($value) . ' into a coroutine.' - ); - } + public function adapt(Strand $strand, $value); } diff --git a/src/Coroutine/CoroutineAdaptorInterface.php b/src/Coroutine/CoroutineAdaptorInterface.php deleted file mode 100644 index f0fc320..0000000 --- a/src/Coroutine/CoroutineAdaptorInterface.php +++ /dev/null @@ -1,22 +0,0 @@ -returnValue($value); } @@ -30,10 +31,10 @@ public function resumeWithValue(StrandInterface $strand, $value) /** * Resume execution of a suspended coroutine by passing it an exception. * - * @param StrandInterface $strand The strand that is executing the coroutine. - * @param Exception $exception The exception to send to the coroutine. + * @param Strand $strand The strand that is executing the coroutine. + * @param Exception $exception The exception to send to the coroutine. */ - public function resumeWithException(StrandInterface $strand, Exception $exception) + public function resumeWithException(Strand $strand, Exception $exception) { $strand->throwException($exception); } @@ -41,9 +42,9 @@ public function resumeWithException(StrandInterface $strand, Exception $exceptio /** * Inform the coroutine that the executing strand is being terminated. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function terminate(StrandInterface $strand) + public function terminate(Strand $strand) { } @@ -52,9 +53,9 @@ public function terminate(StrandInterface $strand) * * This method is invoked after the coroutine is popped from the call stack. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function finalize(StrandInterface $strand) + public function finalize(Strand $strand) { } } diff --git a/src/Coroutine/Exception/PromiseRejectedException.php b/src/Coroutine/Exception/PromiseRejectedException.php index 6f5ff7c..9dcff7b 100644 --- a/src/Coroutine/Exception/PromiseRejectedException.php +++ b/src/Coroutine/Exception/PromiseRejectedException.php @@ -1,4 +1,5 @@ generator->valid(); @@ -54,10 +55,10 @@ public function call(StrandInterface $strand) /** * Invoked when tick() is called after sendOnNextTick(). * - * @param StrandInterface $strand The strand that is executing the coroutine. - * @param mixed $value The value passed to sendOnNextTick(). + * @param Strand $strand The strand that is executing the coroutine. + * @param mixed $value The value passed to sendOnNextTick(). */ - public function resumeWithValue(StrandInterface $strand, $value) + public function resumeWithValue(Strand $strand, $value) { try { $this->generator->send($value); @@ -82,10 +83,10 @@ public function resumeWithValue(StrandInterface $strand, $value) /** * Resume execution of a suspended coroutine by passing it an exception. * - * @param StrandInterface $strand The strand that is executing the coroutine. - * @param Exception $exception The exception to send to the coroutine. + * @param Strand $strand The strand that is executing the coroutine. + * @param Exception $exception The exception to send to the coroutine. */ - public function resumeWithException(StrandInterface $strand, Exception $exception) + public function resumeWithException(Strand $strand, Exception $exception) { try { $this->generator->throw($exception); @@ -112,9 +113,9 @@ public function resumeWithException(StrandInterface $strand, Exception $exceptio * * This method is invoked after the coroutine is popped from the call stack. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function finalize(StrandInterface $strand) + public function finalize(Strand $strand) { $this->generator = null; @@ -128,7 +129,7 @@ public function finalize(StrandInterface $strand) /** * Register a callback to be invoked when the coroutine is finalized. * - * @internal + * @access private * * @param callable $callback The callback to invoke. */ diff --git a/src/Coroutine/PromiseCoroutine.php b/src/Coroutine/PromiseCoroutine.php index c7924f6..06ff6d8 100644 --- a/src/Coroutine/PromiseCoroutine.php +++ b/src/Coroutine/PromiseCoroutine.php @@ -1,33 +1,34 @@ promise = $promise; + $this->cancellable = $cancellable; } /** * Start the coroutine. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function call(StrandInterface $strand) + public function call(Strand $strand) { $strand->suspend(); @@ -50,11 +51,11 @@ function ($reason) use ($strand) { /** * Inform the coroutine that the executing strand is being terminated. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function terminate(StrandInterface $strand) + public function terminate(Strand $strand) { - if ($this->promise instanceof CancellablePromiseInterface) { + if ($this->cancellable) { $this->promise->cancel(); } } @@ -64,9 +65,9 @@ public function terminate(StrandInterface $strand) * * This method is invoked after the coroutine is popped from the call stack. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function finalize(StrandInterface $strand) + public function finalize(Strand $strand) { $this->promise = null; } diff --git a/src/Coroutine/StandardCoroutineAdaptor.php b/src/Coroutine/StandardCoroutineAdaptor.php new file mode 100644 index 0000000..e600732 --- /dev/null +++ b/src/Coroutine/StandardCoroutineAdaptor.php @@ -0,0 +1,54 @@ +coroutine($strand); + } + + if ($value instanceof Coroutine) { + return $value; + } elseif ($value instanceof Generator) { + return new GeneratorCoroutine($value); + } elseif ($value instanceof CancellablePromiseInterface) { + return new PromiseCoroutine($value, true); + } elseif ($value instanceof PromiseInterface) { + return new PromiseCoroutine($value, false); + } elseif ($value instanceof GuzzlePromiseInterface) { + return new PromiseCoroutine($value, true); + } elseif (is_array($value)) { + return Recoil::all($value); + } elseif (null === $value) { + return Recoil::cooperate(); + } + + throw new InvalidArgumentException( + 'Unable to adapt ' . Repr::repr($value) . ' into a coroutine.' + ); + } +} diff --git a/src/Kernel/Api/KernelApi.php b/src/Kernel/Api/KernelApi.php index 2cc8459..5cb38a3 100644 --- a/src/Kernel/Api/KernelApi.php +++ b/src/Kernel/Api/KernelApi.php @@ -1,65 +1,51 @@ resumeWithValue($strand); - } + public function strand(Strand $strand); /** * Get the coroutine kernel that the current strand is executing on. * - * @param StrandInterface $strand The currently executing strand. + * @param Strand $strand The currently executing strand. */ - public function kernel(StrandInterface $strand) - { - $strand->resumeWithValue($strand->kernel()); - } + public function kernel(Strand $strand); /** * Get the React event-loop that the coroutine kernel is executing on. * - * @param StrandInterface $strand The currently executing strand. + * @param Strand $strand The currently executing strand. */ - public function eventLoop(StrandInterface $strand) - { - $strand->resumeWithValue($strand->kernel()->eventLoop()); - } + public function eventLoop(Strand $strand); /** * Return a value to the calling coroutine. * - * @param StrandInterface $strand The currently executing strand. - * @param mixed $value The value to send to the calling coroutine. + * @param Strand $strand The currently executing strand. + * @param mixed $value The value to send to the calling coroutine. */ - public function return_(StrandInterface $strand, $value = null) - { - $strand->returnValue($value); - } + public function return_(Strand $strand, $value = null); /** * Throw an exception to the calling coroutine. * - * @param StrandInterface $strand The currently executing strand. - * @param Exception $exception The error to send to the calling coroutine. + * @param Strand $strand The currently executing strand. + * @param Exception $exception The error to send to the calling coroutine. */ - public function throw_(StrandInterface $strand, Exception $exception) - { - $strand->throwException($exception); - } + public function throw_(Strand $strand, Exception $exception); /** * Register a callback to be invoked when the current coroutine is popped @@ -68,48 +54,33 @@ public function throw_(StrandInterface $strand, Exception $exception) * The callback is guaranteed to be called even if a reference to the * coroutine is held elsewhere (unlike a PHP finally block in a generator). * - * @param StrandInterface $strand The currently executing strand. - * @param callable $callback The callback to invoke. + * @param Strand $strand The currently executing strand. + * @param callable $callback The callback to invoke. */ - public function finally_(StrandInterface $strand, callable $callback) - { - $strand->current()->registerFinalizeCallback($callback); - - $strand->resumeWithValue(null); - } + public function finally_(Strand $strand, callable $callback); /** * Terminate execution of the strand. * - * @param StrandInterface $strand The currently executing strand. + * @param Strand $strand The currently executing strand. */ - public function terminate(StrandInterface $strand) - { - $strand->terminate(); - } + public function terminate(Strand $strand); /** * Suspend execution for a specified period of time. * - * @param StrandInterface $strand The currently executing strand. - * @param number $timeout The number of seconds to wait before resuming. + * @param Strand $strand The currently executing strand. + * @param number $timeout The number of seconds to wait before resuming. */ - public function sleep(StrandInterface $strand, $timeout) - { - return new Sleep($timeout); - } + public function sleep(Strand $strand, $timeout); /** - * Suspend execution of the strand. + * Suspend execution of the strand until it is resumed manually. * - * @param StrandInterface $strand The currently executing strand. + * @param Strand $strand The currently executing strand. + * @param callable|null $callback A callback which is passed the strand after it is suspended. */ - public function suspend(StrandInterface $strand, callable $callback) - { - $strand->suspend(); - - $callback($strand); - } + public function suspend(Strand $strand, callable $callback = null); /** * Execute a coroutine with a time limit. @@ -117,14 +88,11 @@ public function suspend(StrandInterface $strand, callable $callback) * If the coroutine does not complete within the specified time it is * cancelled. * - * @param StrandInterface $strand The currently executing strand. - * @param number $timeout The number of seconds to wait before cancelling. - * @param mixed $coroutine The coroutine to execute. + * @param Strand $strand The currently executing strand. + * @param number $timeout The number of seconds to wait before cancelling. + * @param mixed $coroutine The coroutine to execute. */ - public function timeout(StrandInterface $strand, $timeout, $coroutine) - { - return new Timeout($timeout, $coroutine); - } + public function timeout(Strand $strand, $timeout, $coroutine); /** * Execute the given coroutines concurrently. @@ -133,68 +101,50 @@ public function timeout(StrandInterface $strand, $timeout, $coroutine) * are completed. If any of the coroutines fails, the remaining coroutines * are terminated. * - * @param StrandInterface $strand The currently executing strand. - * @param array $coroutines The coroutines to execute. + * @param Strand $strand The currently executing strand. + * @param array $coroutines The coroutines to execute. */ - public function all(StrandInterface $strand, array $coroutines) - { - return new WaitAll($coroutines); - } + public function all(Strand $strand, array $coroutines); /** - * Suspend the strand until the next tick. + * Resume the strand immediately. * - * @param StrandInterface $strand The currently executing strand. + * @param Strand $strand The currently executing strand. */ - public function cooperate(StrandInterface $strand) - { - $strand->suspend(); - - $strand - ->kernel() - ->eventLoop() - ->futureTick( - function () use ($strand) { - $strand->resumeWithValue(null); - } - ); - } + public function noop(Strand $strand); /** - * Resume the strand immediately. + * Suspend the strand until the next tick. * - * @param StrandInterface $strand The currently executing strand. + * @param Strand $strand The currently executing strand. */ - public function noop(StrandInterface $strand) - { - $strand->resumeWithValue(null); - } + public function cooperate(Strand $strand); /** * Execute a coroutine on its own strand. * - * @param StrandInterface $strand The currently executing strand. - * @param mixed $coroutine The coroutine to execute. + * @param Strand $strand The currently executing strand. + * @param mixed $coroutine The coroutine to execute. */ - public function execute(StrandInterface $strand, $coroutine) - { - $substrand = $strand - ->kernel() - ->execute($coroutine); + public function execute(Strand $strand, $coroutine); - $strand->resumeWithValue($substrand); - } + /** + * Create a function that executes a coroutine in its own strand. + * + * If $coroutine is callable, it is expected to return a coroutine. + * + * @param Strand $strand The currently executing strand. + * @param mixed $coroutine The coroutine to execute. + */ + public function callback(Strand $stand, $coroutine); /** * Wait for one or more of the given strands to exit. * - * @param StrandInterface $strand The currently executing strand. - * @param StrandInterface[] $strands The strands to wait for. + * @param Strand $strand The currently executing strand. + * @param Strand[] $strands The strands to wait for. */ - public function select(StrandInterface $strand, array $strands) - { - return new Select($strands); - } + public function select(Strand $strand, array $strands); /** * Stop the coroutine kernel / event-loop. @@ -202,15 +152,8 @@ public function select(StrandInterface $strand, array $strands) * The React event-loop can optionally be stopped when all strands have been * terminated. * - * @param StrandInterface $strand The currently executing strand. - * @param boolean $stopEventLoop Indicates whether or not the React event-loop should also be stopped. + * @param Strand $strand The currently executing strand. + * @param boolean $stopEventLoop Indicates whether or not the React event-loop should also be stopped. */ - public function stop(StrandInterface $strand, $stopEventLoop = true) - { - $strand->terminate(); - - $strand - ->kernel() - ->stop($stopEventLoop); - } + public function stop(Strand $strand, $stopEventLoop = true); } diff --git a/src/Kernel/Api/KernelApiCall.php b/src/Kernel/Api/KernelApiCall.php index b06f227..d02c9c1 100644 --- a/src/Kernel/Api/KernelApiCall.php +++ b/src/Kernel/Api/KernelApiCall.php @@ -1,18 +1,19 @@ kernel()->api(), $this->name]; diff --git a/src/Kernel/Api/KernelApiInterface.php b/src/Kernel/Api/KernelApiInterface.php deleted file mode 100644 index d1377da..0000000 --- a/src/Kernel/Api/KernelApiInterface.php +++ /dev/null @@ -1,148 +0,0 @@ -exited) { @@ -60,9 +61,9 @@ public function call(StrandInterface $strand) * * This method is invoked after the coroutine is popped from the call stack. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function finalize(StrandInterface $strand) + public function finalize(Strand $strand) { foreach ($this->substrands as $strand) { $strand->removeListener( @@ -72,7 +73,7 @@ public function finalize(StrandInterface $strand) } } - public function onStrandExit(StrandInterface $strand) + public function onStrandExit(Strand $strand) { $index = $this->substrands[$strand]; diff --git a/src/Kernel/Api/Sleep.php b/src/Kernel/Api/Sleep.php index 34205d7..7aad333 100644 --- a/src/Kernel/Api/Sleep.php +++ b/src/Kernel/Api/Sleep.php @@ -1,16 +1,17 @@ suspend(); @@ -44,9 +45,9 @@ function () use ($strand) { * * This method is invoked after the coroutine is popped from the call stack. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function finalize(StrandInterface $strand) + public function finalize(Strand $strand) { if ($this->timer) { $this->timer->cancel(); diff --git a/src/Kernel/Api/StandardKernelApi.php b/src/Kernel/Api/StandardKernelApi.php new file mode 100644 index 0000000..912abc3 --- /dev/null +++ b/src/Kernel/Api/StandardKernelApi.php @@ -0,0 +1,249 @@ +resumeWithValue($strand); + } + + /** + * Get the coroutine kernel that the current strand is executing on. + * + * @param Strand $strand The currently executing strand. + */ + public function kernel(Strand $strand) + { + $strand->resumeWithValue($strand->kernel()); + } + + /** + * Get the React event-loop that the coroutine kernel is executing on. + * + * @param Strand $strand The currently executing strand. + */ + public function eventLoop(Strand $strand) + { + $strand->resumeWithValue($strand->kernel()->eventLoop()); + } + + /** + * Return a value to the calling coroutine. + * + * @param Strand $strand The currently executing strand. + * @param mixed $value The value to send to the calling coroutine. + */ + public function return_(Strand $strand, $value = null) + { + $strand->returnValue($value); + } + + /** + * Throw an exception to the calling coroutine. + * + * @param Strand $strand The currently executing strand. + * @param Exception $exception The error to send to the calling coroutine. + */ + public function throw_(Strand $strand, Exception $exception) + { + $strand->throwException($exception); + } + + /** + * Register a callback to be invoked when the current coroutine is popped + * from the call stack. + * + * The callback is guaranteed to be called even if a reference to the + * coroutine is held elsewhere (unlike a PHP finally block in a generator). + * + * @param Strand $strand The currently executing strand. + * @param callable $callback The callback to invoke. + */ + public function finally_(Strand $strand, callable $callback) + { + $strand->current()->registerFinalizeCallback($callback); + + $strand->resumeWithValue(null); + } + + /** + * Terminate execution of the strand. + * + * @param Strand $strand The currently executing strand. + */ + public function terminate(Strand $strand) + { + $strand->terminate(); + } + + /** + * Suspend execution for a specified period of time. + * + * @param Strand $strand The currently executing strand. + * @param number $timeout The number of seconds to wait before resuming. + */ + public function sleep(Strand $strand, $timeout) + { + return new Sleep($timeout); + } + + /** + * Suspend execution of the strand. + * + * @param Strand $strand The currently executing strand. + */ + public function suspend(Strand $strand, callable $callback = null) + { + $strand->suspend(); + + if ($callback) { + $callback($strand); + } + } + + /** + * Execute a coroutine with a time limit. + * + * If the coroutine does not complete within the specified time it is + * cancelled. + * + * @param Strand $strand The currently executing strand. + * @param number $timeout The number of seconds to wait before cancelling. + * @param mixed $coroutine The coroutine to execute. + */ + public function timeout(Strand $strand, $timeout, $coroutine) + { + return new Timeout($timeout, $coroutine); + } + + /** + * Execute the given coroutines concurrently. + * + * Execution of the current strand is suspended until all of the coroutines + * are completed. If any of the coroutines fails, the remaining coroutines + * are terminated. + * + * @param Strand $strand The currently executing strand. + * @param array $coroutines The coroutines to execute. + */ + public function all(Strand $strand, array $coroutines) + { + return new WaitAll($coroutines); + } + + /** + * Suspend the strand until the next tick. + * + * @param Strand $strand The currently executing strand. + */ + public function cooperate(Strand $strand) + { + $strand->suspend(); + + $strand + ->kernel() + ->eventLoop() + ->futureTick( + function () use ($strand) { + $strand->resumeWithValue(null); + } + ); + } + + /** + * Resume the strand immediately. + * + * @param Strand $strand The currently executing strand. + */ + public function noop(Strand $strand) + { + $strand->resumeWithValue(null); + } + + /** + * Execute a coroutine on its own strand. + * + * @param Strand $strand The currently executing strand. + * @param mixed $coroutine The coroutine to execute. + */ + public function execute(Strand $strand, $coroutine) + { + $substrand = $strand + ->kernel() + ->execute($coroutine); + + $strand->resumeWithValue($substrand); + } + + /** + * Create a function that executes a coroutine in its own strand. + * + * If $coroutine is callable, it is expected to return a coroutine. + * + * @param Strand $strand The currently executing strand. + * @param mixed $coroutine The coroutine to execute. + */ + public function callback(Strand $strand, $coroutine) + { + $kernel = $strand->kernel(); + + if (is_callable($coroutine)) { + $callback = function () use ($kernel, $coroutine) { + $kernel->execute( + call_user_func_array( + $coroutine, + func_get_args() + ) + ); + }; + } else { + $callback = function () use ($kernel, $coroutine) { + $kernel->execute($coroutine); + }; + } + + $strand->resumeWithValue($callback); + } + + /** + * Wait for one or more of the given strands to exit. + * + * @param Strand $strand The currently executing strand. + * @param Strand[] $strands The strands to wait for. + */ + public function select(Strand $strand, array $strands) + { + return new Select($strands); + } + + /** + * Stop the coroutine kernel / event-loop. + * + * The React event-loop can optionally be stopped when all strands have been + * terminated. + * + * @param Strand $strand The currently executing strand. + * @param boolean $stopEventLoop Indicates whether or not the React event-loop should also be stopped. + */ + public function stop(Strand $strand, $stopEventLoop = true) + { + $strand->terminate(); + + $strand + ->kernel() + ->stop($stopEventLoop); + } +} diff --git a/src/Kernel/Api/Timeout.php b/src/Kernel/Api/Timeout.php index b3635c5..994fa2d 100644 --- a/src/Kernel/Api/Timeout.php +++ b/src/Kernel/Api/Timeout.php @@ -1,18 +1,19 @@ timer = $strand ->kernel() @@ -46,9 +47,9 @@ function () use ($strand) { /** * Inform the coroutine that the executing strand is being terminated. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function terminate(StrandInterface $strand) + public function terminate(Strand $strand) { if (!$this->timer) { // Stop termination of the strand and instead propagate a timeout exception. @@ -60,9 +61,9 @@ public function terminate(StrandInterface $strand) * * This method is invoked after the coroutine is popped from the call stack. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function finalize(StrandInterface $strand) + public function finalize(Strand $strand) { if ($this->timer) { $this->timer->cancel(); diff --git a/src/Kernel/Api/WaitAll.php b/src/Kernel/Api/WaitAll.php index 6afc0ac..ec02b52 100644 --- a/src/Kernel/Api/WaitAll.php +++ b/src/Kernel/Api/WaitAll.php @@ -1,18 +1,19 @@ strand = $strand; $this->strand->suspend(); @@ -86,9 +87,9 @@ function () use ($index) { * * This method is invoked after the coroutine is popped from the call stack. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function finalize(StrandInterface $strand) + public function finalize(Strand $strand) { $this->strand = null; diff --git a/src/Kernel/Exception/StrandTerminatedException.php b/src/Kernel/Exception/StrandTerminatedException.php index fbd92ab..64ce3d3 100644 --- a/src/Kernel/Exception/StrandTerminatedException.php +++ b/src/Kernel/Exception/StrandTerminatedException.php @@ -1,4 +1,5 @@ eventLoop = $eventLoop; - $this->api = $api; - $this->coroutineAdaptor = $coroutineAdaptor; - $this->strandFactory = $strandFactory; - $this->strands = []; - $this->terminateStrands = false; - } - /** * Execute a coroutine in a new strand of execution. * @@ -60,83 +20,51 @@ public function __construct( * * @param mixed $coroutine The coroutine to execute. * - * @return StrandInterface The strand on which the coroutine will execute. + * @return Strand The strand on which the coroutine will execute. */ - public function execute($coroutine) - { - $strand = $this->strandFactory()->createStrand($this); - $strand->call($coroutine); - - return $strand; - } + public function execute($coroutine); /** * Attach an existing strand to this kernel. * - * @param StrandInterface The strand to attach. + * @param Strand The strand to attach. */ - public function attachStrand(StrandInterface $strand) - { - if (!$this->strands) { - $this->eventLoop->futureTick([$this, 'onTick']); - } - - $this->strands[] = $strand; - } + public function attachStrand(Strand $strand); /** * Detach an existing strand from this kernel. * - * @param StrandInterface The strand to detach. + * @param Strand The strand to detach. */ - public function detachStrand(StrandInterface $strand) - { - $index = array_search($strand, $this->strands, true); - - if (false !== $index) { - unset($this->strands[$index]); - } - } + public function detachStrand(Strand $strand); /** * Fetch the object that implements the kernel API. * - * @return KernelApiInterface The kernel's API implementation. + * @return KernelApi The kernel's API implementation. */ - public function api() - { - return $this->api; - } + public function api(); /** * Fetch the object used to adapt values into coroutines. * - * @return CoroutineAdaptorInterface The kernel's coroutine adaptor. + * @return CoroutineAdaptor The kernel's coroutine adaptor. */ - public function coroutineAdaptor() - { - return $this->coroutineAdaptor; - } + public function coroutineAdaptor(); /** * Fetch the factory used to create new strands. * - * @return StrandFactoryInterface The kernel's strand factory. + * @return StrandFactory The kernel's strand factory. */ - public function strandFactory() - { - return $this->strandFactory; - } + public function strandFactory(); /** * Fetch the React event-loop. * * @return LoopInterface The React event-loop. */ - public function eventLoop() - { - return $this->eventLoop; - } + public function eventLoop(); /** * Terminate all strands and stop execution. @@ -146,44 +74,5 @@ public function eventLoop() * * @param boolean $stopEventLoop Indicates whether or not the React event-loop should also be stopped. */ - public function stop($stopEventLoop = true) - { - $this->terminateStrands = true; - $this->stopEventLoop = $stopEventLoop; - } - - /** - * Step each of the strands attached to this kernel. - * - * @internal - */ - public function onTick() - { - $strands = $this->strands; - - foreach ($strands as $strand) { - if ($this->terminateStrands) { - $strand->terminate(); - } - - $strand->tick(); - } - - if ($this->strands) { - $this->eventLoop->futureTick([$this, 'onTick']); - } elseif ($this->stopEventLoop) { - $this->eventLoop->stop(); - } - - $this->terminateStrands = false; - $this->stopEventLoop = false; - } - - private $eventLoop; - private $api; - private $coroutineAdaptor; - private $strandFactory; - private $strands; - private $terminateStrands; - private $stopEventLoop; + public function stop($stopEventLoop = true); } diff --git a/src/Kernel/KernelInterface.php b/src/Kernel/KernelInterface.php deleted file mode 100644 index b165c62..0000000 --- a/src/Kernel/KernelInterface.php +++ /dev/null @@ -1,77 +0,0 @@ -eventLoop = $eventLoop; + $this->api = $api; + $this->coroutineAdaptor = $coroutineAdaptor; + $this->strandFactory = $strandFactory; + $this->strands = []; + $this->terminateStrands = false; + } + + /** + * Execute a coroutine in a new strand of execution. + * + * The parameter may be any value that can be adapted into a coroutine by + * the kernel's coroutine adaptor. + * + * @param mixed $coroutine The coroutine to execute. + * + * @return Strand The strand on which the coroutine will execute. + */ + public function execute($coroutine) + { + $strand = $this->strandFactory()->createStrand($this); + $strand->call($coroutine); + + return $strand; + } + + /** + * Attach an existing strand to this kernel. + * + * @param Strand The strand to attach. + */ + public function attachStrand(Strand $strand) + { + if (!$this->strands) { + $this->eventLoop->futureTick([$this, 'onTick']); + } + + $this->strands[] = $strand; + } + + /** + * Detach an existing strand from this kernel. + * + * @param Strand The strand to detach. + */ + public function detachStrand(Strand $strand) + { + $index = array_search($strand, $this->strands, true); + + if (false !== $index) { + unset($this->strands[$index]); + } + } + + /** + * Fetch the object that implements the kernel API. + * + * @return KernelApi The kernel's API implementation. + */ + public function api() + { + return $this->api; + } + + /** + * Fetch the object used to adapt values into coroutines. + * + * @return CoroutineAdaptor The kernel's coroutine adaptor. + */ + public function coroutineAdaptor() + { + return $this->coroutineAdaptor; + } + + /** + * Fetch the factory used to create new strands. + * + * @return StrandFactory The kernel's strand factory. + */ + public function strandFactory() + { + return $this->strandFactory; + } + + /** + * Fetch the React event-loop. + * + * @return LoopInterface The React event-loop. + */ + public function eventLoop() + { + return $this->eventLoop; + } + + /** + * Terminate all strands and stop execution. + * + * The React event-loop can optionally be stopped when all strands have been + * terminated. + * + * @param boolean $stopEventLoop Indicates whether or not the React event-loop should also be stopped. + */ + public function stop($stopEventLoop = true) + { + $this->terminateStrands = true; + $this->stopEventLoop = $stopEventLoop; + } + + /** + * Step each of the strands attached to this kernel. + * + * @access private + */ + public function onTick() + { + $strands = $this->strands; + + foreach ($strands as $strand) { + if ($this->terminateStrands) { + $strand->terminate(); + } + + $strand->tick(); + } + + if ($this->strands) { + $this->eventLoop->futureTick([$this, 'onTick']); + } elseif ($this->stopEventLoop) { + $this->eventLoop->stop(); + } + + $this->terminateStrands = false; + $this->stopEventLoop = false; + } + + private $eventLoop; + private $api; + private $coroutineAdaptor; + private $strandFactory; + private $strands; + private $terminateStrands; + private $stopEventLoop; +} diff --git a/src/Kernel/Strand/StackBase.php b/src/Kernel/Strand/StackBase.php index 5ec6126..d0f336f 100644 --- a/src/Kernel/Strand/StackBase.php +++ b/src/Kernel/Strand/StackBase.php @@ -1,17 +1,18 @@ emit('success', [$strand, $value]); $strand->emit('exit', [$strand]); @@ -46,10 +47,10 @@ public function resumeWithValue(StrandInterface $strand, $value) /** * Resume execution of a suspended coroutine by passing it an exception. * - * @param StrandInterface $strand The strand that is executing the coroutine. - * @param Exception $exception The exception to send to the coroutine. + * @param Strand $strand The strand that is executing the coroutine. + * @param Exception $exception The exception to send to the coroutine. */ - public function resumeWithException(StrandInterface $strand, Exception $exception) + public function resumeWithException(Strand $strand, Exception $exception) { $throwException = true; @@ -72,9 +73,9 @@ public function resumeWithException(StrandInterface $strand, Exception $exceptio /** * Inform the coroutine that the executing strand is being terminated. * - * @param StrandInterface $strand The strand that is executing the coroutine. + * @param Strand $strand The strand that is executing the coroutine. */ - public function terminate(StrandInterface $strand) + public function terminate(Strand $strand) { $strand->emit('terminate', [$strand]); $strand->emit('exit', [$strand]); diff --git a/src/Kernel/Strand/StandardStrand.php b/src/Kernel/Strand/StandardStrand.php new file mode 100644 index 0000000..b50cf8d --- /dev/null +++ b/src/Kernel/Strand/StandardStrand.php @@ -0,0 +1,256 @@ +kernel = $kernel; + $this->suspended = false; + $this->stack = []; + + $this->stack[] = $this->current = new StackBase(); + + $kernel->attachStrand($this); + } + + /** + * Fetch the kernel on which this strand is executing. + * + * @return Kernel The coroutine kernel. + */ + public function kernel() + { + return $this->kernel; + } + + /** + * Fetch the coroutine currently being executed. + * + * @return Coroutine The coroutine currently being executed. + */ + public function current() + { + return $this->current; + } + + /** + * Push a coroutine onto the stack. + * + * The value must be adaptable using the kernel's coroutine adaptor. + * + * @param mixed $coroutine The coroutine to call. + * + * @return Coroutine The adapted coroutine. + */ + public function push($coroutine) + { + $coroutine = $this + ->kernel() + ->coroutineAdaptor() + ->adapt($this, $coroutine); + + $this->stack[] = $coroutine; + $this->current = $coroutine; + + return $coroutine; + } + + /** + * Pop the current coroutine off the stack. + * + * @return Coroutine + */ + public function pop() + { + $coroutine = array_pop($this->stack); + $this->current = end($this->stack); + + $coroutine->finalize($this); + + return $coroutine; + } + + /** + * Call the given coroutine immediately. + * + * The value must be adaptable using the kernel's coroutine adaptor. + * + * @param mixed $coroutine The coroutine to call. + * + * @return Coroutine|null The adapted coroutine, or null if no adaptation could be made. + */ + public function call($coroutine) + { + try { + $coroutine = $this->push($coroutine); + } catch (Exception $e) { + $this->resumeWithException($e); + + return null; + } + + $this->state = self::STATE_CALL; + $this->resumeData = null; + + return $coroutine; + } + + /** + * Return a value to calling coroutine. + * + * @param mixed $value The value to return. + */ + public function returnValue($value = null) + { + $this->pop(); + $this->resumeWithValue($value); + } + + /** + * Throw an exception to the calling coroutine. + * + * @param Exception $exception The exception to throw. + */ + public function throwException(Exception $exception) + { + $this->pop(); + $this->resumeWithException($exception); + } + + /** + * Suspend execution of this strand. + */ + public function suspend() + { + if ($this->suspended) { + return; + } + + $this->suspended = true; + + $this->kernel->detachStrand($this); + } + + /** + * Resume execution of this strand and send a value to the current coroutine. + * + * @param mixed $value The value to send to the coroutine. + */ + public function resumeWithValue($value) + { + $this->resume(); + + $this->state = self::STATE_RESUME; + $this->resumeData = $value; + } + + /** + * Resume execution of this strand and throw an exception to the current coroutine. + * + * @param Exception $exception The exception to send to the coroutine. + */ + public function resumeWithException(Exception $exception) + { + $this->resume(); + + $this->state = self::STATE_EXCEPTION; + $this->resumeData = $exception; + } + + /** + * Terminate this execution context. + */ + public function terminate() + { + $this->resume(); + + $this->state = self::STATE_TERMINATE; + $this->resumeData = null; + } + + /** + * Check if the strand has exited. + * + * @return boolean True if the strand has exited; otherwise false. + */ + public function hasExited() + { + return !$this->stack; + } + + /** + * Perform the next unit-of-work for this strand. + */ + public function tick() + { + while (!$this->suspended) { + if ($this->state === self::STATE_CALL) { + $this->state = null; + $this->current->call($this); + } elseif ($this->state === self::STATE_RESUME) { + $this->state = null; + $this->current->resumeWithValue($this, $this->resumeData); + } elseif ($this->state === self::STATE_EXCEPTION) { + $this->state = null; + $this->current->resumeWithException($this, $this->resumeData); + } elseif (self::STATE_TERMINATE === $this->state) { + $this->current->terminate($this); + + // Check if the state has been changed, if not continue with + // termination of the strand. + if ($this->state === self::STATE_TERMINATE) { + $this->pop(); + } + } else { + throw new LogicException('No action has been requested.'); + } + } + } + + /** + * Resume execution of this strand. + */ + private function resume() + { + if (!$this->suspended) { + return; + } + + $this->suspended = false; + + $this->kernel->attachStrand($this); + } + + const STATE_CALL = 1; + const STATE_RESUME = 2; + const STATE_EXCEPTION = 3; + const STATE_TERMINATE = 4; + + private $kernel; + private $suspended; + private $stack; + private $current; + private $state; + private $resumeData; +} diff --git a/src/Kernel/Strand/StandardStrandFactory.php b/src/Kernel/Strand/StandardStrandFactory.php new file mode 100644 index 0000000..e8921bf --- /dev/null +++ b/src/Kernel/Strand/StandardStrandFactory.php @@ -0,0 +1,23 @@ +kernel = $kernel; - $this->suspended = false; - $this->stack = []; - - $this->stack[] = $this->current = new StackBase(); - - $kernel->attachStrand($this); - } - /** * Fetch the kernel on which this strand is executing. * - * @return KernelInterface The coroutine kernel. + * @return Kernel The coroutine kernel. */ - public function kernel() - { - return $this->kernel; - } + public function kernel(); /** - * Fetch the coroutine currently being executed. + * Fetch the coroutine this strand is currently executing. * - * @return CoroutineInterface The coroutine currently being executed. + * @return Coroutine The coroutine currently being executed. */ - public function current() - { - return $this->current; - } + public function current(); /** * Push a coroutine onto the stack. @@ -60,196 +40,77 @@ public function current() * * @param mixed $coroutine The coroutine to call. * - * @return CoroutineInterface The adapted coroutine. + * @return Coroutine The adapted coroutine. */ - public function push($coroutine) - { - $coroutine = $this - ->kernel() - ->coroutineAdaptor() - ->adapt($this, $coroutine); - - $this->stack[] = $coroutine; - $this->current = $coroutine; - - return $coroutine; - } + public function push($coroutine); /** * Pop the current coroutine off the stack. * - * @return CoroutineInterface + * @return Coroutine */ - public function pop() - { - $coroutine = array_pop($this->stack); - $this->current = end($this->stack); - - $coroutine->finalize($this); - - return $coroutine; - } + public function pop(); /** - * Call the given coroutine immediately. + * Call the given coroutine. * * The value must be adaptable using the kernel's coroutine adaptor. * * @param mixed $coroutine The coroutine to call. * - * @return CoroutineInterface|null The adapted coroutine, or null if no adaptation could be made. + * @return Coroutine|null The adapted coroutine, or null if no adaptation could be made. */ - public function call($coroutine) - { - try { - $coroutine = $this->push($coroutine); - } catch (Exception $e) { - $this->resumeWithException($e); - - return null; - } - - $this->state = self::STATE_CALL; - $this->resumeData = null; - - return $coroutine; - } + public function call($coroutine); /** - * Return a value to calling coroutine. + * Return a value to the calling coroutine. * * @param mixed $value The value to return. */ - public function returnValue($value = null) - { - $this->pop(); - $this->resumeWithValue($value); - } + public function returnValue($value = null); /** * Throw an exception to the calling coroutine. * * @param Exception $exception The exception to throw. */ - public function throwException(Exception $exception) - { - $this->pop(); - $this->resumeWithException($exception); - } + public function throwException(Exception $exception); /** * Suspend execution of this strand. + * + * The kernel will not call tick() until the strand is resumed. */ - public function suspend() - { - if ($this->suspended) { - return; - } - - $this->suspended = true; - - $this->kernel->detachStrand($this); - } + public function suspend(); /** * Resume execution of this strand and send a value to the current coroutine. * * @param mixed $value The value to send to the coroutine. */ - public function resumeWithValue($value) - { - $this->resume(); - - $this->state = self::STATE_RESUME; - $this->resumeData = $value; - } + public function resumeWithValue($value); /** * Resume execution of this strand and throw an exception to the current coroutine. * * @param Exception $exception The exception to send to the coroutine. */ - public function resumeWithException(Exception $exception) - { - $this->resume(); - - $this->state = self::STATE_EXCEPTION; - $this->resumeData = $exception; - } + public function resumeWithException(Exception $exception); /** - * Terminate this execution context. + * Terminate execution of this strand. */ - public function terminate() - { - $this->resume(); - - $this->state = self::STATE_TERMINATE; - $this->resumeData = null; - } + public function terminate(); /** * Check if the strand has exited. * * @return boolean True if the strand has exited; otherwise false. */ - public function hasExited() - { - return !$this->stack; - } + public function hasExited(); /** * Perform the next unit-of-work for this strand. */ - public function tick() - { - while (!$this->suspended) { - if ($this->state === self::STATE_CALL) { - $this->state = null; - $this->current->call($this); - } elseif ($this->state === self::STATE_RESUME) { - $this->state = null; - $this->current->resumeWithValue($this, $this->resumeData); - } elseif ($this->state === self::STATE_EXCEPTION) { - $this->state = null; - $this->current->resumeWithException($this, $this->resumeData); - } elseif (self::STATE_TERMINATE === $this->state) { - $this->current->terminate($this); - - // Check if the state has been changed, if not continue with - // termination of the strand. - if ($this->state === self::STATE_TERMINATE) { - $this->pop(); - } - } else { - throw new LogicException('No action has been requested.'); - } - } - } - - /** - * Resume execution of this strand. - */ - private function resume() - { - if (!$this->suspended) { - return; - } - - $this->suspended = false; - - $this->kernel->attachStrand($this); - } - - const STATE_CALL = 1; - const STATE_RESUME = 2; - const STATE_EXCEPTION = 3; - const STATE_TERMINATE = 4; - - private $kernel; - private $suspended; - private $stack; - private $current; - private $state; - private $resumeData; + public function tick(); } diff --git a/src/Kernel/Strand/StrandFactory.php b/src/Kernel/Strand/StrandFactory.php index cc7c276..08e6624 100644 --- a/src/Kernel/Strand/StrandFactory.php +++ b/src/Kernel/Strand/StrandFactory.php @@ -1,22 +1,20 @@ execute($entryPoint()); $kernel->eventLoop()->run(); } diff --git a/src/Stream/BidirectionalStream.php b/src/Stream/BidirectionalStream.php new file mode 100644 index 0000000..f02792d --- /dev/null +++ b/src/Stream/BidirectionalStream.php @@ -0,0 +1,10 @@ +readStream = $readStream; $this->writeStream = $writeStream; diff --git a/src/Stream/BidirectionalStreamInterface.php b/src/Stream/BidirectionalStreamInterface.php deleted file mode 100644 index 8e4d5fc..0000000 --- a/src/Stream/BidirectionalStreamInterface.php +++ /dev/null @@ -1,11 +0,0 @@ -stream = $stream; + } + + /** + * [COROUTINE] Read data from the stream. + * + * Execution of the current strand is suspended until data is available or + * the end of the data stream is reached. + * + * Read operations must be exclusive. If concurrent reads are attempted a + * StreamLockedException is thrown. + * + * @param integer $length The maximum number of bytes to read. + * + * @return string The data read from the stream. + * @throws StreamClosedException if the stream is already closed. + * @throws StreamLockedException if concurrent reads are unsupported. + * @throws StreamReadException if an error occurs while reading from the stream. + */ + public function read($length) + { + if ($this->strand) { + throw new StreamLockedException(); + } elseif ($this->isClosed()) { + throw new StreamClosedException(); + } + + yield Recoil::suspend( + function ($strand) { + $this->strand = $strand; + + $strand + ->kernel() + ->eventLoop() + ->addReadStream( + $this->stream, + function ($stream, $eventLoop) { + $eventLoop->removeReadStream($stream); + $this->strand->resumeWithValue(null); + } + ); + } + ); + + $this->strand = null; + + $exception = null; + + set_error_handler( + function ($code, $message, $file, $line) use (&$exception) { + $exception = new ErrorException($message, 0, $code, $file, $line); + } + ); + + $buffer = fread($this->stream, $length); + + restore_error_handler(); + + if (is_resource($this->stream) && feof($this->stream)) { + fclose($this->stream); + } + + if (false === $buffer) { + throw new StreamReadException($exception); + } + + yield Recoil::return_($buffer); + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + + /** + * [COROUTINE] Close this stream. + * + * Closing a stream indicates that no more data will be read from the + * stream. + */ + public function close() + { + if ($this->strand) { + $this + ->strand + ->kernel() + ->eventLoop() + ->removeReadStream($this->stream); + + $this->strand->resumeWithException(new StreamClosedException()); + $this->strand = null; + } + + if (is_resource($this->stream)) { + fclose($this->stream); + } + + yield Recoil::noop(); + } + + /** + * Check if this stream is closed. + * + * @return boolean True if the stream has been closed; otherwise, false. + */ + public function isClosed() + { + return !is_resource($this->stream); + } + + private $stream; + private $strand; +} diff --git a/src/Stream/ReadableReactStream.php b/src/Stream/ReadableReactStream.php index 57870c4..fe00106 100644 --- a/src/Stream/ReadableReactStream.php +++ b/src/Stream/ReadableReactStream.php @@ -1,8 +1,9 @@ stream = $stream; $this->buffer = ''; @@ -108,7 +109,7 @@ public function isClosed() } /** - * @internal + * @access private */ public function onStreamData($data) { @@ -124,7 +125,7 @@ public function onStreamData($data) } /** - * @internal + * @access private */ public function onStreamEnd() { @@ -139,7 +140,7 @@ public function onStreamEnd() } /** - * @internal + * @access private */ public function onStreamClose() { @@ -155,7 +156,7 @@ public function onStreamClose() } /** - * @internal + * @access private */ public function onStreamError(Exception $exception) { diff --git a/src/Stream/ReadableStream.php b/src/Stream/ReadableStream.php index e964adc..edcfcbf 100644 --- a/src/Stream/ReadableStream.php +++ b/src/Stream/ReadableStream.php @@ -1,33 +1,38 @@ stream = $stream; - } - /** * [COROUTINE] Read data from the stream. * - * Execution of the current strand is suspended until data is available or - * the end of the data stream is reached. + * The implementation MUST suspend execution of the current strand until + * data is available or the end of the data stream is reached. Execution + * MAY be resumed before $length bytes have been read. + * + * If the stream is already closed a StreamClosedException MUST be thrown. + * + * If the end of the data stream is reached the implementation MUST close + * the stream such that future invocations throw a StreamClosedException and + * isClosed() returns true. * - * Read operations must be exclusive. If concurrent reads are attempted a - * StreamLockedException is thrown. + * The implementation MAY require read operations to be exclusive. If + * concurrent reads are attempted but not supported the implementation MUST + * throw a StreamLockedException. * * @param integer $length The maximum number of bytes to read. * @@ -36,94 +41,32 @@ public function __construct($stream) * @throws StreamLockedException if concurrent reads are unsupported. * @throws StreamReadException if an error occurs while reading from the stream. */ - public function read($length) - { - if ($this->strand) { - throw new StreamLockedException(); - } elseif ($this->isClosed()) { - throw new StreamClosedException(); - } - - yield Recoil::suspend( - function ($strand) { - $this->strand = $strand; - - $strand - ->kernel() - ->eventLoop() - ->addReadStream( - $this->stream, - function ($stream, $eventLoop) { - $eventLoop->removeReadStream($stream); - $this->strand->resumeWithValue(null); - } - ); - } - ); - - $this->strand = null; - - $exception = null; - - set_error_handler( - function ($code, $message, $file, $line) use (&$exception) { - $exception = new ErrorException($message, 0, $code, $file, $line); - } - ); - - $buffer = fread($this->stream, $length); - - restore_error_handler(); - - if (is_resource($this->stream) && feof($this->stream)) { - fclose($this->stream); - } - - if (false === $buffer) { - throw new StreamReadException($exception); - } - - yield Recoil::return_($buffer); - // @codeCoverageIgnoreStart - } - // @codeCoverageIgnoreEnd + public function read($length); /** * [COROUTINE] Close this stream. * * Closing a stream indicates that no more data will be read from the - * stream. + * stream. Once a stream is closed future, invocations of read() MUST throw + * a StreamClosedException. + * + * The implementation SHOULD NOT throw an exception if close() is called on + * an already-closed stream. + * + * The implementation SHOULD support closing the stream while a read + * operation is in progress, otherwise StreamLockedException MUST be thrown. + * + * @throws StreamLockedException if the stream can not be closed due to a pending read operation. */ - public function close() - { - if ($this->strand) { - $this - ->strand - ->kernel() - ->eventLoop() - ->removeReadStream($this->stream); - - $this->strand->resumeWithException(new StreamClosedException()); - $this->strand = null; - } - - if (is_resource($this->stream)) { - fclose($this->stream); - } - - yield Recoil::noop(); - } + public function close(); /** * Check if this stream is closed. * + * The implementation MUST return true after close() has been called or if + * the end of the data stream has been reached. + * * @return boolean True if the stream has been closed; otherwise, false. */ - public function isClosed() - { - return !is_resource($this->stream); - } - - private $stream; - private $strand; + public function isClosed(); } diff --git a/src/Stream/ReadableStreamInterface.php b/src/Stream/ReadableStreamInterface.php deleted file mode 100644 index 3b9045f..0000000 --- a/src/Stream/ReadableStreamInterface.php +++ /dev/null @@ -1,71 +0,0 @@ -stream = $stream; + } + + /** + * [COROUTINE] Write data to this stream. + * + * Execution of the current strand is suspended until the data is sent. + * + * Write operations must be exclusive. If concurrent writes are attempted a + * StreamLockedException is thrown. + * + * @param string $buffer The data to write to the stream. + * @param integer|null $length The maximum number of bytes to write. + * + * @return integer The number of bytes written. + * @throws StreamClosedException if the stream is already closed. + * @throws StreamLockedException if concurrent writes are unsupported. + * @throws StreamWriteException if an error occurs while writing to the stream. + */ + public function write($buffer, $length = null) + { + if ($this->strand) { + throw new StreamLockedException(); + } elseif ($this->isClosed()) { + throw new StreamClosedException(); + } + + yield Recoil::suspend( + function ($strand) { + $this->strand = $strand; + + $strand + ->kernel() + ->eventLoop() + ->addWriteStream( + $this->stream, + function ($stream, $eventLoop) use ($strand) { + $eventLoop->removeWriteStream($this->stream); + $this->strand->resumeWithValue(null); + } + ); + } + ); + + $this->strand = null; + + $exception = null; + + set_error_handler( + function ($code, $message, $file, $line) use (&$exception) { + $exception = new ErrorException($message, 0, $code, $file, $line); + } + ); + + $bytesWritten = fwrite( + $this->stream, + $buffer, + $length ?: strlen($buffer) + ); + + restore_error_handler(); + + if (false === $bytesWritten) { + throw new StreamWriteException($exception); + } + + yield Recoil::return_($bytesWritten); + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + + /** + * [COROUTINE] Write all data from the given buffer to this stream. + * + * Execution of the current strand is suspended until the data is sent. + * + * Write operations must be exclusive. If concurrent writes are attempted a + * StreamLockedException is thrown. + * + * @param string $buffer The data to write to the stream. + * + * @throws StreamClosedException if the stream is already closed. + * @throws StreamLockedException if concurrent writes are unsupported. + * @throws StreamWriteException if an error occurs while writing to the stream. + */ + public function writeAll($buffer) + { + while ($buffer) { + $bytesWritten = (yield $this->write($buffer)); + $buffer = substr($buffer, $bytesWritten); + } + } + + /** + * [COROUTINE] Close this stream. + * + * Closing a stream indicates that no more data will be written to the + * stream. + */ + public function close() + { + if ($this->strand) { + $this + ->strand + ->kernel() + ->eventLoop() + ->removeWriteStream($this->stream); + + $this->strand->resumeWithException(new StreamClosedException()); + $this->strand = null; + } + + if (is_resource($this->stream)) { + fclose($this->stream); + } + + yield Recoil::noop(); + } + + /** + * Check if this stream is closed. + * + * @return boolean True if the stream has been closed; otherwise, false. + */ + public function isClosed() + { + return !is_resource($this->stream); + } + + private $stream; + private $strand; +} diff --git a/src/Stream/WritableReactStream.php b/src/Stream/WritableReactStream.php index 7413631..8a3b59c 100644 --- a/src/Stream/WritableReactStream.php +++ b/src/Stream/WritableReactStream.php @@ -1,8 +1,9 @@ stream = $stream; @@ -129,7 +130,7 @@ public function isClosed() } /** - * @internal + * @access private */ public function onStreamDrain() { @@ -139,7 +140,7 @@ public function onStreamDrain() } /** - * @internal + * @access private */ public function onStreamError(Exception $exception) { diff --git a/src/Stream/WritableStream.php b/src/Stream/WritableStream.php index 8bcfe24..a1e9576 100644 --- a/src/Stream/WritableStream.php +++ b/src/Stream/WritableStream.php @@ -1,32 +1,34 @@ stream = $stream; - } - /** * [COROUTINE] Write data to this stream. * - * Execution of the current strand is suspended until the data is sent. + * The implementation MAY suspend execution of the current strand until the + * data is sent. * - * Write operations must be exclusive. If concurrent writes are attempted a - * StreamLockedException is thrown. + * If the stream is already closed, or is closed while a write operation is + * pending the implementation MUST throw a StreamClosedException. + * + * The implementation MAY require write operations to be exclusive. If + * concurrent writes are attempted but not supported the implementation MUST + * throw a StreamLockedException. * * @param string $buffer The data to write to the stream. * @param integer|null $length The maximum number of bytes to write. @@ -36,116 +38,54 @@ public function __construct($stream) * @throws StreamLockedException if concurrent writes are unsupported. * @throws StreamWriteException if an error occurs while writing to the stream. */ - public function write($buffer, $length = null) - { - if ($this->strand) { - throw new StreamLockedException(); - } elseif ($this->isClosed()) { - throw new StreamClosedException(); - } - - yield Recoil::suspend( - function ($strand) { - $this->strand = $strand; - - $strand - ->kernel() - ->eventLoop() - ->addWriteStream( - $this->stream, - function ($stream, $eventLoop) use ($strand) { - $eventLoop->removeWriteStream($this->stream); - $this->strand->resumeWithValue(null); - } - ); - } - ); - - $this->strand = null; - - $exception = null; - - set_error_handler( - function ($code, $message, $file, $line) use (&$exception) { - $exception = new ErrorException($message, 0, $code, $file, $line); - } - ); - - $bytesWritten = fwrite( - $this->stream, - $buffer, - $length ?: strlen($buffer) - ); - - restore_error_handler(); - - if (false === $bytesWritten) { - throw new StreamWriteException($exception); - } - - yield Recoil::return_($bytesWritten); - // @codeCoverageIgnoreStart - } - // @codeCoverageIgnoreEnd + public function write($buffer, $length = null); /** * [COROUTINE] Write all data from the given buffer to this stream. * - * Execution of the current strand is suspended until the data is sent. + * The implementation MAY suspend execution of the current strand until the + * data is sent. + * + * If the stream is already closed, or is closed while a write operation is + * pending the implementation MUST throw a StreamClosedException. * - * Write operations must be exclusive. If concurrent writes are attempted a - * StreamLockedException is thrown. + * The implementation MAY require write operations to be exclusive. If + * concurrent writes are attempted but not supported the implementation MUST + * throw a StreamLockedException. * - * @param string $buffer The data to write to the stream. + * @param string $buffer The data to write to the stream. + * @param integer|null $length The maximum number of bytes to write. * * @throws StreamClosedException if the stream is already closed. * @throws StreamLockedException if concurrent writes are unsupported. * @throws StreamWriteException if an error occurs while writing to the stream. */ - public function writeAll($buffer) - { - while ($buffer) { - $bytesWritten = (yield $this->write($buffer)); - $buffer = substr($buffer, $bytesWritten); - } - } + public function writeAll($buffer); /** * [COROUTINE] Close this stream. * - * Closing a stream indicates that no more data will be written to the - * stream. + * Closing a stream indicates that no more data will be written. Once a + * stream is closed future invocations of write() MUST throw + * a StreamClosedException. + * + * The implementation SHOULD NOT throw an exception if close() is called on + * an already-closed stream. + * + * The implementation SHOULD support closing the stream while a write + * operation is in progress, otherwise StreamLockedException MUST be thrown. + * + * @throws StreamLockedException if the stream can not be closed due to a pending write operation. */ - public function close() - { - if ($this->strand) { - $this - ->strand - ->kernel() - ->eventLoop() - ->removeWriteStream($this->stream); - - $this->strand->resumeWithException(new StreamClosedException()); - $this->strand = null; - } - - if (is_resource($this->stream)) { - fclose($this->stream); - } - - yield Recoil::noop(); - } + public function close(); /** * Check if this stream is closed. * + * The implementation MUST return true after close() has been called or the + * stream is closed during a write operation. + * * @return boolean True if the stream has been closed; otherwise, false. */ - public function isClosed() - { - return !is_resource($this->stream); - } - - private $stream; - private $strand; + public function isClosed(); } diff --git a/src/Stream/WritableStreamInterface.php b/src/Stream/WritableStreamInterface.php deleted file mode 100644 index 931fb99..0000000 --- a/src/Stream/WritableStreamInterface.php +++ /dev/null @@ -1,90 +0,0 @@ -readChannel = Phake::mock(ReadableChannelInterface::class); - $this->writeChannel = Phake::mock(WritableChannelInterface::class); + $this->readChannel = Phake::mock(ReadableChannel::class); + $this->writeChannel = Phake::mock(WritableChannel::class); $this->adaptor = new BidirectionalChannelAdaptor( $this->readChannel, $this->writeChannel diff --git a/test/suite/Channel/ChannelTest.php b/test/suite/Channel/ChannelTest.php index 20e4d33..5497720 100644 --- a/test/suite/Channel/ChannelTest.php +++ b/test/suite/Channel/ChannelTest.php @@ -1,9 +1,10 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); $this->channel = new Channel(); } diff --git a/test/suite/Channel/Exception/ChannelClosedExceptionTest.php b/test/suite/Channel/Exception/ChannelClosedExceptionTest.php index 2e3de9c..9040d05 100644 --- a/test/suite/Channel/Exception/ChannelClosedExceptionTest.php +++ b/test/suite/Channel/Exception/ChannelClosedExceptionTest.php @@ -1,4 +1,5 @@ path = tempnam(sys_get_temp_dir(), 'recoil-'); $this->resource = fopen($this->path, 'r+'); - $this->stream = new ReadableStream($this->resource); + $this->stream = new ReadablePhpStream($this->resource); $this->channel = new ReadableStreamChannel($this->stream); $this->serializer = new PhpSerializer(); } diff --git a/test/suite/Channel/Serialization/PhpSerializerTest.php b/test/suite/Channel/Serialization/PhpSerializerTest.php index 4ccc340..0f07e0d 100644 --- a/test/suite/Channel/Serialization/PhpSerializerTest.php +++ b/test/suite/Channel/Serialization/PhpSerializerTest.php @@ -1,4 +1,5 @@ path = tempnam(sys_get_temp_dir(), 'recoil-'); $this->resource = fopen($this->path, 'w'); - $this->stream = new WritableStream($this->resource); + $this->stream = new WritablePhpStream($this->resource); $this->channel = new WritableStreamChannel($this->stream); $this->unserializer = new PhpUnserializer(); } diff --git a/test/suite/Coroutine/Exception/PromiseRejectedExceptionTest.php b/test/suite/Coroutine/Exception/PromiseRejectedExceptionTest.php index 625aa5d..9e7a0b7 100644 --- a/test/suite/Coroutine/Exception/PromiseRejectedExceptionTest.php +++ b/test/suite/Coroutine/Exception/PromiseRejectedExceptionTest.php @@ -1,4 +1,5 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); } public function testCall() diff --git a/test/suite/Coroutine/PromiseCoroutineTest.php b/test/suite/Coroutine/PromiseCoroutineTest.php index 47bce7f..eeb42d9 100644 --- a/test/suite/Coroutine/PromiseCoroutineTest.php +++ b/test/suite/Coroutine/PromiseCoroutineTest.php @@ -1,4 +1,5 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); } public function testFulfilledPromise() @@ -27,7 +28,8 @@ public function testFulfilledPromise() $value = null; $coroutine = function () use (&$value) { $value = (yield new PromiseCoroutine( - new FulfilledPromise(123) + new FulfilledPromise(123), + false )); }; @@ -44,7 +46,8 @@ public function testRejectedPromise() $coroutine = function () use (&$exception) { try { yield new PromiseCoroutine( - new RejectedPromise(new Exception('This is the exception.')) + new RejectedPromise(new Exception('This is the exception.')), + false ); } catch (Exception $e) { $exception = $e; @@ -65,7 +68,8 @@ public function testRejectedPromiseWithNonExceptionReason() $coroutine = function () use (&$exception) { try { yield new PromiseCoroutine( - new RejectedPromise('This is the exception.') + new RejectedPromise('This is the exception.'), + false ); } catch (PromiseRejectedException $e) { $exception = $e; @@ -84,7 +88,7 @@ public function testTerminateThenFulfill() { $deferred = new Deferred(); $promise = $deferred->promise(); - $promiseCoroutine = new PromiseCoroutine($promise); + $promiseCoroutine = new PromiseCoroutine($promise, false); $resumed = null; $coroutine = function () use (&$resumed, $promiseCoroutine) { @@ -116,7 +120,7 @@ public function testTerminateThenReject() }; $promise = new Promise(function () {}, $promiseCanceller); - $promiseCoroutine = new PromiseCoroutine($promise); + $promiseCoroutine = new PromiseCoroutine($promise, true); $coroutine = function () use ($promiseCoroutine) { yield $promiseCoroutine; diff --git a/test/suite/Coroutine/CoroutineAdaptorTest.php b/test/suite/Coroutine/StandardCoroutineAdaptorTest.php similarity index 64% rename from test/suite/Coroutine/CoroutineAdaptorTest.php rename to test/suite/Coroutine/StandardCoroutineAdaptorTest.php index 050ab56..6da0299 100644 --- a/test/suite/Coroutine/CoroutineAdaptorTest.php +++ b/test/suite/Coroutine/StandardCoroutineAdaptorTest.php @@ -1,23 +1,26 @@ strand = Phake::mock(StrandInterface::class); - $this->adaptor = new CoroutineAdaptor(); + $this->strand = Phake::mock(Strand::class); + $this->adaptor = new StandardCoroutineAdaptor(); } public function testAdaptPassThru() { - $coroutine = Phake::mock(CoroutineInterface::class); + $coroutine = Phake::mock(Coroutine::class); $this->assertSame( $coroutine, @@ -46,7 +49,32 @@ public function testAdaptWithPromise() $promise = Phake::mock(PromiseInterface::class); $coroutine = $this->adaptor->adapt($this->strand, $promise); - $this->assertInstanceOf(PromiseCoroutine::class, $coroutine); + $this->assertEquals( + new PromiseCoroutine($promise, false), + $coroutine + ); + } + + public function testAdaptWithCancellablePromise() + { + $promise = Phake::mock(CancellablePromiseInterface::class); + $coroutine = $this->adaptor->adapt($this->strand, $promise); + + $this->assertEquals( + new PromiseCoroutine($promise, true), + $coroutine + ); + } + + public function testAdaptWithGuzzlePromise() + { + $promise = Phake::mock(GuzzlePromiseInterface::class); + $coroutine = $this->adaptor->adapt($this->strand, $promise); + + $this->assertEquals( + new PromiseCoroutine($promise, true), + $coroutine + ); } public function testAdaptWithArray() @@ -78,8 +106,8 @@ public function testAdaptFailure() public function testAdaptProvider() { - $provider = Phake::mock(CoroutineProviderInterface::class); - $coroutine = Phake::mock(CoroutineInterface::class); + $provider = Phake::mock(CoroutineProvider::class); + $coroutine = Phake::mock(Coroutine::class); Phake::when($provider) ->coroutine($this->identicalTo($this->strand)) @@ -93,8 +121,8 @@ public function testAdaptProvider() public function testAdaptNestedProvider() { - $provider = Phake::mock(CoroutineProviderInterface::class); - $coroutine = Phake::mock(CoroutineInterface::class); + $provider = Phake::mock(CoroutineProvider::class); + $coroutine = Phake::mock(Coroutine::class); Phake::when($provider) ->coroutine($this->identicalTo($this->strand)) diff --git a/test/suite/Kernel/Api/KernelApiCallTest.php b/test/suite/Kernel/Api/KernelApiCallTest.php index f1e6d5f..bceb397 100644 --- a/test/suite/Kernel/Api/KernelApiCallTest.php +++ b/test/suite/Kernel/Api/KernelApiCallTest.php @@ -1,19 +1,20 @@ api = Phake::partialMock(KernelApi::class); - $this->kernel = new Kernel(null, $this->api); + $this->api = Phake::partialMock(StandardKernelApi::class); + $this->kernel = new StandardKernel(null, $this->api); } public function testTick() diff --git a/test/suite/Kernel/Api/SelectTest.php b/test/suite/Kernel/Api/SelectTest.php index 8a14bc3..4acb5ec 100644 --- a/test/suite/Kernel/Api/SelectTest.php +++ b/test/suite/Kernel/Api/SelectTest.php @@ -1,15 +1,16 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); } public function testSelect() diff --git a/test/suite/Kernel/Api/SleepTest.php b/test/suite/Kernel/Api/SleepTest.php index cf7f055..8ad25c9 100644 --- a/test/suite/Kernel/Api/SleepTest.php +++ b/test/suite/Kernel/Api/SleepTest.php @@ -1,15 +1,16 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); $this->tolerance = 0.02; } diff --git a/test/suite/Kernel/Api/KernelApiTest.php b/test/suite/Kernel/Api/StandardKernelApiTest.php similarity index 86% rename from test/suite/Kernel/Api/KernelApiTest.php rename to test/suite/Kernel/Api/StandardKernelApiTest.php index cede9fe..dcd4ec6 100644 --- a/test/suite/Kernel/Api/KernelApiTest.php +++ b/test/suite/Kernel/Api/StandardKernelApiTest.php @@ -1,22 +1,23 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); $this->tolerance = 0.02; } @@ -211,6 +212,20 @@ public function testSuspend() { $this->expectOutputString(''); + $coroutine = function () { + yield Recoil::suspend(); + echo 'X'; + }; + + $expectedStrand = $this->kernel->execute($coroutine()); + + $this->kernel->eventLoop()->run(); + } + + public function testSuspendWithCallback() + { + $this->expectOutputString(''); + $strand = null; $coroutine = function () use (&$strand) { @@ -348,7 +363,49 @@ public function testExecute() $this->kernel->eventLoop()->run(); - $this->assertInstanceOf(StrandInterface::class, $strand); + $this->assertInstanceOf(Strand::class, $strand); + } + + public function testCallback() + { + $this->expectOutputString('123'); + + $coroutine = function () { + $f = function () { + echo 3; + yield Recoil::noop(); + }; + + echo 1; + $callback = (yield Recoil::callback($f())); + $callback(); + echo 2; + }; + + $this->kernel->execute($coroutine()); + + $this->kernel->eventLoop()->run(); + } + + public function testCallbackWithCallable() + { + $this->expectOutputString('123'); + + $coroutine = function () { + $f = function ($value) { + echo $value; + yield Recoil::noop(); + }; + + echo 1; + $callback = (yield Recoil::callback($f)); + $callback(3); + echo 2; + }; + + $this->kernel->execute($coroutine()); + + $this->kernel->eventLoop()->run(); } public function testStop() diff --git a/test/suite/Kernel/Api/TimeoutTest.php b/test/suite/Kernel/Api/TimeoutTest.php index 70360e2..0152c11 100644 --- a/test/suite/Kernel/Api/TimeoutTest.php +++ b/test/suite/Kernel/Api/TimeoutTest.php @@ -1,17 +1,18 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); } public function testTimeout() diff --git a/test/suite/Kernel/Api/WaitAllTest.php b/test/suite/Kernel/Api/WaitAllTest.php index f16f0c1..6b2c140 100644 --- a/test/suite/Kernel/Api/WaitAllTest.php +++ b/test/suite/Kernel/Api/WaitAllTest.php @@ -1,4 +1,5 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); } /** diff --git a/test/suite/Kernel/KernelTest.php b/test/suite/Kernel/StandardKernelTest.php similarity index 59% rename from test/suite/Kernel/KernelTest.php rename to test/suite/Kernel/StandardKernelTest.php index 6b7efa8..c6dc47b 100644 --- a/test/suite/Kernel/KernelTest.php +++ b/test/suite/Kernel/StandardKernelTest.php @@ -1,25 +1,26 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); } public function testConstructorDefaults() { - $this->assertInstanceOf(KernelApi::class, $this->kernel->api()); - $this->assertInstanceOf(CoroutineAdaptor::class, $this->kernel->coroutineAdaptor()); - $this->assertInstanceOf(StrandFactory::class, $this->kernel->strandFactory()); + $this->assertInstanceOf(StandardKernelApi::class, $this->kernel->api()); + $this->assertInstanceOf(StandardCoroutineAdaptor::class, $this->kernel->coroutineAdaptor()); + $this->assertInstanceOf(StandardStrandFactory::class, $this->kernel->strandFactory()); $this->assertInstanceOf(LoopInterface::class, $this->kernel->eventLoop()); } diff --git a/test/suite/Kernel/Strand/StrandFunctionalTest.php b/test/suite/Kernel/Strand/StandardStrandFunctionalTest.php similarity index 97% rename from test/suite/Kernel/Strand/StrandFunctionalTest.php rename to test/suite/Kernel/Strand/StandardStrandFunctionalTest.php index 47868a1..6048b4c 100644 --- a/test/suite/Kernel/Strand/StrandFunctionalTest.php +++ b/test/suite/Kernel/Strand/StandardStrandFunctionalTest.php @@ -1,19 +1,20 @@ kernel = new Kernel(); + $this->kernel = new StandardKernel(); } /** @@ -329,7 +330,7 @@ public function testTerminateEvent() public function testTickWithoutAction() { - $strand = new Strand($this->kernel); + $strand = new StandardStrand($this->kernel); $this->setExpectedException( 'LogicException', diff --git a/test/suite/RecoilTest.php b/test/suite/RecoilTest.php index f7dd268..c000144 100644 --- a/test/suite/RecoilTest.php +++ b/test/suite/RecoilTest.php @@ -1,4 +1,5 @@ readStream = Phake::mock(ReadableStreamInterface::class); - $this->writeStream = Phake::mock(WritableStreamInterface::class); + $this->readStream = Phake::mock(ReadableStream::class); + $this->writeStream = Phake::mock(WritableStream::class); $this->adaptor = new BidirectionalStreamAdaptor( $this->readStream, $this->writeStream diff --git a/test/suite/Stream/Exception/StreamClosedExceptionTest.php b/test/suite/Stream/Exception/StreamClosedExceptionTest.php index d4bede6..9d1afb8 100644 --- a/test/suite/Stream/Exception/StreamClosedExceptionTest.php +++ b/test/suite/Stream/Exception/StreamClosedExceptionTest.php @@ -1,4 +1,5 @@ resource); + return new ReadablePhpStream($this->resource); } public function testReadFailure() diff --git a/test/suite/Stream/ReadableReactStreamTest.php b/test/suite/Stream/ReadableReactStreamTest.php index 195d250..a705e01 100644 --- a/test/suite/Stream/ReadableReactStreamTest.php +++ b/test/suite/Stream/ReadableReactStreamTest.php @@ -1,9 +1,10 @@ resource); + return new WritablePhpStream($this->resource); } } diff --git a/test/suite/Stream/WritableReactStreamTest.php b/test/suite/Stream/WritableReactStreamTest.php index d07a7b9..204c1f8 100644 --- a/test/suite/Stream/WritableReactStreamTest.php +++ b/test/suite/Stream/WritableReactStreamTest.php @@ -1,4 +1,5 @@