Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

96
vendor/react/cache/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,96 @@
# Changelog
## 1.2.0 (2022-11-30)
* Feature: Support PHP 8.1 and PHP 8.2.
(#47 by @SimonFrings and #52 by @WyriHaximus)
* Minor documentation improvements.
(#48 by @SimonFrings and #51 by @nhedger)
* Update test suite and use GitHub actions for continuous integration (CI).
(#45 and #49 by @SimonFrings and #54 by @clue)
## 1.1.0 (2020-09-18)
* Feature: Forward compatibility with react/promise 3.
(#39 by @WyriHaximus)
* Add `.gitattributes` to exclude dev files from exports.
(#40 by @reedy)
* Improve test suite, update to support PHP 8 and PHPUnit 9.3.
(#41 and #43 by @SimonFrings and #42 by @WyriHaximus)
## 1.0.0 (2019-07-11)
* First stable LTS release, now following [SemVer](https://semver.org/).
We'd like to emphasize that this component is production ready and battle-tested.
We plan to support all long-term support (LTS) releases for at least 24 months,
so you have a rock-solid foundation to build on top of.
> Contains no other changes, so it's actually fully compatible with the v0.6.0 release.
## 0.6.0 (2019-07-04)
* Feature / BC break: Add support for `getMultiple()`, `setMultiple()`, `deleteMultiple()`, `clear()` and `has()`
supporting multiple cache items (inspired by PSR-16).
(#32 by @krlv and #37 by @clue)
* Documentation for TTL precision with millisecond accuracy or below and
use high-resolution timer for cache TTL on PHP 7.3+.
(#35 and #38 by @clue)
* Improve API documentation and allow legacy HHVM to fail in Travis CI config.
(#34 and #36 by @clue)
* Prefix all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
(#31 by @WyriHaximus)
## 0.5.0 (2018-06-25)
* Improve documentation by describing what is expected of a class implementing `CacheInterface`.
(#21, #22, #23, #27 by @WyriHaximus)
* Implemented (optional) Least Recently Used (LRU) cache algorithm for `ArrayCache`.
(#26 by @clue)
* Added support for cache expiration (TTL).
(#29 by @clue and @WyriHaximus)
* Renamed `remove` to `delete` making it more in line with `PSR-16`.
(#30 by @clue)
## 0.4.2 (2017-12-20)
* Improve documentation with usage and installation instructions
(#10 by @clue)
* Improve test suite by adding PHPUnit to `require-dev` and
add forward compatibility with PHPUnit 5 and PHPUnit 6 and
sanitize Composer autoload paths
(#14 by @shaunbramley and #12 and #18 by @clue)
## 0.4.1 (2016-02-25)
* Repository maintenance, split off from main repo, improve test suite and documentation
* First class support for PHP7 and HHVM (#9 by @clue)
* Adjust compatibility to 5.3 (#7 by @clue)
## 0.4.0 (2014-02-02)
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
* BC break: Update to React/Promise 2.0
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
## 0.3.2 (2013-05-10)
* Version bump
## 0.3.0 (2013-04-14)
* Version bump
## 0.2.6 (2012-12-26)
* Feature: New cache component, used by DNS

21
vendor/react/cache/LICENSE vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

367
vendor/react/cache/README.md vendored Executable file
View File

@@ -0,0 +1,367 @@
# Cache
[![CI status](https://github.com/reactphp/cache/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/cache/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/cache?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/cache)
Async, [Promise](https://github.com/reactphp/promise)-based cache interface
for [ReactPHP](https://reactphp.org/).
The cache component provides a
[Promise](https://github.com/reactphp/promise)-based
[`CacheInterface`](#cacheinterface) and an in-memory [`ArrayCache`](#arraycache)
implementation of that.
This allows consumers to type hint against the interface and third parties to
provide alternate implementations.
This project is heavily inspired by
[PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/),
but uses an interface more suited for async, non-blocking applications.
**Table of Contents**
* [Usage](#usage)
* [CacheInterface](#cacheinterface)
* [get()](#get)
* [set()](#set)
* [delete()](#delete)
* [getMultiple()](#getmultiple)
* [setMultiple()](#setmultiple)
* [deleteMultiple()](#deletemultiple)
* [clear()](#clear)
* [has()](#has)
* [ArrayCache](#arraycache)
* [Common usage](#common-usage)
* [Fallback get](#fallback-get)
* [Fallback-get-and-set](#fallback-get-and-set)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
## Usage
### CacheInterface
The `CacheInterface` describes the main interface of this component.
This allows consumers to type hint against the interface and third parties to
provide alternate implementations.
#### get()
The `get(string $key, mixed $default = null): PromiseInterface<mixed>` method can be used to
retrieve an item from the cache.
This method will resolve with the cached value on success or with the
given `$default` value when no item can be found or when an error occurs.
Similarly, an expired cache item (once the time-to-live is expired) is
considered a cache miss.
```php
$cache
->get('foo')
->then('var_dump');
```
This example fetches the value of the key `foo` and passes it to the
`var_dump` function. You can use any of the composition provided by
[promises](https://github.com/reactphp/promise).
#### set()
The `set(string $key, mixed $value, ?float $ttl = null): PromiseInterface<bool>` method can be used to
store an item in the cache.
This method will resolve with `true` on success or `false` when an error
occurs. If the cache implementation has to go over the network to store
it, it may take a while.
The optional `$ttl` parameter sets the maximum time-to-live in seconds
for this cache item. If this parameter is omitted (or `null`), the item
will stay in the cache for as long as the underlying implementation
supports. Trying to access an expired cache item results in a cache miss,
see also [`get()`](#get).
```php
$cache->set('foo', 'bar', 60);
```
This example eventually sets the value of the key `foo` to `bar`. If it
already exists, it is overridden.
This interface does not enforce any particular TTL resolution, so special
care may have to be taken if you rely on very high precision with
millisecond accuracy or below. Cache implementations SHOULD work on a
best effort basis and SHOULD provide at least second accuracy unless
otherwise noted. Many existing cache implementations are known to provide
microsecond or millisecond accuracy, but it's generally not recommended
to rely on this high precision.
This interface suggests that cache implementations SHOULD use a monotonic
time source if available. Given that a monotonic time source is only
available as of PHP 7.3 by default, cache implementations MAY fall back
to using wall-clock time.
While this does not affect many common use cases, this is an important
distinction for programs that rely on a high time precision or on systems
that are subject to discontinuous time adjustments (time jumps).
This means that if you store a cache item with a TTL of 30s and then
adjust your system time forward by 20s, the cache item SHOULD still
expire in 30s.
#### delete()
The `delete(string $key): PromiseInterface<bool>` method can be used to
delete an item from the cache.
This method will resolve with `true` on success or `false` when an error
occurs. When no item for `$key` is found in the cache, it also resolves
to `true`. If the cache implementation has to go over the network to
delete it, it may take a while.
```php
$cache->delete('foo');
```
This example eventually deletes the key `foo` from the cache. As with
`set()`, this may not happen instantly and a promise is returned to
provide guarantees whether or not the item has been removed from cache.
#### getMultiple()
The `getMultiple(string[] $keys, mixed $default = null): PromiseInterface<array>` method can be used to
retrieve multiple cache items by their unique keys.
This method will resolve with an array of cached values on success or with the
given `$default` value when an item can not be found or when an error occurs.
Similarly, an expired cache item (once the time-to-live is expired) is
considered a cache miss.
```php
$cache->getMultiple(array('name', 'age'))->then(function (array $values) {
$name = $values['name'] ?? 'User';
$age = $values['age'] ?? 'n/a';
echo $name . ' is ' . $age . PHP_EOL;
});
```
This example fetches the cache items for the `name` and `age` keys and
prints some example output. You can use any of the composition provided
by [promises](https://github.com/reactphp/promise).
#### setMultiple()
The `setMultiple(array $values, ?float $ttl = null): PromiseInterface<bool>` method can be used to
persist a set of key => value pairs in the cache, with an optional TTL.
This method will resolve with `true` on success or `false` when an error
occurs. If the cache implementation has to go over the network to store
it, it may take a while.
The optional `$ttl` parameter sets the maximum time-to-live in seconds
for these cache items. If this parameter is omitted (or `null`), these items
will stay in the cache for as long as the underlying implementation
supports. Trying to access an expired cache items results in a cache miss,
see also [`getMultiple()`](#getmultiple).
```php
$cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
```
This example eventually sets the list of values - the key `foo` to `1` value
and the key `bar` to `2`. If some of the keys already exist, they are overridden.
#### deleteMultiple()
The `setMultiple(string[] $keys): PromiseInterface<bool>` method can be used to
delete multiple cache items in a single operation.
This method will resolve with `true` on success or `false` when an error
occurs. When no items for `$keys` are found in the cache, it also resolves
to `true`. If the cache implementation has to go over the network to
delete it, it may take a while.
```php
$cache->deleteMultiple(array('foo', 'bar, 'baz'));
```
This example eventually deletes keys `foo`, `bar` and `baz` from the cache.
As with `setMultiple()`, this may not happen instantly and a promise is returned to
provide guarantees whether or not the item has been removed from cache.
#### clear()
The `clear(): PromiseInterface<bool>` method can be used to
wipe clean the entire cache.
This method will resolve with `true` on success or `false` when an error
occurs. If the cache implementation has to go over the network to
delete it, it may take a while.
```php
$cache->clear();
```
This example eventually deletes all keys from the cache. As with `deleteMultiple()`,
this may not happen instantly and a promise is returned to provide guarantees
whether or not all the items have been removed from cache.
#### has()
The `has(string $key): PromiseInterface<bool>` method can be used to
determine whether an item is present in the cache.
This method will resolve with `true` on success or `false` when no item can be found
or when an error occurs. Similarly, an expired cache item (once the time-to-live
is expired) is considered a cache miss.
```php
$cache
->has('foo')
->then('var_dump');
```
This example checks if the value of the key `foo` is set in the cache and passes
the result to the `var_dump` function. You can use any of the composition provided by
[promises](https://github.com/reactphp/promise).
NOTE: It is recommended that has() is only to be used for cache warming type purposes
and not to be used within your live applications operations for get/set, as this method
is subject to a race condition where your has() will return true and immediately after,
another script can remove it making the state of your app out of date.
### ArrayCache
The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
```php
$cache = new ArrayCache();
$cache->set('foo', 'bar');
```
Its constructor accepts an optional `?int $limit` parameter to limit the
maximum number of entries to store in the LRU cache. If you add more
entries to this instance, it will automatically take care of removing
the one that was least recently used (LRU).
For example, this snippet will overwrite the first value and only store
the last two entries:
```php
$cache = new ArrayCache(2);
$cache->set('foo', '1');
$cache->set('bar', '2');
$cache->set('baz', '3');
```
This cache implementation is known to rely on wall-clock time to schedule
future cache expiration times when using any version before PHP 7.3,
because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
While this does not affect many common use cases, this is an important
distinction for programs that rely on a high time precision or on systems
that are subject to discontinuous time adjustments (time jumps).
This means that if you store a cache item with a TTL of 30s on PHP < 7.3
and then adjust your system time forward by 20s, the cache item may
expire in 10s. See also [`set()`](#set) for more details.
## Common usage
### Fallback get
A common use case of caches is to attempt fetching a cached value and as a
fallback retrieve it from the original data source if not found. Here is an
example of that:
```php
$cache
->get('foo')
->then(function ($result) {
if ($result === null) {
return getFooFromDb();
}
return $result;
})
->then('var_dump');
```
First an attempt is made to retrieve the value of `foo`. A callback function is
registered that will call `getFooFromDb` when the resulting value is null.
`getFooFromDb` is a function (can be any PHP callable) that will be called if the
key does not exist in the cache.
`getFooFromDb` can handle the missing key by returning a promise for the
actual value from the database (or any other data source). As a result, this
chain will correctly fall back, and provide the value in both cases.
### Fallback get and set
To expand on the fallback get example, often you want to set the value on the
cache after fetching it from the data source.
```php
$cache
->get('foo')
->then(function ($result) {
if ($result === null) {
return $this->getAndCacheFooFromDb();
}
return $result;
})
->then('var_dump');
public function getAndCacheFooFromDb()
{
return $this->db
->get('foo')
->then(array($this, 'cacheFooFromDb'));
}
public function cacheFooFromDb($foo)
{
$this->cache->set('foo', $foo);
return $foo;
}
```
By using chaining you can easily conditionally cache the value if it is
fetched from the database.
## Install
The recommended way to install this library is [through Composer](https://getcomposer.org).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This project follows [SemVer](https://semver.org/).
This will install the latest supported version:
```bash
composer require react/cache:^1.2
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
HHVM.
It's *highly recommended to use PHP 7+* for this project.
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org):
```bash
composer install
```
To run the test suite, go to the project root and run:
```bash
vendor/bin/phpunit
```
## License
MIT, see [LICENSE file](LICENSE).

45
vendor/react/cache/composer.json vendored Executable file
View File

@@ -0,0 +1,45 @@
{
"name": "react/cache",
"description": "Async, Promise-based cache interface for ReactPHP",
"keywords": ["cache", "caching", "promise", "ReactPHP"],
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"homepage": "https://clue.engineering/",
"email": "christian@clue.engineering"
},
{
"name": "Cees-Jan Kiewiet",
"homepage": "https://wyrihaximus.net/",
"email": "reactphp@ceesjankiewiet.nl"
},
{
"name": "Jan Sorgalla",
"homepage": "https://sorgalla.com/",
"email": "jsorgalla@gmail.com"
},
{
"name": "Chris Boden",
"homepage": "https://cboden.dev/",
"email": "cboden@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"react/promise": "^3.0 || ^2.0 || ^1.1"
},
"require-dev": {
"phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
},
"autoload": {
"psr-4": {
"React\\Cache\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"React\\Tests\\Cache\\": "tests/"
}
}
}

181
vendor/react/cache/src/ArrayCache.php vendored Executable file
View File

@@ -0,0 +1,181 @@
<?php
namespace React\Cache;
use React\Promise;
use React\Promise\PromiseInterface;
class ArrayCache implements CacheInterface
{
private $limit;
private $data = array();
private $expires = array();
private $supportsHighResolution;
/**
* The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
*
* ```php
* $cache = new ArrayCache();
*
* $cache->set('foo', 'bar');
* ```
*
* Its constructor accepts an optional `?int $limit` parameter to limit the
* maximum number of entries to store in the LRU cache. If you add more
* entries to this instance, it will automatically take care of removing
* the one that was least recently used (LRU).
*
* For example, this snippet will overwrite the first value and only store
* the last two entries:
*
* ```php
* $cache = new ArrayCache(2);
*
* $cache->set('foo', '1');
* $cache->set('bar', '2');
* $cache->set('baz', '3');
* ```
*
* This cache implementation is known to rely on wall-clock time to schedule
* future cache expiration times when using any version before PHP 7.3,
* because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you store a cache item with a TTL of 30s on PHP < 7.3
* and then adjust your system time forward by 20s, the cache item may
* expire in 10s. See also [`set()`](#set) for more details.
*
* @param int|null $limit maximum number of entries to store in the LRU cache
*/
public function __construct($limit = null)
{
$this->limit = $limit;
// prefer high-resolution timer, available as of PHP 7.3+
$this->supportsHighResolution = \function_exists('hrtime');
}
public function get($key, $default = null)
{
// delete key if it is already expired => below will detect this as a cache miss
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
unset($this->data[$key], $this->expires[$key]);
}
if (!\array_key_exists($key, $this->data)) {
return Promise\resolve($default);
}
// remove and append to end of array to keep track of LRU info
$value = $this->data[$key];
unset($this->data[$key]);
$this->data[$key] = $value;
return Promise\resolve($value);
}
public function set($key, $value, $ttl = null)
{
// unset before setting to ensure this entry will be added to end of array (LRU info)
unset($this->data[$key]);
$this->data[$key] = $value;
// sort expiration times if TTL is given (first will expire first)
unset($this->expires[$key]);
if ($ttl !== null) {
$this->expires[$key] = $this->now() + $ttl;
\asort($this->expires);
}
// ensure size limit is not exceeded or remove first entry from array
if ($this->limit !== null && \count($this->data) > $this->limit) {
// first try to check if there's any expired entry
// expiration times are sorted, so we can simply look at the first one
\reset($this->expires);
$key = \key($this->expires);
// check to see if the first in the list of expiring keys is already expired
// if the first key is not expired, we have to overwrite by using LRU info
if ($key === null || $this->now() - $this->expires[$key] < 0) {
\reset($this->data);
$key = \key($this->data);
}
unset($this->data[$key], $this->expires[$key]);
}
return Promise\resolve(true);
}
public function delete($key)
{
unset($this->data[$key], $this->expires[$key]);
return Promise\resolve(true);
}
public function getMultiple(array $keys, $default = null)
{
$values = array();
foreach ($keys as $key) {
$values[$key] = $this->get($key, $default);
}
return Promise\all($values);
}
public function setMultiple(array $values, $ttl = null)
{
foreach ($values as $key => $value) {
$this->set($key, $value, $ttl);
}
return Promise\resolve(true);
}
public function deleteMultiple(array $keys)
{
foreach ($keys as $key) {
unset($this->data[$key], $this->expires[$key]);
}
return Promise\resolve(true);
}
public function clear()
{
$this->data = array();
$this->expires = array();
return Promise\resolve(true);
}
public function has($key)
{
// delete key if it is already expired
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
unset($this->data[$key], $this->expires[$key]);
}
if (!\array_key_exists($key, $this->data)) {
return Promise\resolve(false);
}
// remove and append to end of array to keep track of LRU info
$value = $this->data[$key];
unset($this->data[$key]);
$this->data[$key] = $value;
return Promise\resolve(true);
}
/**
* @return float
*/
private function now()
{
return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
}
}

194
vendor/react/cache/src/CacheInterface.php vendored Executable file
View File

@@ -0,0 +1,194 @@
<?php
namespace React\Cache;
use React\Promise\PromiseInterface;
interface CacheInterface
{
/**
* Retrieves an item from the cache.
*
* This method will resolve with the cached value on success or with the
* given `$default` value when no item can be found or when an error occurs.
* Similarly, an expired cache item (once the time-to-live is expired) is
* considered a cache miss.
*
* ```php
* $cache
* ->get('foo')
* ->then('var_dump');
* ```
*
* This example fetches the value of the key `foo` and passes it to the
* `var_dump` function. You can use any of the composition provided by
* [promises](https://github.com/reactphp/promise).
*
* @param string $key
* @param mixed $default Default value to return for cache miss or null if not given.
* @return PromiseInterface<mixed>
*/
public function get($key, $default = null);
/**
* Stores an item in the cache.
*
* This method will resolve with `true` on success or `false` when an error
* occurs. If the cache implementation has to go over the network to store
* it, it may take a while.
*
* The optional `$ttl` parameter sets the maximum time-to-live in seconds
* for this cache item. If this parameter is omitted (or `null`), the item
* will stay in the cache for as long as the underlying implementation
* supports. Trying to access an expired cache item results in a cache miss,
* see also [`get()`](#get).
*
* ```php
* $cache->set('foo', 'bar', 60);
* ```
*
* This example eventually sets the value of the key `foo` to `bar`. If it
* already exists, it is overridden.
*
* This interface does not enforce any particular TTL resolution, so special
* care may have to be taken if you rely on very high precision with
* millisecond accuracy or below. Cache implementations SHOULD work on a
* best effort basis and SHOULD provide at least second accuracy unless
* otherwise noted. Many existing cache implementations are known to provide
* microsecond or millisecond accuracy, but it's generally not recommended
* to rely on this high precision.
*
* This interface suggests that cache implementations SHOULD use a monotonic
* time source if available. Given that a monotonic time source is only
* available as of PHP 7.3 by default, cache implementations MAY fall back
* to using wall-clock time.
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you store a cache item with a TTL of 30s and then
* adjust your system time forward by 20s, the cache item SHOULD still
* expire in 30s.
*
* @param string $key
* @param mixed $value
* @param ?float $ttl
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
*/
public function set($key, $value, $ttl = null);
/**
* Deletes an item from the cache.
*
* This method will resolve with `true` on success or `false` when an error
* occurs. When no item for `$key` is found in the cache, it also resolves
* to `true`. If the cache implementation has to go over the network to
* delete it, it may take a while.
*
* ```php
* $cache->delete('foo');
* ```
*
* This example eventually deletes the key `foo` from the cache. As with
* `set()`, this may not happen instantly and a promise is returned to
* provide guarantees whether or not the item has been removed from cache.
*
* @param string $key
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
*/
public function delete($key);
/**
* Retrieves multiple cache items by their unique keys.
*
* This method will resolve with an array of cached values on success or with the
* given `$default` value when an item can not be found or when an error occurs.
* Similarly, an expired cache item (once the time-to-live is expired) is
* considered a cache miss.
*
* ```php
* $cache->getMultiple(array('name', 'age'))->then(function (array $values) {
* $name = $values['name'] ?? 'User';
* $age = $values['age'] ?? 'n/a';
*
* echo $name . ' is ' . $age . PHP_EOL;
* });
* ```
*
* This example fetches the cache items for the `name` and `age` keys and
* prints some example output. You can use any of the composition provided
* by [promises](https://github.com/reactphp/promise).
*
* @param string[] $keys A list of keys that can obtained in a single operation.
* @param mixed $default Default value to return for keys that do not exist.
* @return PromiseInterface<array> Returns a promise which resolves to an `array` of cached values
*/
public function getMultiple(array $keys, $default = null);
/**
* Persists a set of key => value pairs in the cache, with an optional TTL.
*
* This method will resolve with `true` on success or `false` when an error
* occurs. If the cache implementation has to go over the network to store
* it, it may take a while.
*
* The optional `$ttl` parameter sets the maximum time-to-live in seconds
* for these cache items. If this parameter is omitted (or `null`), these items
* will stay in the cache for as long as the underlying implementation
* supports. Trying to access an expired cache items results in a cache miss,
* see also [`get()`](#get).
*
* ```php
* $cache->setMultiple(array('foo' => 1, 'bar' => 2), 60);
* ```
*
* This example eventually sets the list of values - the key `foo` to 1 value
* and the key `bar` to 2. If some of the keys already exist, they are overridden.
*
* @param array $values A list of key => value pairs for a multiple-set operation.
* @param ?float $ttl Optional. The TTL value of this item.
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
*/
public function setMultiple(array $values, $ttl = null);
/**
* Deletes multiple cache items in a single operation.
*
* @param string[] $keys A list of string-based keys to be deleted.
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
*/
public function deleteMultiple(array $keys);
/**
* Wipes clean the entire cache.
*
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
*/
public function clear();
/**
* Determines whether an item is present in the cache.
*
* This method will resolve with `true` on success or `false` when no item can be found
* or when an error occurs. Similarly, an expired cache item (once the time-to-live
* is expired) is considered a cache miss.
*
* ```php
* $cache
* ->has('foo')
* ->then('var_dump');
* ```
*
* This example checks if the value of the key `foo` is set in the cache and passes
* the result to the `var_dump` function. You can use any of the composition provided by
* [promises](https://github.com/reactphp/promise).
*
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
* and not to be used within your live applications operations for get/set, as this method
* is subject to a race condition where your has() will return true and immediately after,
* another script can remove it making the state of your app out of date.
*
* @param string $key The cache item key.
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
*/
public function has($key);
}

176
vendor/react/child-process/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,176 @@
# Changelog
## 0.6.6 (2025-01-01)
This is a compatibility release that contains backported features from the `0.7.x` branch.
Once v0.7 is released, it will be the way forward for this project.
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable types.
(#114 by @clue)
* Improve test suite to run tests on latest PHP versions and report failed assertions.
(#113 by @clue)
## 0.6.5 (2022-09-16)
* Feature: Full support for PHP 8.1 and PHP 8.2 release.
(#91 by @SimonFrings and #99 by @WyriHaximus)
* Feature / Fix: Improve error reporting when custom error handler is used.
(#94 by @clue)
* Minor documentation improvements.
(#92 by @SimonFrings and #95 by @nhedger)
* Improve test suite, skip failing tests on bugged versions and fix legacy HHVM build.
(#96 and #98 by @clue and #93 by @SimonFrings)
## 0.6.4 (2021-10-12)
* Feature / Fix: Skip sigchild check if `phpinfo()` has been disabled.
(#89 by @clue)
* Fix: Fix detecting closed socket pipes on PHP 8.
(#90 by @clue)
## 0.6.3 (2021-07-11)
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
(#87 by @clue)
```php
// old (still supported)
$process = new React\ChildProcess\Process($command);
$process->start($loop);
// new (using default loop)
$process = new React\ChildProcess\Process($command);
$process->start();
```
## 0.6.2 (2021-02-05)
* Feature: Support PHP 8 and add non-blocking I/O support on Windows with PHP 8.
(#85 by @clue)
* Minor documentation improvements.
(#78 by @WyriHaximus and #80 by @gdejong)
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
Run tests on PHPUnit 9, switch to GitHub actions and clean up test suite.
(#75 by @reedy, #81 by @gdejong, #82 by @SimonFrings and #84 by @clue)
## 0.6.1 (2019-02-15)
* Feature / Fix: Improve error reporting when spawning child process fails.
(#73 by @clue)
## 0.6.0 (2019-01-14)
A major feature release with some minor API improvements!
This project now has limited Windows support and supports passing custom pipes
and file descriptors to the child process.
This update involves a few minor BC breaks. We've tried hard to avoid BC breaks
where possible and minimize impact otherwise. We expect that most consumers of
this package will actually not be affected by any BC breaks, see below for more
details.
* Feature / BC break: Support passing custom pipes and file descriptors to child process,
expose all standard I/O pipes in an array and remove unused Windows-only options.
(#62, #64 and #65 by @clue)
> BC note: The optional `$options` parameter in the `Process` constructor
has been removed and a new `$fds` parameter has been added instead. The
previous `$options` parameter was Windows-only, available options were not
documented or referenced anywhere else in this library, so its actual
impact is expected to be relatively small. See the documentation and the
following changelog entry if you're looking for Windows support.
* Feature: Support spawning child process on Windows without process I/O pipes.
(#67 by @clue)
* Feature / BC break: Improve sigchild compatibility and support explicit configuration.
(#63 by @clue)
```php
// advanced: not recommended by default
Process::setSigchildEnabled(true);
```
> BC note: The old public sigchild methods have been removed, but its
practical impact is believed to be relatively small due to the automatic detection.
* Improve performance by prefixing all global functions calls with \ to skip
the look up and resolve process and go straight to the global function.
(#68 by @WyriHaximus)
* Minor documentation improvements and docblock updates.
(#59 by @iamluc and #69 by @CharlotteDunois)
* Improve test suite to test against PHP7.2 and PHP 7.3, improve HHVM compatibility,
add forward compatibility with PHPUnit 7 and run tests on Windows via Travis CI.
(#66 and #71 by @clue)
## 0.5.2 (2018-01-18)
* Feature: Detect "exit" immediately if last process pipe is closed
(#58 by @clue)
This introduces a simple check to see if the program is already known to be
closed when the last process pipe is closed instead of relying on a periodic
timer. This simple change improves "exit" detection significantly for most
programs and does not cause a noticeable penalty for more advanced use cases.
* Fix forward compatibility with upcoming EventLoop releases
(#56 by @clue)
## 0.5.1 (2017-12-22)
* Fix: Update Stream dependency to work around SEGFAULT in legacy PHP < 5.4.28
and PHP < 5.5.12
(#50 and #52 by @clue)
* Improve test suite by simplifying test bootstrapping logic via Composer and
adding forward compatibility with PHPUnit 6
(#53, #54 and #55 by @clue)
## 0.5.0 (2017-08-15)
* Forward compatibility: react/event-loop 1.0 and 0.5, react/stream 0.7.2 and 1.0, and Événement 3.0
(#38 and #44 by @WyriHaximus, and #46 by @clue)
* Windows compatibility: Documentate that windows isn't supported in 0.5 unless used from within WSL
(#41 and #47 by @WyriHaximus)
* Documentation: Termination examples
(#42 by @clue)
* BC: Throw LogicException in Process instanciating when on Windows or when proc_open is missing (was `RuntimeException`)
(#49 by @mdrost)
## 0.4.3 (2017-03-14)
* Ease getting started by improving documentation and adding examples
(#33 and #34 by @clue)
* First class support for PHP 5.3 through PHP 7.1 and HHVM
(#29 by @clue and #32 by @WyriHaximus)
## 0.4.2 (2017-03-10)
* Feature: Forward compatibility with Stream v0.5
(#26 by @clue)
* Improve test suite by removing AppVeyor and adding PHPUnit to `require-dev`
(#27 and #28 by @clue)
## 0.4.1 (2016-08-01)
* Standalone component
* Test against PHP 7 and HHVM, report test coverage, AppVeyor tests
* Wait for stdout and stderr to close before watching for process exit
(#18 by @mbonneau)
## 0.4.0 (2014-02-02)
* Feature: Added ChildProcess to run async child processes within the event loop (@jmikola)

21
vendor/react/child-process/LICENSE vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

619
vendor/react/child-process/README.md vendored Executable file
View File

@@ -0,0 +1,619 @@
# ChildProcess
[![CI status](https://github.com/reactphp/child-process/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/child-process/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/child-process?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/child-process)
Event-driven library for executing child processes with
[ReactPHP](https://reactphp.org/).
This library integrates [Program Execution](http://php.net/manual/en/book.exec.php)
with the [EventLoop](https://github.com/reactphp/event-loop).
Child processes launched may be signaled and will emit an
`exit` event upon termination.
Additionally, process I/O streams (i.e. STDIN, STDOUT, STDERR) are exposed
as [Streams](https://github.com/reactphp/stream).
**Table of contents**
* [Quickstart example](#quickstart-example)
* [Process](#process)
* [Stream Properties](#stream-properties)
* [Command](#command)
* [Termination](#termination)
* [Custom pipes](#custom-pipes)
* [Sigchild Compatibility](#sigchild-compatibility)
* [Windows Compatibility](#windows-compatibility)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
## Quickstart example
```php
$process = new React\ChildProcess\Process('echo foo');
$process->start();
$process->stdout->on('data', function ($chunk) {
echo $chunk;
});
$process->on('exit', function($exitCode, $termSignal) {
echo 'Process exited with code ' . $exitCode . PHP_EOL;
});
```
See also the [examples](examples).
## Process
### Stream Properties
Once a process is started, its I/O streams will be constructed as instances of
`React\Stream\ReadableStreamInterface` and `React\Stream\WritableStreamInterface`.
Before `start()` is called, these properties are not set. Once a process terminates,
the streams will become closed but not unset.
Following common Unix conventions, this library will start each child process
with the three pipes matching the standard I/O streams as given below by default.
You can use the named references for common use cases or access these as an
array with all three pipes.
* `$stdin` or `$pipes[0]` is a `WritableStreamInterface`
* `$stdout` or `$pipes[1]` is a `ReadableStreamInterface`
* `$stderr` or `$pipes[2]` is a `ReadableStreamInterface`
Note that this default configuration may be overridden by explicitly passing
[custom pipes](#custom-pipes), in which case they may not be set or be assigned
different values. In particular, note that [Windows support](#windows-compatibility)
is limited in that it doesn't support non-blocking STDIO pipes. The `$pipes`
array will always contain references to all pipes as configured and the standard
I/O references will always be set to reference the pipes matching the above
conventions. See [custom pipes](#custom-pipes) for more details.
Because each of these implement the underlying
[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) or
[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface),
you can use any of their events and methods as usual:
```php
$process = new Process($command);
$process->start();
$process->stdout->on('data', function ($chunk) {
echo $chunk;
});
$process->stdout->on('end', function () {
echo 'ended';
});
$process->stdout->on('error', function (Exception $e) {
echo 'error: ' . $e->getMessage();
});
$process->stdout->on('close', function () {
echo 'closed';
});
$process->stdin->write($data);
$process->stdin->end($data = null);
// …
```
For more details, see the
[`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) and
[`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface).
### Command
The `Process` class allows you to pass any kind of command line string:
```php
$process = new Process('echo test');
$process->start();
```
The command line string usually consists of a whitespace-separated list with
your main executable bin and any number of arguments. Special care should be
taken to escape or quote any arguments, escpecially if you pass any user input
along. Likewise, keep in mind that especially on Windows, it is rather common to
have path names containing spaces and other special characters. If you want to
run a binary like this, you will have to ensure this is quoted as a single
argument using `escapeshellarg()` like this:
```php
$bin = 'C:\\Program files (x86)\\PHP\\php.exe';
$file = 'C:\\Users\\me\\Desktop\\Application\\main.php';
$process = new Process(escapeshellarg($bin) . ' ' . escapeshellarg($file));
$process->start();
```
By default, PHP will launch processes by wrapping the given command line string
in a `sh` command on Unix, so that the first example will actually execute
`sh -c echo test` under the hood on Unix. On Windows, it will not launch
processes by wrapping them in a shell.
This is a very useful feature because it does not only allow you to pass single
commands, but actually allows you to pass any kind of shell command line and
launch multiple sub-commands using command chains (with `&&`, `||`, `;` and
others) and allows you to redirect STDIO streams (with `2>&1` and family).
This can be used to pass complete command lines and receive the resulting STDIO
streams from the wrapping shell command like this:
```php
$process = new Process('echo run && demo || echo failed');
$process->start();
```
> Note that [Windows support](#windows-compatibility) is limited in that it
doesn't support STDIO streams at all and also that processes will not be run
in a wrapping shell by default. If you want to run a shell built-in function
such as `echo hello` or `sleep 10`, you may have to prefix your command line
with an explicit shell like `cmd /c echo hello`.
In other words, the underlying shell is responsible for managing this command
line and launching the individual sub-commands and connecting their STDIO
streams as appropriate.
This implies that the `Process` class will only receive the resulting STDIO
streams from the wrapping shell, which will thus contain the complete
input/output with no way to discern the input/output of single sub-commands.
If you want to discern the output of single sub-commands, you may want to
implement some higher-level protocol logic, such as printing an explicit
boundary between each sub-command like this:
```php
$process = new Process('cat first && echo --- && cat second');
$process->start();
```
As an alternative, considering launching one process at a time and listening on
its `exit` event to conditionally start the next process in the chain.
This will give you an opportunity to configure the subsequent process I/O streams:
```php
$first = new Process('cat first');
$first->start();
$first->on('exit', function () {
$second = new Process('cat second');
$second->start();
});
```
Keep in mind that PHP uses the shell wrapper for ALL command lines on Unix.
While this may seem reasonable for more complex command lines, this actually
also applies to running the most simple single command:
```php
$process = new Process('yes');
$process->start();
```
This will actually spawn a command hierarchy similar to this on Unix:
```
5480 … \_ php example.php
5481 … \_ sh -c yes
5482 … \_ yes
```
This means that trying to get the underlying process PID or sending signals
will actually target the wrapping shell, which may not be the desired result
in many cases.
If you do not want this wrapping shell process to show up, you can simply
prepend the command string with `exec` on Unix platforms, which will cause the
wrapping shell process to be replaced by our process:
```php
$process = new Process('exec yes');
$process->start();
```
This will show a resulting command hierarchy similar to this:
```
5480 … \_ php example.php
5481 … \_ yes
```
This means that trying to get the underlying process PID and sending signals
will now target the actual command as expected.
Note that in this case, the command line will not be run in a wrapping shell.
This implies that when using `exec`, there's no way to pass command lines such
as those containing command chains or redirected STDIO streams.
As a rule of thumb, most commands will likely run just fine with the wrapping
shell.
If you pass a complete command line (or are unsure), you SHOULD most likely keep
the wrapping shell.
If you're running on Unix and you want to pass an invidual command only, you MAY
want to consider prepending the command string with `exec` to avoid the wrapping shell.
### Termination
The `exit` event will be emitted whenever the process is no longer running.
Event listeners will receive the exit code and termination signal as two
arguments:
```php
$process = new Process('sleep 10');
$process->start();
$process->on('exit', function ($code, $term) {
if ($term === null) {
echo 'exit with code ' . $code . PHP_EOL;
} else {
echo 'terminated with signal ' . $term . PHP_EOL;
}
});
```
Note that `$code` is `null` if the process has terminated, but the exit
code could not be determined (for example
[sigchild compatibility](#sigchild-compatibility) was disabled).
Similarly, `$term` is `null` unless the process has terminated in response to
an uncaught signal sent to it.
This is not a limitation of this project, but actual how exit codes and signals
are exposed on POSIX systems, for more details see also
[here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
It's also worth noting that process termination depends on all file descriptors
being closed beforehand.
This means that all [process pipes](#stream-properties) will emit a `close`
event before the `exit` event and that no more `data` events will arrive after
the `exit` event.
Accordingly, if either of these pipes is in a paused state (`pause()` method
or internally due to a `pipe()` call), this detection may not trigger.
The `terminate(?int $signal = null): bool` method can be used to send the
process a signal (SIGTERM by default).
Depending on which signal you send to the process and whether it has a signal
handler registered, this can be used to either merely signal a process or even
forcefully terminate it.
```php
$process->terminate(SIGUSR1);
```
Keep the above section in mind if you want to forcefully terminate a process.
If your process spawn sub-processes or implicitly uses the
[wrapping shell mentioned above](#command), its file descriptors may be
inherited to child processes and terminating the main process may not
necessarily terminate the whole process tree.
It is highly suggested that you explicitly `close()` all process pipes
accordingly when terminating a process:
```php
$process = new Process('sleep 10');
$process->start();
Loop::addTimer(2.0, function () use ($process) {
foreach ($process->pipes as $pipe) {
$pipe->close();
}
$process->terminate();
});
```
For many simple programs these seamingly complicated steps can also be avoided
by prefixing the command line with `exec` to avoid the wrapping shell and its
inherited process pipes as [mentioned above](#command).
```php
$process = new Process('exec sleep 10');
$process->start();
Loop::addTimer(2.0, function () use ($process) {
$process->terminate();
});
```
Many command line programs also wait for data on `STDIN` and terminate cleanly
when this pipe is closed.
For example, the following can be used to "soft-close" a `cat` process:
```php
$process = new Process('cat');
$process->start();
Loop::addTimer(2.0, function () use ($process) {
$process->stdin->end();
});
```
While process pipes and termination may seem confusing to newcomers, the above
properties actually allow some fine grained control over process termination,
such as first trying a soft-close and then applying a force-close after a
timeout.
### Custom pipes
Following common Unix conventions, this library will start each child process
with the three pipes matching the standard I/O streams by default. For more
advanced use cases it may be useful to pass in custom pipes, such as explicitly
passing additional file descriptors (FDs) or overriding default process pipes.
Note that passing custom pipes is considered advanced usage and requires a
more in-depth understanding of Unix file descriptors and how they are inherited
to child processes and shared in multi-processing applications.
If you do not want to use the default standard I/O pipes, you can explicitly
pass an array containing the file descriptor specification to the constructor
like this:
```php
$fds = array(
// standard I/O pipes for stdin/stdout/stderr
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
// example FDs for files or open resources
4 => array('file', '/dev/null', 'r'),
6 => fopen('log.txt','a'),
8 => STDERR,
// example FDs for sockets
10 => fsockopen('localhost', 8080),
12 => stream_socket_server('tcp://0.0.0.0:4711')
);
$process = new Process($cmd, null, null, $fds);
$process->start();
```
Unless your use case has special requirements that demand otherwise, you're
highly recommended to (at least) pass in the standard I/O pipes as given above.
The file descriptor specification accepts arguments in the exact same format
as the underlying [`proc_open()`](http://php.net/proc_open) function.
Once the process is started, the `$pipes` array will always contain references to
all pipes as configured and the standard I/O references will always be set to
reference the pipes matching common Unix conventions. This library supports any
number of pipes and additional file descriptors, but many common applications
being run as a child process will expect that the parent process properly
assigns these file descriptors.
### Sigchild Compatibility
Internally, this project uses a work-around to improve compatibility when PHP
has been compiled with the `--enable-sigchild` option. This should not affect most
installations as this configure option is not used by default and many
distributions (such as Debian and Ubuntu) are known to not use this by default.
Some installations that use [Oracle OCI8](http://php.net/manual/en/book.oci8.php)
may use this configure option to circumvent `defunct` processes.
When PHP has been compiled with the `--enable-sigchild` option, a child process'
exit code cannot be reliably determined via `proc_close()` or `proc_get_status()`.
To work around this, we execute the child process with an additional pipe and
use that to retrieve its exit code.
This work-around incurs some overhead, so we only trigger this when necessary
and when we detect that PHP has been compiled with the `--enable-sigchild` option.
Because PHP does not provide a way to reliably detect this option, we try to
inspect output of PHP's configure options from the `phpinfo()` function.
The static `setSigchildEnabled(bool $sigchild): void` method can be used to
explicitly enable or disable this behavior like this:
```php
// advanced: not recommended by default
Process::setSigchildEnabled(true);
```
Note that all processes instantiated after this method call will be affected.
If this work-around is disabled on an affected PHP installation, the `exit`
event may receive `null` instead of the actual exit code as described above.
Similarly, some distributions are known to omit the configure options from
`phpinfo()`, so automatic detection may fail to enable this work-around in some
cases. You may then enable this explicitly as given above.
**Note:** The original functionality was taken from Symfony's
[Process](https://github.com/symfony/process) compoment.
### Windows Compatibility
Due to platform constraints, this library provides only limited support for
spawning child processes on Windows. In particular, PHP does not allow accessing
standard I/O pipes on Windows without blocking. As such, this project will not
allow constructing a child process with the default process pipes and will
instead throw a `LogicException` on Windows by default:
```php
// throws LogicException on Windows
$process = new Process('ping example.com');
$process->start();
```
There are a number of alternatives and workarounds as detailed below if you want
to run a child process on Windows, each with its own set of pros and cons:
* As of PHP 8, you can start the child process with `socket` pair descriptors
in place of normal standard I/O pipes like this:
```php
$process = new Process(
'ping example.com',
null,
null,
[
['socket'],
['socket'],
['socket']
]
);
$process->start();
$process->stdout->on('data', function ($chunk) {
echo $chunk;
});
```
These `socket` pairs support non-blocking process I/O on any platform,
including Windows. However, not all programs accept stdio sockets.
* This package does work on
[`Windows Subsystem for Linux`](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)
(or WSL) without issues. When you are in control over how your application is
deployed, we recommend [installing WSL](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide)
when you want to run this package on Windows.
* If you only care about the exit code of a child process to check if its
execution was successful, you can use [custom pipes](#custom-pipes) to omit
any standard I/O pipes like this:
```php
$process = new Process('ping example.com', null, null, array());
$process->start();
$process->on('exit', function ($exitcode) {
echo 'exit with ' . $exitcode . PHP_EOL;
});
```
Similarly, this is also useful if your child process communicates over
sockets with remote servers or even your parent process using the
[Socket component](https://github.com/reactphp/socket). This is usually
considered the best alternative if you have control over how your child
process communicates with the parent process.
* If you only care about command output after the child process has been
executed, you can use [custom pipes](#custom-pipes) to configure file
handles to be passed to the child process instead of pipes like this:
```php
$process = new Process('ping example.com', null, null, array(
array('file', 'nul', 'r'),
$stdout = tmpfile(),
array('file', 'nul', 'w')
));
$process->start();
$process->on('exit', function ($exitcode) use ($stdout) {
echo 'exit with ' . $exitcode . PHP_EOL;
// rewind to start and then read full file (demo only, this is blocking).
// reading from shared file is only safe if you have some synchronization in place
// or after the child process has terminated.
rewind($stdout);
echo stream_get_contents($stdout);
fclose($stdout);
});
```
Note that this example uses `tmpfile()`/`fopen()` for illustration purposes only.
This should not be used in a truly async program because the filesystem is
inherently blocking and each call could potentially take several seconds.
See also the [Filesystem component](https://github.com/reactphp/filesystem) as an
alternative.
* If you want to access command output as it happens in a streaming fashion,
you can use redirection to spawn an additional process to forward your
standard I/O streams to a socket and use [custom pipes](#custom-pipes) to
omit any actual standard I/O pipes like this:
```php
$server = new React\Socket\Server('127.0.0.1:0');
$server->on('connection', function (React\Socket\ConnectionInterface $connection) {
$connection->on('data', function ($chunk) {
echo $chunk;
});
});
$command = 'ping example.com | foobar ' . escapeshellarg($server->getAddress());
$process = new Process($command, null, null, array());
$process->start();
$process->on('exit', function ($exitcode) use ($server) {
$server->close();
echo 'exit with ' . $exitcode . PHP_EOL;
});
```
Note how this will spawn another fictional `foobar` helper program to consume
the standard output from the actual child process. This is in fact similar
to the above recommendation of using socket connections in the child process,
but in this case does not require modification of the actual child process.
In this example, the fictional `foobar` helper program can be implemented by
simply consuming all data from standard input and forwarding it to a socket
connection like this:
```php
$socket = stream_socket_client($argv[1]);
do {
fwrite($socket, $data = fread(STDIN, 8192));
} while (isset($data[0]));
```
Accordingly, this example can also be run with plain PHP without having to
rely on any external helper program like this:
```php
$code = '$s=stream_socket_client($argv[1]);do{fwrite($s,$d=fread(STDIN, 8192));}while(isset($d[0]));';
$command = 'ping example.com | php -r ' . escapeshellarg($code) . ' ' . escapeshellarg($server->getAddress());
$process = new Process($command, null, null, array());
$process->start();
```
See also [example #23](examples/23-forward-socket.php).
Note that this is for illustration purposes only and you may want to implement
some proper error checks and/or socket verification in actual production use
if you do not want to risk other processes connecting to the server socket.
In this case, we suggest looking at the excellent
[createprocess-windows](https://github.com/cubiclesoft/createprocess-windows).
Additionally, note that the [command](#command) given to the `Process` will be
passed to the underlying Windows-API
([`CreateProcess`](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-createprocessa))
as-is and the process will not be launched in a wrapping shell by default. In
particular, this means that shell built-in functions such as `echo hello` or
`sleep 10` may have to be prefixed with an explicit shell command like this:
```php
$process = new Process('cmd /c echo hello', null, null, $pipes);
$process->start();
```
## Install
The recommended way to install this library is [through Composer](https://getcomposer.org/).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This will install the latest supported version:
```bash
composer require react/child-process:^0.6.6
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and HHVM.
It's *highly recommended to use the latest supported PHP version* for this project.
See above note for limited [Windows Compatibility](#windows-compatibility).
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org/):
```bash
composer install
```
To run the test suite, go to the project root and run:
```bash
vendor/bin/phpunit
```
## License
MIT, see [LICENSE file](LICENSE).

49
vendor/react/child-process/composer.json vendored Executable file
View File

@@ -0,0 +1,49 @@
{
"name": "react/child-process",
"description": "Event-driven library for executing child processes with ReactPHP.",
"keywords": ["process", "event-driven", "ReactPHP"],
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"homepage": "https://clue.engineering/",
"email": "christian@clue.engineering"
},
{
"name": "Cees-Jan Kiewiet",
"homepage": "https://wyrihaximus.net/",
"email": "reactphp@ceesjankiewiet.nl"
},
{
"name": "Jan Sorgalla",
"homepage": "https://sorgalla.com/",
"email": "jsorgalla@gmail.com"
},
{
"name": "Chris Boden",
"homepage": "https://cboden.dev/",
"email": "cboden@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"react/event-loop": "^1.2",
"react/stream": "^1.4"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"react/socket": "^1.16",
"sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0"
},
"autoload": {
"psr-4": {
"React\\ChildProcess\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"React\\Tests\\ChildProcess\\": "tests/"
}
}
}

585
vendor/react/child-process/src/Process.php vendored Executable file
View File

@@ -0,0 +1,585 @@
<?php
namespace React\ChildProcess;
use Evenement\EventEmitter;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Stream\ReadableResourceStream;
use React\Stream\ReadableStreamInterface;
use React\Stream\WritableResourceStream;
use React\Stream\WritableStreamInterface;
use React\Stream\DuplexResourceStream;
use React\Stream\DuplexStreamInterface;
/**
* Process component.
*
* This class borrows logic from Symfony's Process component for ensuring
* compatibility when PHP is compiled with the --enable-sigchild option.
*
* This class also implements the `EventEmitterInterface`
* which allows you to react to certain events:
*
* exit event:
* The `exit` event will be emitted whenever the process is no longer running.
* Event listeners will receive the exit code and termination signal as two
* arguments:
*
* ```php
* $process = new Process('sleep 10');
* $process->start();
*
* $process->on('exit', function ($code, $term) {
* if ($term === null) {
* echo 'exit with code ' . $code . PHP_EOL;
* } else {
* echo 'terminated with signal ' . $term . PHP_EOL;
* }
* });
* ```
*
* Note that `$code` is `null` if the process has terminated, but the exit
* code could not be determined (for example
* [sigchild compatibility](#sigchild-compatibility) was disabled).
* Similarly, `$term` is `null` unless the process has terminated in response to
* an uncaught signal sent to it.
* This is not a limitation of this project, but actual how exit codes and signals
* are exposed on POSIX systems, for more details see also
* [here](https://unix.stackexchange.com/questions/99112/default-exit-code-when-process-is-terminated).
*
* It's also worth noting that process termination depends on all file descriptors
* being closed beforehand.
* This means that all [process pipes](#stream-properties) will emit a `close`
* event before the `exit` event and that no more `data` events will arrive after
* the `exit` event.
* Accordingly, if either of these pipes is in a paused state (`pause()` method
* or internally due to a `pipe()` call), this detection may not trigger.
*/
class Process extends EventEmitter
{
/**
* @var WritableStreamInterface|null|DuplexStreamInterface|ReadableStreamInterface
*/
public $stdin;
/**
* @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
*/
public $stdout;
/**
* @var ReadableStreamInterface|null|DuplexStreamInterface|WritableStreamInterface
*/
public $stderr;
/**
* Array with all process pipes (once started)
*
* Unless explicitly configured otherwise during construction, the following
* standard I/O pipes will be assigned by default:
* - 0: STDIN (`WritableStreamInterface`)
* - 1: STDOUT (`ReadableStreamInterface`)
* - 2: STDERR (`ReadableStreamInterface`)
*
* @var array<ReadableStreamInterface|WritableStreamInterface|DuplexStreamInterface>
*/
public $pipes = array();
private $cmd;
private $cwd;
private $env;
private $fds;
private $enhanceSigchildCompatibility;
private $sigchildPipe;
private $process;
private $status;
private $exitCode;
private $fallbackExitCode;
private $stopSignal;
private $termSignal;
private static $sigchild;
/**
* Constructor.
*
* @param string $cmd Command line to run
* @param null|string $cwd Current working directory or null to inherit
* @param null|array $env Environment variables or null to inherit
* @param null|array $fds File descriptors to allocate for this process (or null = default STDIO streams)
* @throws \LogicException On windows or when proc_open() is not installed
*/
public function __construct($cmd, $cwd = null, $env = null, $fds = null)
{
if ($env !== null && !\is_array($env)) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #3 ($env) expected null|array');
}
if ($fds !== null && !\is_array($fds)) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #4 ($fds) expected null|array');
}
if (!\function_exists('proc_open')) {
throw new \LogicException('The Process class relies on proc_open(), which is not available on your PHP installation.');
}
$this->cmd = $cmd;
$this->cwd = $cwd;
if (null !== $env) {
$this->env = array();
foreach ($env as $key => $value) {
$this->env[(binary) $key] = (binary) $value;
}
}
if ($fds === null) {
$fds = array(
array('pipe', 'r'), // stdin
array('pipe', 'w'), // stdout
array('pipe', 'w'), // stderr
);
}
if (\DIRECTORY_SEPARATOR === '\\') {
foreach ($fds as $fd) {
if (isset($fd[0]) && $fd[0] === 'pipe') {
throw new \LogicException('Process pipes are not supported on Windows due to their blocking nature on Windows');
}
}
}
$this->fds = $fds;
$this->enhanceSigchildCompatibility = self::isSigchildEnabled();
}
/**
* Start the process.
*
* After the process is started, the standard I/O streams will be constructed
* and available via public properties.
*
* This method takes an optional `LoopInterface|null $loop` parameter that can be used to
* pass the event loop instance to use for this process. You can use a `null` value
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
* given event loop instance.
*
* @param ?LoopInterface $loop Loop interface for stream construction
* @param float $interval Interval to periodically monitor process state (seconds)
* @throws \RuntimeException If the process is already running or fails to start
*/
public function start($loop = null, $interval = 0.1)
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
}
if ($this->isRunning()) {
throw new \RuntimeException('Process is already running');
}
$loop = $loop ?: Loop::get();
$cmd = $this->cmd;
$fdSpec = $this->fds;
$sigchild = null;
// Read exit code through fourth pipe to work around --enable-sigchild
if ($this->enhanceSigchildCompatibility) {
$fdSpec[] = array('pipe', 'w');
\end($fdSpec);
$sigchild = \key($fdSpec);
// make sure this is fourth or higher (do not mess with STDIO)
if ($sigchild < 3) {
$fdSpec[3] = $fdSpec[$sigchild];
unset($fdSpec[$sigchild]);
$sigchild = 3;
}
$cmd = \sprintf('(%s) ' . $sigchild . '>/dev/null; code=$?; echo $code >&' . $sigchild . '; exit $code', $cmd);
}
// on Windows, we do not launch the given command line in a shell (cmd.exe) by default and omit any error dialogs
// the cmd.exe shell can explicitly be given as part of the command as detailed in both documentation and tests
$options = array();
if (\DIRECTORY_SEPARATOR === '\\') {
$options['bypass_shell'] = true;
$options['suppress_errors'] = true;
}
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errstr) {
// Match errstr from PHP's warning message.
// proc_open(/dev/does-not-exist): Failed to open stream: No such file or directory
$errstr = $error;
});
$pipes = array();
$this->process = @\proc_open($cmd, $fdSpec, $pipes, $this->cwd, $this->env, $options);
\restore_error_handler();
if (!\is_resource($this->process)) {
throw new \RuntimeException('Unable to launch a new process: ' . $errstr);
}
// count open process pipes and await close event for each to drain buffers before detecting exit
$that = $this;
$closeCount = 0;
$streamCloseHandler = function () use (&$closeCount, $loop, $interval, $that) {
$closeCount--;
if ($closeCount > 0) {
return;
}
// process already closed => report immediately
if (!$that->isRunning()) {
$that->close();
$that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
return;
}
// close not detected immediately => check regularly
$loop->addPeriodicTimer($interval, function ($timer) use ($that, $loop) {
if (!$that->isRunning()) {
$loop->cancelTimer($timer);
$that->close();
$that->emit('exit', array($that->getExitCode(), $that->getTermSignal()));
}
});
};
if ($sigchild !== null) {
$this->sigchildPipe = $pipes[$sigchild];
unset($pipes[$sigchild]);
}
foreach ($pipes as $n => $fd) {
// use open mode from stream meta data or fall back to pipe open mode for legacy HHVM
$meta = \stream_get_meta_data($fd);
$mode = $meta['mode'] === '' ? ($this->fds[$n][1] === 'r' ? 'w' : 'r') : $meta['mode'];
if ($mode === 'r+') {
$stream = new DuplexResourceStream($fd, $loop);
$stream->on('close', $streamCloseHandler);
$closeCount++;
} elseif ($mode === 'w') {
$stream = new WritableResourceStream($fd, $loop);
} else {
$stream = new ReadableResourceStream($fd, $loop);
$stream->on('close', $streamCloseHandler);
$closeCount++;
}
$this->pipes[$n] = $stream;
}
$this->stdin = isset($this->pipes[0]) ? $this->pipes[0] : null;
$this->stdout = isset($this->pipes[1]) ? $this->pipes[1] : null;
$this->stderr = isset($this->pipes[2]) ? $this->pipes[2] : null;
// immediately start checking for process exit when started without any I/O pipes
if (!$closeCount) {
$streamCloseHandler();
}
}
/**
* Close the process.
*
* This method should only be invoked via the periodic timer that monitors
* the process state.
*/
public function close()
{
if ($this->process === null) {
return;
}
foreach ($this->pipes as $pipe) {
$pipe->close();
}
if ($this->enhanceSigchildCompatibility) {
$this->pollExitCodePipe();
$this->closeExitCodePipe();
}
$exitCode = \proc_close($this->process);
$this->process = null;
if ($this->exitCode === null && $exitCode !== -1) {
$this->exitCode = $exitCode;
}
if ($this->exitCode === null && $this->status['exitcode'] !== -1) {
$this->exitCode = $this->status['exitcode'];
}
if ($this->exitCode === null && $this->fallbackExitCode !== null) {
$this->exitCode = $this->fallbackExitCode;
$this->fallbackExitCode = null;
}
}
/**
* Terminate the process with an optional signal.
*
* @param int $signal Optional signal (default: SIGTERM)
* @return bool Whether the signal was sent successfully
*/
public function terminate($signal = null)
{
if ($this->process === null) {
return false;
}
if ($signal !== null) {
return \proc_terminate($this->process, $signal);
}
return \proc_terminate($this->process);
}
/**
* Get the command string used to launch the process.
*
* @return string
*/
public function getCommand()
{
return $this->cmd;
}
/**
* Get the exit code returned by the process.
*
* This value is only meaningful if isRunning() has returned false. Null
* will be returned if the process is still running.
*
* Null may also be returned if the process has terminated, but the exit
* code could not be determined (e.g. sigchild compatibility was disabled).
*
* @return int|null
*/
public function getExitCode()
{
return $this->exitCode;
}
/**
* Get the process ID.
*
* @return int|null
*/
public function getPid()
{
$status = $this->getCachedStatus();
return $status !== null ? $status['pid'] : null;
}
/**
* Get the signal that caused the process to stop its execution.
*
* This value is only meaningful if isStopped() has returned true. Null will
* be returned if the process was never stopped.
*
* @return int|null
*/
public function getStopSignal()
{
return $this->stopSignal;
}
/**
* Get the signal that caused the process to terminate its execution.
*
* This value is only meaningful if isTerminated() has returned true. Null
* will be returned if the process was never terminated.
*
* @return int|null
*/
public function getTermSignal()
{
return $this->termSignal;
}
/**
* Return whether the process is still running.
*
* @return bool
*/
public function isRunning()
{
if ($this->process === null) {
return false;
}
$status = $this->getFreshStatus();
return $status !== null ? $status['running'] : false;
}
/**
* Return whether the process has been stopped by a signal.
*
* @return bool
*/
public function isStopped()
{
$status = $this->getFreshStatus();
return $status !== null ? $status['stopped'] : false;
}
/**
* Return whether the process has been terminated by an uncaught signal.
*
* @return bool
*/
public function isTerminated()
{
$status = $this->getFreshStatus();
return $status !== null ? $status['signaled'] : false;
}
/**
* Return whether PHP has been compiled with the '--enable-sigchild' option.
*
* @see \Symfony\Component\Process\Process::isSigchildEnabled()
* @return bool
*/
public final static function isSigchildEnabled()
{
if (null !== self::$sigchild) {
return self::$sigchild;
}
if (!\function_exists('phpinfo')) {
return self::$sigchild = false; // @codeCoverageIgnore
}
\ob_start();
\phpinfo(INFO_GENERAL);
return self::$sigchild = false !== \strpos(\ob_get_clean(), '--enable-sigchild');
}
/**
* Enable or disable sigchild compatibility mode.
*
* Sigchild compatibility mode is required to get the exit code and
* determine the success of a process when PHP has been compiled with
* the --enable-sigchild option.
*
* @param bool $sigchild
* @return void
*/
public final static function setSigchildEnabled($sigchild)
{
self::$sigchild = (bool) $sigchild;
}
/**
* Check the fourth pipe for an exit code.
*
* This should only be used if --enable-sigchild compatibility was enabled.
*/
private function pollExitCodePipe()
{
if ($this->sigchildPipe === null) {
return;
}
$r = array($this->sigchildPipe);
$w = $e = null;
$n = @\stream_select($r, $w, $e, 0);
if (1 !== $n) {
return;
}
$data = \fread($r[0], 8192);
if (\strlen($data) > 0) {
$this->fallbackExitCode = (int) $data;
}
}
/**
* Close the fourth pipe used to relay an exit code.
*
* This should only be used if --enable-sigchild compatibility was enabled.
*/
private function closeExitCodePipe()
{
if ($this->sigchildPipe === null) {
return;
}
\fclose($this->sigchildPipe);
$this->sigchildPipe = null;
}
/**
* Return the cached process status.
*
* @return array
*/
private function getCachedStatus()
{
if ($this->status === null) {
$this->updateStatus();
}
return $this->status;
}
/**
* Return the updated process status.
*
* @return array
*/
private function getFreshStatus()
{
$this->updateStatus();
return $this->status;
}
/**
* Update the process status, stop/term signals, and exit code.
*
* Stop/term signals are only updated if the process is currently stopped or
* signaled, respectively. Otherwise, signal values will remain as-is so the
* corresponding getter methods may be used at a later point in time.
*/
private function updateStatus()
{
if ($this->process === null) {
return;
}
$this->status = \proc_get_status($this->process);
if ($this->status === false) {
throw new \UnexpectedValueException('proc_get_status() failed');
}
if ($this->status['stopped']) {
$this->stopSignal = $this->status['stopsig'];
}
if ($this->status['signaled']) {
$this->termSignal = $this->status['termsig'];
}
if (!$this->status['running'] && -1 !== $this->status['exitcode']) {
$this->exitCode = $this->status['exitcode'];
}
}
}

452
vendor/react/dns/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,452 @@
# Changelog
## 1.13.0 (2024-06-13)
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
(#224 by @WyriHaximus)
## 1.12.0 (2023-11-29)
* Feature: Full PHP 8.3 compatibility.
(#217 by @sergiy-petrov)
* Update test environment and avoid unhandled promise rejections.
(#215, #216 and #218 by @clue)
## 1.11.0 (2023-06-02)
* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer.
(#213 by @clue)
* Improve test suite and project setup and report failed assertions.
(#210 by @clue, #212 by @WyriHaximus and #209 and #211 by @SimonFrings)
## 1.10.0 (2022-09-08)
* Feature: Full support for PHP 8.2 release.
(#201 by @clue and #207 by @WyriHaximus)
* Feature: Optimize forward compatibility with Promise v3, avoid hitting autoloader.
(#202 by @clue)
* Feature / Fix: Improve error reporting when custom error handler is used.
(#197 by @clue)
* Fix: Fix invalid references in exception stack trace.
(#191 by @clue)
* Minor documentation improvements.
(#195 by @SimonFrings and #203 by @nhedger)
* Improve test suite, update to use default loop and new reactphp/async package.
(#204, #205 and #206 by @clue and #196 by @SimonFrings)
## 1.9.0 (2021-12-20)
* Feature: Full support for PHP 8.1 release and prepare PHP 8.2 compatibility
by refactoring `Parser` to avoid assigning dynamic properties.
(#188 and #186 by @clue and #184 by @SimonFrings)
* Feature: Avoid dependency on `ext-filter`.
(#185 by @clue)
* Feature / Fix: Skip invalid nameserver entries from `resolv.conf` and ignore IPv6 zone IDs.
(#187 by @clue)
* Feature / Fix: Reduce socket read chunk size for queries over TCP/IP.
(#189 by @clue)
## 1.8.0 (2021-07-11)
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
(#182 by @clue)
```php
// old (still supported)
$factory = new React\Dns\Resolver\Factory();
$resolver = $factory->create($config, $loop);
// new (using default loop)
$factory = new React\Dns\Resolver\Factory();
$resolver = $factory->create($config);
```
## 1.7.0 (2021-06-25)
* Feature: Update DNS `Factory` to accept complete `Config` object.
Add new `FallbackExecutor` and use fallback DNS servers when `Config` lists multiple servers.
(#179 and #180 by @clue)
```php
// old (still supported)
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
$resolver = $factory->create($server, $loop);
// new
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
if (!$config->nameservers) {
$config->nameservers[] = '8.8.8.8';
}
$resolver = $factory->create($config, $loop);
```
## 1.6.0 (2021-06-21)
* Feature: Add support for legacy `SPF` record type.
(#178 by @akondas and @clue)
* Fix: Fix integer overflow for TCP/IP chunk size on 32 bit platforms.
(#177 by @clue)
## 1.5.0 (2021-03-05)
* Feature: Improve error reporting when query fails, include domain and query type and DNS server address where applicable.
(#174 by @clue)
* Feature: Improve error handling when sending data to DNS server fails (macOS).
(#171 and #172 by @clue)
* Fix: Improve DNS response parser to limit recursion for compressed labels.
(#169 by @clue)
* Improve test suite, use GitHub actions for continuous integration (CI).
(#170 by @SimonFrings)
## 1.4.0 (2020-09-18)
* Feature: Support upcoming PHP 8.
(#168 by @clue)
* Improve test suite and update to PHPUnit 9.3.
(#164 by @clue, #165 and #166 by @SimonFrings and #167 by @WyriHaximus)
## 1.3.0 (2020-07-10)
* Feature: Forward compatibility with react/promise v3.
(#153 by @WyriHaximus)
* Feature: Support parsing `OPT` records (EDNS0).
(#157 by @clue)
* Fix: Avoid PHP warnings due to lack of args in exception trace on PHP 7.4.
(#160 by @clue)
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
Run tests on PHPUnit 9 and PHP 7.4 and clean up test suite.
(#154 by @reedy, #156 by @clue and #163 by @SimonFrings)
## 1.2.0 (2019-08-15)
* Feature: Add `TcpTransportExecutor` to send DNS queries over TCP/IP connection,
add `SelectiveTransportExecutor` to retry with TCP if UDP is truncated and
automatically select transport protocol when no explicit `udp://` or `tcp://` scheme is given in `Factory`.
(#145, #146, #147 and #148 by @clue)
* Feature: Support escaping literal dots and special characters in domain names.
(#144 by @clue)
## 1.1.0 (2019-07-18)
* Feature: Support parsing `CAA` and `SSHFP` records.
(#141 and #142 by @clue)
* Feature: Add `ResolverInterface` as common interface for `Resolver` class.
(#139 by @clue)
* Fix: Add missing private property definitions and
remove unneeded dependency on `react/stream`.
(#140 and #143 by @clue)
## 1.0.0 (2019-07-11)
* First stable LTS release, now following [SemVer](https://semver.org/).
We'd like to emphasize that this component is production ready and battle-tested.
We plan to support all long-term support (LTS) releases for at least 24 months,
so you have a rock-solid foundation to build on top of.
This update involves a number of BC breaks due to dropped support for
deprecated functionality and some internal API cleanup. We've tried hard to
avoid BC breaks where possible and minimize impact otherwise. We expect that
most consumers of this package will actually not be affected by any BC
breaks, see below for more details:
* BC break: Delete all deprecated APIs, use `Query` objects for `Message` questions
instead of nested arrays and increase code coverage to 100%.
(#130 by @clue)
* BC break: Move `$nameserver` from `ExecutorInterface` to `UdpTransportExecutor`,
remove advanced/internal `UdpTransportExecutor` args for `Parser`/`BinaryDumper` and
add API documentation for `ExecutorInterface`.
(#135, #137 and #138 by @clue)
* BC break: Replace `HeaderBag` attributes with simple `Message` properties.
(#132 by @clue)
* BC break: Mark all `Record` attributes as required, add documentation vs `Query`.
(#136 by @clue)
* BC break: Mark all classes as final to discourage inheritance
(#134 by @WyriHaximus)
## 0.4.19 (2019-07-10)
* Feature: Avoid garbage references when DNS resolution rejects on legacy PHP <= 5.6.
(#133 by @clue)
## 0.4.18 (2019-09-07)
* Feature / Fix: Implement `CachingExecutor` using cache TTL, deprecate old `CachedExecutor`,
respect TTL from response records when caching and do not cache truncated responses.
(#129 by @clue)
* Feature: Limit cache size to 256 last responses by default.
(#127 by @clue)
* Feature: Cooperatively resolve hosts to avoid running same query concurrently.
(#125 by @clue)
## 0.4.17 (2019-04-01)
* Feature: Support parsing `authority` and `additional` records from DNS response.
(#123 by @clue)
* Feature: Support dumping records as part of outgoing binary DNS message.
(#124 by @clue)
* Feature: Forward compatibility with upcoming Cache v0.6 and Cache v1.0
(#121 by @clue)
* Improve test suite to add forward compatibility with PHPUnit 7,
test against PHP 7.3 and use legacy PHPUnit 5 on legacy HHVM.
(#122 by @clue)
## 0.4.16 (2018-11-11)
* Feature: Improve promise cancellation for DNS lookup retries and clean up any garbage references.
(#118 by @clue)
* Fix: Reject parsing malformed DNS response messages such as incomplete DNS response messages,
malformed record data or malformed compressed domain name labels.
(#115 and #117 by @clue)
* Fix: Fix interpretation of TTL as UINT32 with most significant bit unset.
(#116 by @clue)
* Fix: Fix caching advanced MX/SRV/TXT/SOA structures.
(#112 by @clue)
## 0.4.15 (2018-07-02)
* Feature: Add `resolveAll()` method to support custom query types in `Resolver`.
(#110 by @clue and @WyriHaximus)
```php
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});
```
* Feature: Support parsing `NS`, `TXT`, `MX`, `SOA` and `SRV` records.
(#104, #105, #106, #107 and #108 by @clue)
* Feature: Add support for `Message::TYPE_ANY` and parse unknown types as binary data.
(#104 by @clue)
* Feature: Improve error messages for failed queries and improve documentation.
(#109 by @clue)
* Feature: Add reverse DNS lookup example.
(#111 by @clue)
## 0.4.14 (2018-06-26)
* Feature: Add `UdpTransportExecutor`, validate incoming DNS response messages
to avoid cache poisoning attacks and deprecate legacy `Executor`.
(#101 and #103 by @clue)
* Feature: Forward compatibility with Cache 0.5
(#102 by @clue)
* Deprecate legacy `Query::$currentTime` and binary parser data attributes to clean up and simplify API.
(#99 by @clue)
## 0.4.13 (2018-02-27)
* Add `Config::loadSystemConfigBlocking()` to load default system config
and support parsing DNS config on all supported platforms
(`/etc/resolv.conf` on Unix/Linux/Mac and WMIC on Windows)
(#92, #93, #94 and #95 by @clue)
```php
$config = Config::loadSystemConfigBlocking();
$server = $config->nameservers ? reset($config->nameservers) : '8.8.8.8';
```
* Remove unneeded cyclic dependency on react/socket
(#96 by @clue)
## 0.4.12 (2018-01-14)
* Improve test suite by adding forward compatibility with PHPUnit 6,
test against PHP 7.2, fix forward compatibility with upcoming EventLoop releases,
add test group to skip integration tests relying on internet connection
and add minor documentation improvements.
(#85 and #87 by @carusogabriel, #88 and #89 by @clue and #83 by @jsor)
## 0.4.11 (2017-08-25)
* Feature: Support resolving from default hosts file
(#75, #76 and #77 by @clue)
This means that resolving hosts such as `localhost` will now work as
expected across all platforms with no changes required:
```php
$resolver->resolve('localhost')->then(function ($ip) {
echo 'IP: ' . $ip;
});
```
The new `HostsExecutor` exists for advanced usage and is otherwise used
internally for this feature.
## 0.4.10 (2017-08-10)
* Feature: Forward compatibility with EventLoop v1.0 and v0.5 and
lock minimum dependencies and work around circular dependency for tests
(#70 and #71 by @clue)
* Fix: Work around DNS timeout issues for Windows users
(#74 by @clue)
* Documentation and examples for advanced usage
(#66 by @WyriHaximus)
* Remove broken TCP code, do not retry with invalid TCP query
(#73 by @clue)
* Improve test suite by fixing HHVM build for now again and ignore future HHVM build errors and
lock Travis distro so new defaults will not break the build and
fix failing tests for PHP 7.1
(#68 by @WyriHaximus and #69 and #72 by @clue)
## 0.4.9 (2017-05-01)
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
(#61 by @clue)
## 0.4.8 (2017-04-16)
* Feature: Add support for the AAAA record type to the protocol parser
(#58 by @othillo)
* Feature: Add support for the PTR record type to the protocol parser
(#59 by @othillo)
## 0.4.7 (2017-03-31)
* Feature: Forward compatibility with upcoming Socket v0.6 and v0.7 component
(#57 by @clue)
## 0.4.6 (2017-03-11)
* Fix: Fix DNS timeout issues for Windows users and add forward compatibility
with Stream v0.5 and upcoming v0.6
(#53 by @clue)
* Improve test suite by adding PHPUnit to `require-dev`
(#54 by @clue)
## 0.4.5 (2017-03-02)
* Fix: Ensure we ignore the case of the answer
(#51 by @WyriHaximus)
* Feature: Add `TimeoutExecutor` and simplify internal APIs to allow internal
code re-use for upcoming versions.
(#48 and #49 by @clue)
## 0.4.4 (2017-02-13)
* Fix: Fix handling connection and stream errors
(#45 by @clue)
* Feature: Add examples and forward compatibility with upcoming Socket v0.5 component
(#46 and #47 by @clue)
## 0.4.3 (2016-07-31)
* Feature: Allow for cache adapter injection (#38 by @WyriHaximus)
```php
$factory = new React\Dns\Resolver\Factory();
$cache = new MyCustomCacheInstance();
$resolver = $factory->createCached('8.8.8.8', $loop, $cache);
```
* Feature: Support Promise cancellation (#35 by @clue)
```php
$promise = $resolver->resolve('reactphp.org');
$promise->cancel();
```
## 0.4.2 (2016-02-24)
* Repository maintenance, split off from main repo, improve test suite and documentation
* First class support for PHP7 and HHVM (#34 by @clue)
* Adjust compatibility to 5.3 (#30 by @clue)
## 0.4.1 (2014-04-13)
* Bug fix: Fixed PSR-4 autoload path (@marcj/WyriHaximus)
## 0.4.0 (2014-02-02)
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
* BC break: Update to React/Promise 2.0
* Bug fix: Properly resolve CNAME aliases
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
* Bump React dependencies to v0.4
## 0.3.2 (2013-05-10)
* Feature: Support default port for IPv6 addresses (@clue)
## 0.3.0 (2013-04-14)
* Bump React dependencies to v0.3
## 0.2.6 (2012-12-26)
* Feature: New cache component, used by DNS
## 0.2.5 (2012-11-26)
* Version bump
## 0.2.4 (2012-11-18)
* Feature: Change to promise-based API (@jsor)
## 0.2.3 (2012-11-14)
* Version bump
## 0.2.2 (2012-10-28)
* Feature: DNS executor timeout handling (@arnaud-lb)
* Feature: DNS retry executor (@arnaud-lb)
## 0.2.1 (2012-10-14)
* Minor adjustments to DNS parser
## 0.2.0 (2012-09-10)
* Feature: DNS resolver

21
vendor/react/dns/LICENSE vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

453
vendor/react/dns/README.md vendored Executable file
View File

@@ -0,0 +1,453 @@
# DNS
[![CI status](https://github.com/reactphp/dns/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/dns/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/dns?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/dns)
Async DNS resolver for [ReactPHP](https://reactphp.org/).
The main point of the DNS component is to provide async DNS resolution.
However, it is really a toolkit for working with DNS messages, and could
easily be used to create a DNS server.
**Table of contents**
* [Basic usage](#basic-usage)
* [Caching](#caching)
* [Custom cache adapter](#custom-cache-adapter)
* [ResolverInterface](#resolverinterface)
* [resolve()](#resolve)
* [resolveAll()](#resolveall)
* [Advanced usage](#advanced-usage)
* [UdpTransportExecutor](#udptransportexecutor)
* [TcpTransportExecutor](#tcptransportexecutor)
* [SelectiveTransportExecutor](#selectivetransportexecutor)
* [HostsFileExecutor](#hostsfileexecutor)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
* [References](#references)
## Basic usage
The most basic usage is to just create a resolver through the resolver
factory. All you need to give it is a nameserver, then you can start resolving
names, baby!
```php
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
if (!$config->nameservers) {
$config->nameservers[] = '8.8.8.8';
}
$factory = new React\Dns\Resolver\Factory();
$dns = $factory->create($config);
$dns->resolve('igor.io')->then(function ($ip) {
echo "Host: $ip\n";
});
```
See also the [first example](examples).
The `Config` class can be used to load the system default config. This is an
operation that may access the filesystem and block. Ideally, this method should
thus be executed only once before the loop starts and not repeatedly while it is
running.
Note that this class may return an *empty* configuration if the system config
can not be loaded. As such, you'll likely want to apply a default nameserver
as above if none can be found.
> Note that the factory loads the hosts file from the filesystem once when
creating the resolver instance.
Ideally, this method should thus be executed only once before the loop starts
and not repeatedly while it is running.
But there's more.
## Caching
You can cache results by configuring the resolver to use a `CachedExecutor`:
```php
$config = React\Dns\Config\Config::loadSystemConfigBlocking();
if (!$config->nameservers) {
$config->nameservers[] = '8.8.8.8';
}
$factory = new React\Dns\Resolver\Factory();
$dns = $factory->createCached($config);
$dns->resolve('igor.io')->then(function ($ip) {
echo "Host: $ip\n";
});
...
$dns->resolve('igor.io')->then(function ($ip) {
echo "Host: $ip\n";
});
```
If the first call returns before the second, only one query will be executed.
The second result will be served from an in memory cache.
This is particularly useful for long running scripts where the same hostnames
have to be looked up multiple times.
See also the [third example](examples).
### Custom cache adapter
By default, the above will use an in memory cache.
You can also specify a custom cache implementing [`CacheInterface`](https://github.com/reactphp/cache) to handle the record cache instead:
```php
$cache = new React\Cache\ArrayCache();
$factory = new React\Dns\Resolver\Factory();
$dns = $factory->createCached('8.8.8.8', null, $cache);
```
See also the wiki for possible [cache implementations](https://github.com/reactphp/react/wiki/Users#cache-implementations).
## ResolverInterface
<a id="resolver"><!-- legacy reference --></a>
### resolve()
The `resolve(string $domain): PromiseInterface<string>` method can be used to
resolve the given $domain name to a single IPv4 address (type `A` query).
```php
$resolver->resolve('reactphp.org')->then(function ($ip) {
echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
});
```
This is one of the main methods in this package. It sends a DNS query
for the given $domain name to your DNS server and returns a single IP
address on success.
If the DNS server sends a DNS response message that contains more than
one IP address for this query, it will randomly pick one of the IP
addresses from the response. If you want the full list of IP addresses
or want to send a different type of query, you should use the
[`resolveAll()`](#resolveall) method instead.
If the DNS server sends a DNS response message that indicates an error
code, this method will reject with a `RecordNotFoundException`. Its
message and code can be used to check for the response code.
If the DNS communication fails and the server does not respond with a
valid response message, this message will reject with an `Exception`.
Pending DNS queries can be cancelled by cancelling its pending promise like so:
```php
$promise = $resolver->resolve('reactphp.org');
$promise->cancel();
```
### resolveAll()
The `resolveAll(string $host, int $type): PromiseInterface<array>` method can be used to
resolve all record values for the given $domain name and query $type.
```php
$resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});
$resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
});
```
This is one of the main methods in this package. It sends a DNS query
for the given $domain name to your DNS server and returns a list with all
record values on success.
If the DNS server sends a DNS response message that contains one or more
records for this query, it will return a list with all record values
from the response. You can use the `Message::TYPE_*` constants to control
which type of query will be sent. Note that this method always returns a
list of record values, but each record value type depends on the query
type. For example, it returns the IPv4 addresses for type `A` queries,
the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
`CNAME` and `PTR` queries and structured data for other queries. See also
the `Record` documentation for more details.
If the DNS server sends a DNS response message that indicates an error
code, this method will reject with a `RecordNotFoundException`. Its
message and code can be used to check for the response code.
If the DNS communication fails and the server does not respond with a
valid response message, this message will reject with an `Exception`.
Pending DNS queries can be cancelled by cancelling its pending promise like so:
```php
$promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
$promise->cancel();
```
## Advanced Usage
### UdpTransportExecutor
The `UdpTransportExecutor` can be used to
send DNS queries over a UDP transport.
This is the main class that sends a DNS query to your DNS server and is used
internally by the `Resolver` for the actual message transport.
For more advanced usages one can utilize this class directly.
The following example looks up the `IPv6` address for `igor.io`.
```php
$executor = new UdpTransportExecutor('8.8.8.8:53');
$executor->query(
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
)->then(function (Message $message) {
foreach ($message->answers as $answer) {
echo 'IPv6: ' . $answer->data . PHP_EOL;
}
}, 'printf');
```
See also the [fourth example](examples).
Note that this executor does not implement a timeout, so you will very likely
want to use this in combination with a `TimeoutExecutor` like this:
```php
$executor = new TimeoutExecutor(
new UdpTransportExecutor($nameserver),
3.0
);
```
Also note that this executor uses an unreliable UDP transport and that it
does not implement any retry logic, so you will likely want to use this in
combination with a `RetryExecutor` like this:
```php
$executor = new RetryExecutor(
new TimeoutExecutor(
new UdpTransportExecutor($nameserver),
3.0
)
);
```
Note that this executor is entirely async and as such allows you to execute
any number of queries concurrently. You should probably limit the number of
concurrent queries in your application or you're very likely going to face
rate limitations and bans on the resolver end. For many common applications,
you may want to avoid sending the same query multiple times when the first
one is still pending, so you will likely want to use this in combination with
a `CoopExecutor` like this:
```php
$executor = new CoopExecutor(
new RetryExecutor(
new TimeoutExecutor(
new UdpTransportExecutor($nameserver),
3.0
)
)
);
```
> Internally, this class uses PHP's UDP sockets and does not take advantage
of [react/datagram](https://github.com/reactphp/datagram) purely for
organizational reasons to avoid a cyclic dependency between the two
packages. Higher-level components should take advantage of the Datagram
component instead of reimplementing this socket logic from scratch.
### TcpTransportExecutor
The `TcpTransportExecutor` class can be used to
send DNS queries over a TCP/IP stream transport.
This is one of the main classes that send a DNS query to your DNS server.
For more advanced usages one can utilize this class directly.
The following example looks up the `IPv6` address for `reactphp.org`.
```php
$executor = new TcpTransportExecutor('8.8.8.8:53');
$executor->query(
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
)->then(function (Message $message) {
foreach ($message->answers as $answer) {
echo 'IPv6: ' . $answer->data . PHP_EOL;
}
}, 'printf');
```
See also [example #92](examples).
Note that this executor does not implement a timeout, so you will very likely
want to use this in combination with a `TimeoutExecutor` like this:
```php
$executor = new TimeoutExecutor(
new TcpTransportExecutor($nameserver),
3.0
);
```
Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
transport, so you do not necessarily have to implement any retry logic.
Note that this executor is entirely async and as such allows you to execute
queries concurrently. The first query will establish a TCP/IP socket
connection to the DNS server which will be kept open for a short period.
Additional queries will automatically reuse this existing socket connection
to the DNS server, will pipeline multiple requests over this single
connection and will keep an idle connection open for a short period. The
initial TCP/IP connection overhead may incur a slight delay if you only send
occasional queries when sending a larger number of concurrent queries over
an existing connection, it becomes increasingly more efficient and avoids
creating many concurrent sockets like the UDP-based executor. You may still
want to limit the number of (concurrent) queries in your application or you
may be facing rate limitations and bans on the resolver end. For many common
applications, you may want to avoid sending the same query multiple times
when the first one is still pending, so you will likely want to use this in
combination with a `CoopExecutor` like this:
```php
$executor = new CoopExecutor(
new TimeoutExecutor(
new TcpTransportExecutor($nameserver),
3.0
)
);
```
> Internally, this class uses PHP's TCP/IP sockets and does not take advantage
of [react/socket](https://github.com/reactphp/socket) purely for
organizational reasons to avoid a cyclic dependency between the two
packages. Higher-level components should take advantage of the Socket
component instead of reimplementing this socket logic from scratch.
### SelectiveTransportExecutor
The `SelectiveTransportExecutor` class can be used to
Send DNS queries over a UDP or TCP/IP stream transport.
This class will automatically choose the correct transport protocol to send
a DNS query to your DNS server. It will always try to send it over the more
efficient UDP transport first. If this query yields a size related issue
(truncated messages), it will retry over a streaming TCP/IP transport.
For more advanced usages one can utilize this class directly.
The following example looks up the `IPv6` address for `reactphp.org`.
```php
$executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
$executor->query(
new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
)->then(function (Message $message) {
foreach ($message->answers as $answer) {
echo 'IPv6: ' . $answer->data . PHP_EOL;
}
}, 'printf');
```
Note that this executor only implements the logic to select the correct
transport for the given DNS query. Implementing the correct transport logic,
implementing timeouts and any retry logic is left up to the given executors,
see also [`UdpTransportExecutor`](#udptransportexecutor) and
[`TcpTransportExecutor`](#tcptransportexecutor) for more details.
Note that this executor is entirely async and as such allows you to execute
any number of queries concurrently. You should probably limit the number of
concurrent queries in your application or you're very likely going to face
rate limitations and bans on the resolver end. For many common applications,
you may want to avoid sending the same query multiple times when the first
one is still pending, so you will likely want to use this in combination with
a `CoopExecutor` like this:
```php
$executor = new CoopExecutor(
new SelectiveTransportExecutor(
$datagramExecutor,
$streamExecutor
)
);
```
### HostsFileExecutor
Note that the above `UdpTransportExecutor` class always performs an actual DNS query.
If you also want to take entries from your hosts file into account, you may
use this code:
```php
$hosts = \React\Dns\Config\HostsFile::loadFromPathBlocking();
$executor = new UdpTransportExecutor('8.8.8.8:53');
$executor = new HostsFileExecutor($hosts, $executor);
$executor->query(
new Query('localhost', Message::TYPE_A, Message::CLASS_IN)
);
```
## Install
The recommended way to install this library is [through Composer](https://getcomposer.org/).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This project follows [SemVer](https://semver.org/).
This will install the latest supported version:
```bash
composer require react/dns:^1.13
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
HHVM.
It's *highly recommended to use the latest supported PHP version* for this project.
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org/):
```bash
composer install
```
To run the test suite, go to the project root and run:
```bash
vendor/bin/phpunit
```
The test suite also contains a number of functional integration tests that rely
on a stable internet connection.
If you do not want to run these, they can simply be skipped like this:
```bash
vendor/bin/phpunit --exclude-group internet
```
## License
MIT, see [LICENSE file](LICENSE).
## References
* [RFC 1034](https://tools.ietf.org/html/rfc1034) Domain Names - Concepts and Facilities
* [RFC 1035](https://tools.ietf.org/html/rfc1035) Domain Names - Implementation and Specification

49
vendor/react/dns/composer.json vendored Executable file
View File

@@ -0,0 +1,49 @@
{
"name": "react/dns",
"description": "Async DNS resolver for ReactPHP",
"keywords": ["dns", "dns-resolver", "ReactPHP", "async"],
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"homepage": "https://clue.engineering/",
"email": "christian@clue.engineering"
},
{
"name": "Cees-Jan Kiewiet",
"homepage": "https://wyrihaximus.net/",
"email": "reactphp@ceesjankiewiet.nl"
},
{
"name": "Jan Sorgalla",
"homepage": "https://sorgalla.com/",
"email": "jsorgalla@gmail.com"
},
{
"name": "Chris Boden",
"homepage": "https://cboden.dev/",
"email": "cboden@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"react/cache": "^1.0 || ^0.6 || ^0.5",
"react/event-loop": "^1.2",
"react/promise": "^3.2 || ^2.7 || ^1.2.1"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"react/async": "^4.3 || ^3 || ^2",
"react/promise-timer": "^1.11"
},
"autoload": {
"psr-4": {
"React\\Dns\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"React\\Tests\\Dns\\": "tests/"
}
}
}

7
vendor/react/dns/src/BadServerException.php vendored Executable file
View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns;
final class BadServerException extends \Exception
{
}

137
vendor/react/dns/src/Config/Config.php vendored Executable file
View File

@@ -0,0 +1,137 @@
<?php
namespace React\Dns\Config;
use RuntimeException;
final class Config
{
/**
* Loads the system DNS configuration
*
* Note that this method may block while loading its internal files and/or
* commands and should thus be used with care! While this should be
* relatively fast for most systems, it remains unknown if this may block
* under certain circumstances. In particular, this method should only be
* executed before the loop starts, not while it is running.
*
* Note that this method will try to access its files and/or commands and
* try to parse its output. Currently, this will only parse valid nameserver
* entries from its output and will ignore all other output without
* complaining.
*
* Note that the previous section implies that this may return an empty
* `Config` object if no valid nameserver entries can be found.
*
* @return self
* @codeCoverageIgnore
*/
public static function loadSystemConfigBlocking()
{
// Use WMIC output on Windows
if (DIRECTORY_SEPARATOR === '\\') {
return self::loadWmicBlocking();
}
// otherwise (try to) load from resolv.conf
try {
return self::loadResolvConfBlocking();
} catch (RuntimeException $ignored) {
// return empty config if parsing fails (file not found)
return new self();
}
}
/**
* Loads a resolv.conf file (from the given path or default location)
*
* Note that this method blocks while loading the given path and should
* thus be used with care! While this should be relatively fast for normal
* resolv.conf files, this may be an issue if this file is located on a slow
* device or contains an excessive number of entries. In particular, this
* method should only be executed before the loop starts, not while it is
* running.
*
* Note that this method will throw if the given file can not be loaded,
* such as if it is not readable or does not exist. In particular, this file
* is not available on Windows.
*
* Currently, this will only parse valid "nameserver X" lines from the
* given file contents. Lines can be commented out with "#" and ";" and
* invalid lines will be ignored without complaining. See also
* `man resolv.conf` for more details.
*
* Note that the previous section implies that this may return an empty
* `Config` object if no valid "nameserver X" lines can be found. See also
* `man resolv.conf` which suggests that the DNS server on the localhost
* should be used in this case. This is left up to higher level consumers
* of this API.
*
* @param ?string $path (optional) path to resolv.conf file or null=load default location
* @return self
* @throws RuntimeException if the path can not be loaded (does not exist)
*/
public static function loadResolvConfBlocking($path = null)
{
if ($path === null) {
$path = '/etc/resolv.conf';
}
$contents = @file_get_contents($path);
if ($contents === false) {
throw new RuntimeException('Unable to load resolv.conf file "' . $path . '"');
}
$matches = array();
preg_match_all('/^nameserver\s+(\S+)\s*$/m', $contents, $matches);
$config = new self();
foreach ($matches[1] as $ip) {
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
$ip = substr($ip, 0, $pos);
}
if (@inet_pton($ip) !== false) {
$config->nameservers[] = $ip;
}
}
return $config;
}
/**
* Loads the DNS configurations from Windows's WMIC (from the given command or default command)
*
* Note that this method blocks while loading the given command and should
* thus be used with care! While this should be relatively fast for normal
* WMIC commands, it remains unknown if this may block under certain
* circumstances. In particular, this method should only be executed before
* the loop starts, not while it is running.
*
* Note that this method will only try to execute the given command try to
* parse its output, irrespective of whether this command exists. In
* particular, this command is only available on Windows. Currently, this
* will only parse valid nameserver entries from the command output and will
* ignore all other output without complaining.
*
* Note that the previous section implies that this may return an empty
* `Config` object if no valid nameserver entries can be found.
*
* @param ?string $command (advanced) should not be given (NULL) unless you know what you're doing
* @return self
* @link https://ss64.com/nt/wmic.html
*/
public static function loadWmicBlocking($command = null)
{
$contents = shell_exec($command === null ? 'wmic NICCONFIG get "DNSServerSearchOrder" /format:CSV' : $command);
preg_match_all('/(?<=[{;,"])([\da-f.:]{4,})(?=[};,"])/i', $contents, $matches);
$config = new self();
$config->nameservers = $matches[1];
return $config;
}
public $nameservers = array();
}

153
vendor/react/dns/src/Config/HostsFile.php vendored Executable file
View File

@@ -0,0 +1,153 @@
<?php
namespace React\Dns\Config;
use RuntimeException;
/**
* Represents a static hosts file which maps hostnames to IPs
*
* Hosts files are used on most systems to avoid actually hitting the DNS for
* certain common hostnames.
*
* Most notably, this file usually contains an entry to map "localhost" to the
* local IP. Windows is a notable exception here, as Windows does not actually
* include "localhost" in this file by default. To compensate for this, this
* class may explicitly be wrapped in another HostsFile instance which
* hard-codes these entries for Windows (see also Factory).
*
* This class mostly exists to abstract the parsing/extraction process so this
* can be replaced with a faster alternative in the future.
*/
class HostsFile
{
/**
* Returns the default path for the hosts file on this system
*
* @return string
* @codeCoverageIgnore
*/
public static function getDefaultPath()
{
// use static path for all Unix-based systems
if (DIRECTORY_SEPARATOR !== '\\') {
return '/etc/hosts';
}
// Windows actually stores the path in the registry under
// \HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\DataBasePath
$path = '%SystemRoot%\\system32\drivers\etc\hosts';
$base = getenv('SystemRoot');
if ($base === false) {
$base = 'C:\\Windows';
}
return str_replace('%SystemRoot%', $base, $path);
}
/**
* Loads a hosts file (from the given path or default location)
*
* Note that this method blocks while loading the given path and should
* thus be used with care! While this should be relatively fast for normal
* hosts file, this may be an issue if this file is located on a slow device
* or contains an excessive number of entries. In particular, this method
* should only be executed before the loop starts, not while it is running.
*
* @param ?string $path (optional) path to hosts file or null=load default location
* @return self
* @throws RuntimeException if the path can not be loaded (does not exist)
*/
public static function loadFromPathBlocking($path = null)
{
if ($path === null) {
$path = self::getDefaultPath();
}
$contents = @file_get_contents($path);
if ($contents === false) {
throw new RuntimeException('Unable to load hosts file "' . $path . '"');
}
return new self($contents);
}
private $contents;
/**
* Instantiate new hosts file with the given hosts file contents
*
* @param string $contents
*/
public function __construct($contents)
{
// remove all comments from the contents
$contents = preg_replace('/[ \t]*#.*/', '', strtolower($contents));
$this->contents = $contents;
}
/**
* Returns all IPs for the given hostname
*
* @param string $name
* @return string[]
*/
public function getIpsForHost($name)
{
$name = strtolower($name);
$ips = array();
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
$parts = preg_split('/\s+/', $line);
$ip = array_shift($parts);
if ($parts && array_search($name, $parts) !== false) {
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
if (strpos($ip, ':') !== false && ($pos = strpos($ip, '%')) !== false) {
$ip = substr($ip, 0, $pos);
}
if (@inet_pton($ip) !== false) {
$ips[] = $ip;
}
}
}
return $ips;
}
/**
* Returns all hostnames for the given IPv4 or IPv6 address
*
* @param string $ip
* @return string[]
*/
public function getHostsForIp($ip)
{
// check binary representation of IP to avoid string case and short notation
$ip = @inet_pton($ip);
if ($ip === false) {
return array();
}
$names = array();
foreach (preg_split('/\r?\n/', $this->contents) as $line) {
$parts = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
$addr = (string) array_shift($parts);
// remove IPv6 zone ID (`fe80::1%lo0` => `fe80:1`)
if (strpos($addr, ':') !== false && ($pos = strpos($addr, '%')) !== false) {
$addr = substr($addr, 0, $pos);
}
if (@inet_pton($addr) === $ip) {
foreach ($parts as $part) {
$names[] = $part;
}
}
}
return $names;
}
}

230
vendor/react/dns/src/Model/Message.php vendored Executable file
View File

@@ -0,0 +1,230 @@
<?php
namespace React\Dns\Model;
use React\Dns\Query\Query;
/**
* This class represents an outgoing query message or an incoming response message
*
* @link https://tools.ietf.org/html/rfc1035#section-4.1.1
*/
final class Message
{
const TYPE_A = 1;
const TYPE_NS = 2;
const TYPE_CNAME = 5;
const TYPE_SOA = 6;
const TYPE_PTR = 12;
const TYPE_MX = 15;
const TYPE_TXT = 16;
const TYPE_AAAA = 28;
const TYPE_SRV = 33;
const TYPE_SSHFP = 44;
/**
* pseudo-type for EDNS0
*
* These are included in the additional section and usually not in answer section.
* Defined in [RFC 6891](https://tools.ietf.org/html/rfc6891) (or older
* [RFC 2671](https://tools.ietf.org/html/rfc2671)).
*
* The OPT record uses the "class" field to store the maximum size.
*
* The OPT record uses the "ttl" field to store additional flags.
*/
const TYPE_OPT = 41;
/**
* Sender Policy Framework (SPF) had a dedicated SPF type which has been
* deprecated in favor of reusing the existing TXT type.
*
* @deprecated https://datatracker.ietf.org/doc/html/rfc7208#section-3.1
* @see self::TYPE_TXT
*/
const TYPE_SPF = 99;
const TYPE_ANY = 255;
const TYPE_CAA = 257;
const CLASS_IN = 1;
const OPCODE_QUERY = 0;
const OPCODE_IQUERY = 1; // inverse query
const OPCODE_STATUS = 2;
const RCODE_OK = 0;
const RCODE_FORMAT_ERROR = 1;
const RCODE_SERVER_FAILURE = 2;
const RCODE_NAME_ERROR = 3;
const RCODE_NOT_IMPLEMENTED = 4;
const RCODE_REFUSED = 5;
/**
* The edns-tcp-keepalive EDNS0 Option
*
* Option value contains a `?float` with timeout in seconds (in 0.1s steps)
* for DNS response or `null` for DNS query.
*
* @link https://tools.ietf.org/html/rfc7828
*/
const OPT_TCP_KEEPALIVE = 11;
/**
* The EDNS(0) Padding Option
*
* Option value contains a `string` with binary data (usually variable
* number of null bytes)
*
* @link https://tools.ietf.org/html/rfc7830
*/
const OPT_PADDING = 12;
/**
* Creates a new request message for the given query
*
* @param Query $query
* @return self
*/
public static function createRequestForQuery(Query $query)
{
$request = new Message();
$request->id = self::generateId();
$request->rd = true;
$request->questions[] = $query;
return $request;
}
/**
* Creates a new response message for the given query with the given answer records
*
* @param Query $query
* @param Record[] $answers
* @return self
*/
public static function createResponseWithAnswersForQuery(Query $query, array $answers)
{
$response = new Message();
$response->id = self::generateId();
$response->qr = true;
$response->rd = true;
$response->questions[] = $query;
foreach ($answers as $record) {
$response->answers[] = $record;
}
return $response;
}
/**
* generates a random 16 bit message ID
*
* This uses a CSPRNG so that an outside attacker that is sending spoofed
* DNS response messages can not guess the message ID to avoid possible
* cache poisoning attacks.
*
* The `random_int()` function is only available on PHP 7+ or when
* https://github.com/paragonie/random_compat is installed. As such, using
* the latest supported PHP version is highly recommended. This currently
* falls back to a less secure random number generator on older PHP versions
* in the hope that this system is properly protected against outside
* attackers, for example by using one of the common local DNS proxy stubs.
*
* @return int
* @see self::getId()
* @codeCoverageIgnore
*/
private static function generateId()
{
if (function_exists('random_int')) {
return random_int(0, 0xffff);
}
return mt_rand(0, 0xffff);
}
/**
* The 16 bit message ID
*
* The response message ID has to match the request message ID. This allows
* the receiver to verify this is the correct response message. An outside
* attacker may try to inject fake responses by "guessing" the message ID,
* so this should use a proper CSPRNG to avoid possible cache poisoning.
*
* @var int 16 bit message ID
* @see self::generateId()
*/
public $id = 0;
/**
* @var bool Query/Response flag, query=false or response=true
*/
public $qr = false;
/**
* @var int specifies the kind of query (4 bit), see self::OPCODE_* constants
* @see self::OPCODE_QUERY
*/
public $opcode = self::OPCODE_QUERY;
/**
*
* @var bool Authoritative Answer
*/
public $aa = false;
/**
* @var bool TrunCation
*/
public $tc = false;
/**
* @var bool Recursion Desired
*/
public $rd = false;
/**
* @var bool Recursion Available
*/
public $ra = false;
/**
* @var int response code (4 bit), see self::RCODE_* constants
* @see self::RCODE_OK
*/
public $rcode = Message::RCODE_OK;
/**
* An array of Query objects
*
* ```php
* $questions = array(
* new Query(
* 'reactphp.org',
* Message::TYPE_A,
* Message::CLASS_IN
* )
* );
* ```
*
* @var Query[]
*/
public $questions = array();
/**
* @var Record[]
*/
public $answers = array();
/**
* @var Record[]
*/
public $authority = array();
/**
* @var Record[]
*/
public $additional = array();
}

153
vendor/react/dns/src/Model/Record.php vendored Executable file
View File

@@ -0,0 +1,153 @@
<?php
namespace React\Dns\Model;
/**
* This class represents a single resulting record in a response message
*
* It uses a structure similar to `\React\Dns\Query\Query`, but does include
* fields for resulting TTL and resulting record data (IPs etc.).
*
* @link https://tools.ietf.org/html/rfc1035#section-4.1.3
* @see \React\Dns\Query\Query
*/
final class Record
{
/**
* @var string hostname without trailing dot, for example "reactphp.org"
*/
public $name;
/**
* @var int see Message::TYPE_* constants (UINT16)
*/
public $type;
/**
* Defines the network class, usually `Message::CLASS_IN`.
*
* For `OPT` records (EDNS0), this defines the maximum message size instead.
*
* @var int see Message::CLASS_IN constant (UINT16)
* @see Message::CLASS_IN
*/
public $class;
/**
* Defines the maximum time-to-live (TTL) in seconds
*
* For `OPT` records (EDNS0), this defines additional flags instead.
*
* @var int maximum TTL in seconds (UINT32, most significant bit always unset)
* @link https://tools.ietf.org/html/rfc2181#section-8
* @link https://tools.ietf.org/html/rfc6891#section-6.1.3 for `OPT` records (EDNS0)
*/
public $ttl;
/**
* The payload data for this record
*
* The payload data format depends on the record type. As a rule of thumb,
* this library will try to express this in a way that can be consumed
* easily without having to worry about DNS internals and its binary transport:
*
* - A:
* IPv4 address string, for example "192.168.1.1".
*
* - AAAA:
* IPv6 address string, for example "::1".
*
* - CNAME / PTR / NS:
* The hostname without trailing dot, for example "reactphp.org".
*
* - TXT:
* List of string values, for example `["v=spf1 include:example.com"]`.
* This is commonly a list with only a single string value, but this
* technically allows multiple strings (0-255 bytes each) in a single
* record. This is rarely used and depending on application you may want
* to join these together or handle them separately. Each string can
* transport any binary data, its character encoding is not defined (often
* ASCII/UTF-8 in practice). [RFC 1464](https://tools.ietf.org/html/rfc1464)
* suggests using key-value pairs such as `["name=test","version=1"]`, but
* interpretation of this is not enforced and left up to consumers of this
* library (used for DNS-SD/Zeroconf and others).
*
* - MX:
* Mail server priority (UINT16) and target hostname without trailing dot,
* for example `{"priority":10,"target":"mx.example.com"}`.
* The payload data uses an associative array with fixed keys "priority"
* (also commonly referred to as weight or preference) and "target" (also
* referred to as exchange). If a response message contains multiple
* records of this type, targets should be sorted by priority (lowest
* first) - this is left up to consumers of this library (used for SMTP).
*
* - SRV:
* Service priority (UINT16), service weight (UINT16), service port (UINT16)
* and target hostname without trailing dot, for example
* `{"priority":10,"weight":50,"port":8080,"target":"example.com"}`.
* The payload data uses an associative array with fixed keys "priority",
* "weight", "port" and "target" (also referred to as name).
* The target may be an empty host name string if the service is decidedly
* not available. If a response message contains multiple records of this
* type, targets should be sorted by priority (lowest first) and selected
* randomly according to their weight - this is left up to consumers of
* this library, see also [RFC 2782](https://tools.ietf.org/html/rfc2782)
* for more details.
*
* - SSHFP:
* Includes algorithm (UNIT8), fingerprint type (UNIT8) and fingerprint
* value as lower case hex string, for example:
* `{"algorithm":1,"type":1,"fingerprint":"0123456789abcdef..."}`
* See also https://www.iana.org/assignments/dns-sshfp-rr-parameters/dns-sshfp-rr-parameters.xhtml
* for algorithm and fingerprint type assignments.
*
* - SOA:
* Includes master hostname without trailing dot, responsible person email
* as hostname without trailing dot and serial, refresh, retry, expire and
* minimum times in seconds (UINT32 each), for example:
* `{"mname":"ns.example.com","rname":"hostmaster.example.com","serial":
* 2018082601,"refresh":3600,"retry":1800,"expire":60000,"minimum":3600}`.
*
* - CAA:
* Includes flag (UNIT8), tag string and value string, for example:
* `{"flag":128,"tag":"issue","value":"letsencrypt.org"}`
*
* - OPT:
* Special pseudo-type for EDNS0. Includes an array of additional opt codes
* with a value according to the respective OPT code. See `Message::OPT_*`
* for list of supported OPT codes. Any other OPT code not currently
* supported will be an opaque binary string containing the raw data
* as transported in the DNS record. For forwards compatibility, you should
* not rely on this format for unknown types. Future versions may add
* support for new types and this may then parse the payload data
* appropriately - this will not be considered a BC break. See also
* [RFC 6891](https://tools.ietf.org/html/rfc6891) for more details.
*
* - Any other unknown type:
* An opaque binary string containing the RDATA as transported in the DNS
* record. For forwards compatibility, you should not rely on this format
* for unknown types. Future versions may add support for new types and
* this may then parse the payload data appropriately - this will not be
* considered a BC break. See the format definition of known types above
* for more details.
*
* @var string|string[]|array
*/
public $data;
/**
* @param string $name
* @param int $type
* @param int $class
* @param int $ttl
* @param string|string[]|array $data
*/
public function __construct($name, $type, $class, $ttl, $data)
{
$this->name = $name;
$this->type = $type;
$this->class = $class;
$this->ttl = $ttl;
$this->data = $data;
}
}

199
vendor/react/dns/src/Protocol/BinaryDumper.php vendored Executable file
View File

@@ -0,0 +1,199 @@
<?php
namespace React\Dns\Protocol;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Dns\Query\Query;
final class BinaryDumper
{
/**
* @param Message $message
* @return string
*/
public function toBinary(Message $message)
{
$data = '';
$data .= $this->headerToBinary($message);
$data .= $this->questionToBinary($message->questions);
$data .= $this->recordsToBinary($message->answers);
$data .= $this->recordsToBinary($message->authority);
$data .= $this->recordsToBinary($message->additional);
return $data;
}
/**
* @param Message $message
* @return string
*/
private function headerToBinary(Message $message)
{
$data = '';
$data .= pack('n', $message->id);
$flags = 0x00;
$flags = ($flags << 1) | ($message->qr ? 1 : 0);
$flags = ($flags << 4) | $message->opcode;
$flags = ($flags << 1) | ($message->aa ? 1 : 0);
$flags = ($flags << 1) | ($message->tc ? 1 : 0);
$flags = ($flags << 1) | ($message->rd ? 1 : 0);
$flags = ($flags << 1) | ($message->ra ? 1 : 0);
$flags = ($flags << 3) | 0; // skip unused zero bit
$flags = ($flags << 4) | $message->rcode;
$data .= pack('n', $flags);
$data .= pack('n', count($message->questions));
$data .= pack('n', count($message->answers));
$data .= pack('n', count($message->authority));
$data .= pack('n', count($message->additional));
return $data;
}
/**
* @param Query[] $questions
* @return string
*/
private function questionToBinary(array $questions)
{
$data = '';
foreach ($questions as $question) {
$data .= $this->domainNameToBinary($question->name);
$data .= pack('n*', $question->type, $question->class);
}
return $data;
}
/**
* @param Record[] $records
* @return string
*/
private function recordsToBinary(array $records)
{
$data = '';
foreach ($records as $record) {
/* @var $record Record */
switch ($record->type) {
case Message::TYPE_A:
case Message::TYPE_AAAA:
$binary = \inet_pton($record->data);
break;
case Message::TYPE_CNAME:
case Message::TYPE_NS:
case Message::TYPE_PTR:
$binary = $this->domainNameToBinary($record->data);
break;
case Message::TYPE_TXT:
case Message::TYPE_SPF:
$binary = $this->textsToBinary($record->data);
break;
case Message::TYPE_MX:
$binary = \pack(
'n',
$record->data['priority']
);
$binary .= $this->domainNameToBinary($record->data['target']);
break;
case Message::TYPE_SRV:
$binary = \pack(
'n*',
$record->data['priority'],
$record->data['weight'],
$record->data['port']
);
$binary .= $this->domainNameToBinary($record->data['target']);
break;
case Message::TYPE_SOA:
$binary = $this->domainNameToBinary($record->data['mname']);
$binary .= $this->domainNameToBinary($record->data['rname']);
$binary .= \pack(
'N*',
$record->data['serial'],
$record->data['refresh'],
$record->data['retry'],
$record->data['expire'],
$record->data['minimum']
);
break;
case Message::TYPE_CAA:
$binary = \pack(
'C*',
$record->data['flag'],
\strlen($record->data['tag'])
);
$binary .= $record->data['tag'];
$binary .= $record->data['value'];
break;
case Message::TYPE_SSHFP:
$binary = \pack(
'CCH*',
$record->data['algorithm'],
$record->data['type'],
$record->data['fingerprint']
);
break;
case Message::TYPE_OPT:
$binary = '';
foreach ($record->data as $opt => $value) {
if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
$value = \pack('n', round($value * 10));
}
$binary .= \pack('n*', $opt, \strlen((string) $value)) . $value;
}
break;
default:
// RDATA is already stored as binary value for unknown record types
$binary = $record->data;
}
$data .= $this->domainNameToBinary($record->name);
$data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
$data .= $binary;
}
return $data;
}
/**
* @param string[] $texts
* @return string
*/
private function textsToBinary(array $texts)
{
$data = '';
foreach ($texts as $text) {
$data .= \chr(\strlen($text)) . $text;
}
return $data;
}
/**
* @param string $host
* @return string
*/
private function domainNameToBinary($host)
{
if ($host === '') {
return "\0";
}
// break up domain name at each dot that is not preceeded by a backslash (escaped notation)
return $this->textsToBinary(
\array_map(
'stripcslashes',
\preg_split(
'/(?<!\\\\)\./',
$host . '.'
)
)
);
}
}

356
vendor/react/dns/src/Protocol/Parser.php vendored Executable file
View File

@@ -0,0 +1,356 @@
<?php
namespace React\Dns\Protocol;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Dns\Query\Query;
use InvalidArgumentException;
/**
* DNS protocol parser
*
* Obsolete and uncommon types and classes are not implemented.
*/
final class Parser
{
/**
* Parses the given raw binary message into a Message object
*
* @param string $data
* @throws InvalidArgumentException
* @return Message
*/
public function parseMessage($data)
{
$message = $this->parse($data, 0);
if ($message === null) {
throw new InvalidArgumentException('Unable to parse binary message');
}
return $message;
}
/**
* @param string $data
* @param int $consumed
* @return ?Message
*/
private function parse($data, $consumed)
{
if (!isset($data[12 - 1])) {
return null;
}
list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12)));
$message = new Message();
$message->id = $id;
$message->rcode = $fields & 0xf;
$message->ra = (($fields >> 7) & 1) === 1;
$message->rd = (($fields >> 8) & 1) === 1;
$message->tc = (($fields >> 9) & 1) === 1;
$message->aa = (($fields >> 10) & 1) === 1;
$message->opcode = ($fields >> 11) & 0xf;
$message->qr = (($fields >> 15) & 1) === 1;
$consumed += 12;
// parse all questions
for ($i = $qdCount; $i > 0; --$i) {
list($question, $consumed) = $this->parseQuestion($data, $consumed);
if ($question === null) {
return null;
} else {
$message->questions[] = $question;
}
}
// parse all answer records
for ($i = $anCount; $i > 0; --$i) {
list($record, $consumed) = $this->parseRecord($data, $consumed);
if ($record === null) {
return null;
} else {
$message->answers[] = $record;
}
}
// parse all authority records
for ($i = $nsCount; $i > 0; --$i) {
list($record, $consumed) = $this->parseRecord($data, $consumed);
if ($record === null) {
return null;
} else {
$message->authority[] = $record;
}
}
// parse all additional records
for ($i = $arCount; $i > 0; --$i) {
list($record, $consumed) = $this->parseRecord($data, $consumed);
if ($record === null) {
return null;
} else {
$message->additional[] = $record;
}
}
return $message;
}
/**
* @param string $data
* @param int $consumed
* @return array
*/
private function parseQuestion($data, $consumed)
{
list($labels, $consumed) = $this->readLabels($data, $consumed);
if ($labels === null || !isset($data[$consumed + 4 - 1])) {
return array(null, null);
}
list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
$consumed += 4;
return array(
new Query(
implode('.', $labels),
$type,
$class
),
$consumed
);
}
/**
* @param string $data
* @param int $consumed
* @return array An array with a parsed Record on success or array with null if data is invalid/incomplete
*/
private function parseRecord($data, $consumed)
{
list($name, $consumed) = $this->readDomain($data, $consumed);
if ($name === null || !isset($data[$consumed + 10 - 1])) {
return array(null, null);
}
list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4)));
$consumed += 4;
list($ttl) = array_values(unpack('N', substr($data, $consumed, 4)));
$consumed += 4;
// TTL is a UINT32 that must not have most significant bit set for BC reasons
if ($ttl < 0 || $ttl >= 1 << 31) {
$ttl = 0;
}
list($rdLength) = array_values(unpack('n', substr($data, $consumed, 2)));
$consumed += 2;
if (!isset($data[$consumed + $rdLength - 1])) {
return array(null, null);
}
$rdata = null;
$expected = $consumed + $rdLength;
if (Message::TYPE_A === $type) {
if ($rdLength === 4) {
$rdata = inet_ntop(substr($data, $consumed, $rdLength));
$consumed += $rdLength;
}
} elseif (Message::TYPE_AAAA === $type) {
if ($rdLength === 16) {
$rdata = inet_ntop(substr($data, $consumed, $rdLength));
$consumed += $rdLength;
}
} elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) {
list($rdata, $consumed) = $this->readDomain($data, $consumed);
} elseif (Message::TYPE_TXT === $type || Message::TYPE_SPF === $type) {
$rdata = array();
while ($consumed < $expected) {
$len = ord($data[$consumed]);
$rdata[] = (string)substr($data, $consumed + 1, $len);
$consumed += $len + 1;
}
} elseif (Message::TYPE_MX === $type) {
if ($rdLength > 2) {
list($priority) = array_values(unpack('n', substr($data, $consumed, 2)));
list($target, $consumed) = $this->readDomain($data, $consumed + 2);
$rdata = array(
'priority' => $priority,
'target' => $target
);
}
} elseif (Message::TYPE_SRV === $type) {
if ($rdLength > 6) {
list($priority, $weight, $port) = array_values(unpack('n*', substr($data, $consumed, 6)));
list($target, $consumed) = $this->readDomain($data, $consumed + 6);
$rdata = array(
'priority' => $priority,
'weight' => $weight,
'port' => $port,
'target' => $target
);
}
} elseif (Message::TYPE_SSHFP === $type) {
if ($rdLength > 2) {
list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2)));
$fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2));
$consumed += $rdLength;
$rdata = array(
'algorithm' => $algorithm,
'type' => $hash,
'fingerprint' => $fingerprint
);
}
} elseif (Message::TYPE_SOA === $type) {
list($mname, $consumed) = $this->readDomain($data, $consumed);
list($rname, $consumed) = $this->readDomain($data, $consumed);
if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) {
list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20)));
$consumed += 20;
$rdata = array(
'mname' => $mname,
'rname' => $rname,
'serial' => $serial,
'refresh' => $refresh,
'retry' => $retry,
'expire' => $expire,
'minimum' => $minimum
);
}
} elseif (Message::TYPE_OPT === $type) {
$rdata = array();
while (isset($data[$consumed + 4 - 1])) {
list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4)));
$value = (string) substr($data, $consumed + 4, $length);
if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') {
$value = null;
} elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) {
list($value) = array_values(unpack('n', $value));
$value = round($value * 0.1, 1);
} elseif ($code === Message::OPT_TCP_KEEPALIVE) {
break;
}
$rdata[$code] = $value;
$consumed += 4 + $length;
}
} elseif (Message::TYPE_CAA === $type) {
if ($rdLength > 3) {
list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2)));
if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) {
$tag = substr($data, $consumed + 2, $tagLength);
$value = substr($data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength);
$consumed += $rdLength;
$rdata = array(
'flag' => $flag,
'tag' => $tag,
'value' => $value
);
}
}
} else {
// unknown types simply parse rdata as an opaque binary string
$rdata = substr($data, $consumed, $rdLength);
$consumed += $rdLength;
}
// ensure parsing record data consumes expact number of bytes indicated in record length
if ($consumed !== $expected || $rdata === null) {
return array(null, null);
}
return array(
new Record($name, $type, $class, $ttl, $rdata),
$consumed
);
}
private function readDomain($data, $consumed)
{
list ($labels, $consumed) = $this->readLabels($data, $consumed);
if ($labels === null) {
return array(null, null);
}
// use escaped notation for each label part, then join using dots
return array(
\implode(
'.',
\array_map(
function ($label) {
return \addcslashes($label, "\0..\40.\177");
},
$labels
)
),
$consumed
);
}
/**
* @param string $data
* @param int $consumed
* @param int $compressionDepth maximum depth for compressed labels to avoid unreasonable recursion
* @return array
*/
private function readLabels($data, $consumed, $compressionDepth = 127)
{
$labels = array();
while (true) {
if (!isset($data[$consumed])) {
return array(null, null);
}
$length = \ord($data[$consumed]);
// end of labels reached
if ($length === 0) {
$consumed += 1;
break;
}
// first two bits set? this is a compressed label (14 bit pointer offset)
if (($length & 0xc0) === 0xc0 && isset($data[$consumed + 1]) && $compressionDepth) {
$offset = ($length & ~0xc0) << 8 | \ord($data[$consumed + 1]);
if ($offset >= $consumed) {
return array(null, null);
}
$consumed += 2;
list($newLabels) = $this->readLabels($data, $offset, $compressionDepth - 1);
if ($newLabels === null) {
return array(null, null);
}
$labels = array_merge($labels, $newLabels);
break;
}
// length MUST be 0-63 (6 bits only) and data has to be large enough
if ($length & 0xc0 || !isset($data[$consumed + $length - 1])) {
return array(null, null);
}
$labels[] = substr($data, $consumed + 1, $length);
$consumed += $length + 1;
}
return array($labels, $consumed);
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace React\Dns\Query;
use React\Cache\CacheInterface;
use React\Dns\Model\Message;
use React\Promise\Promise;
final class CachingExecutor implements ExecutorInterface
{
/**
* Default TTL for negative responses (NXDOMAIN etc.).
*
* @internal
*/
const TTL = 60;
private $executor;
private $cache;
public function __construct(ExecutorInterface $executor, CacheInterface $cache)
{
$this->executor = $executor;
$this->cache = $cache;
}
public function query(Query $query)
{
$id = $query->name . ':' . $query->type . ':' . $query->class;
$cache = $this->cache;
$that = $this;
$executor = $this->executor;
$pending = $cache->get($id);
return new Promise(function ($resolve, $reject) use ($query, $id, $cache, $executor, &$pending, $that) {
$pending->then(
function ($message) use ($query, $id, $cache, $executor, &$pending, $that) {
// return cached response message on cache hit
if ($message !== null) {
return $message;
}
// perform DNS lookup if not already cached
return $pending = $executor->query($query)->then(
function (Message $message) use ($cache, $id, $that) {
// DNS response message received => store in cache when not truncated and return
if (!$message->tc) {
$cache->set($id, $message, $that->ttl($message));
}
return $message;
}
);
}
)->then($resolve, function ($e) use ($reject, &$pending) {
$reject($e);
$pending = null;
});
}, function ($_, $reject) use (&$pending, $query) {
$reject(new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled'));
$pending->cancel();
$pending = null;
});
}
/**
* @param Message $message
* @return int
* @internal
*/
public function ttl(Message $message)
{
// select TTL from answers (should all be the same), use smallest value if available
// @link https://tools.ietf.org/html/rfc2181#section-5.2
$ttl = null;
foreach ($message->answers as $answer) {
if ($ttl === null || $answer->ttl < $ttl) {
$ttl = $answer->ttl;
}
}
if ($ttl === null) {
$ttl = self::TTL;
}
return $ttl;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns\Query;
final class CancellationException extends \RuntimeException
{
}

91
vendor/react/dns/src/Query/CoopExecutor.php vendored Executable file
View File

@@ -0,0 +1,91 @@
<?php
namespace React\Dns\Query;
use React\Promise\Promise;
/**
* Cooperatively resolves hosts via the given base executor to ensure same query is not run concurrently
*
* Wraps an existing `ExecutorInterface` to keep tracking of pending queries
* and only starts a new query when the same query is not already pending. Once
* the underlying query is fulfilled/rejected, it will forward its value to all
* promises awaiting the same query.
*
* This means it will not limit concurrency for queries that differ, for example
* when sending many queries for different host names or types.
*
* This is useful because all executors are entirely async and as such allow you
* to execute any number of queries concurrently. You should probably limit the
* number of concurrent queries in your application or you're very likely going
* to face rate limitations and bans on the resolver end. For many common
* applications, you may want to avoid sending the same query multiple times
* when the first one is still pending, so you will likely want to use this in
* combination with some other executor like this:
*
* ```php
* $executor = new CoopExecutor(
* new RetryExecutor(
* new TimeoutExecutor(
* new UdpTransportExecutor($nameserver),
* 3.0
* )
* )
* );
* ```
*/
final class CoopExecutor implements ExecutorInterface
{
private $executor;
private $pending = array();
private $counts = array();
public function __construct(ExecutorInterface $base)
{
$this->executor = $base;
}
public function query(Query $query)
{
$key = $this->serializeQueryToIdentity($query);
if (isset($this->pending[$key])) {
// same query is already pending, so use shared reference to pending query
$promise = $this->pending[$key];
++$this->counts[$key];
} else {
// no such query pending, so start new query and keep reference until it's fulfilled or rejected
$promise = $this->executor->query($query);
$this->pending[$key] = $promise;
$this->counts[$key] = 1;
$pending =& $this->pending;
$counts =& $this->counts;
$promise->then(function () use ($key, &$pending, &$counts) {
unset($pending[$key], $counts[$key]);
}, function () use ($key, &$pending, &$counts) {
unset($pending[$key], $counts[$key]);
});
}
// Return a child promise awaiting the pending query.
// Cancelling this child promise should only cancel the pending query
// when no other child promise is awaiting the same query.
$pending =& $this->pending;
$counts =& $this->counts;
return new Promise(function ($resolve, $reject) use ($promise) {
$promise->then($resolve, $reject);
}, function () use (&$promise, $key, $query, &$pending, &$counts) {
if (--$counts[$key] < 1) {
unset($pending[$key], $counts[$key]);
$promise->cancel();
$promise = null;
}
throw new \RuntimeException('DNS query for ' . $query->describe() . ' has been cancelled');
});
}
private function serializeQueryToIdentity(Query $query)
{
return sprintf('%s:%s:%s', $query->name, $query->type, $query->class);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace React\Dns\Query;
interface ExecutorInterface
{
/**
* Executes a query and will return a response message
*
* It returns a Promise which either fulfills with a response
* `React\Dns\Model\Message` on success or rejects with an `Exception` if
* the query is not successful. A response message may indicate an error
* condition in its `rcode`, but this is considered a valid response message.
*
* ```php
* $executor->query($query)->then(
* function (React\Dns\Model\Message $response) {
* // response message successfully received
* var_dump($response->rcode, $response->answers);
* },
* function (Exception $error) {
* // failed to query due to $error
* }
* );
* ```
*
* The returned Promise MUST be implemented in such a way that it can be
* cancelled when it is still pending. Cancelling a pending promise MUST
* reject its value with an Exception. It SHOULD clean up any underlying
* resources and references as applicable.
*
* ```php
* $promise = $executor->query($query);
*
* $promise->cancel();
* ```
*
* @param Query $query
* @return \React\Promise\PromiseInterface<\React\Dns\Model\Message>
* resolves with response message on success or rejects with an Exception on error
*/
public function query(Query $query);
}

View File

@@ -0,0 +1,49 @@
<?php
namespace React\Dns\Query;
use React\Promise\Promise;
final class FallbackExecutor implements ExecutorInterface
{
private $executor;
private $fallback;
public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback)
{
$this->executor = $executor;
$this->fallback = $fallback;
}
public function query(Query $query)
{
$cancelled = false;
$fallback = $this->fallback;
$promise = $this->executor->query($query);
return new Promise(function ($resolve, $reject) use (&$promise, $fallback, $query, &$cancelled) {
$promise->then($resolve, function (\Exception $e1) use ($fallback, $query, $resolve, $reject, &$cancelled, &$promise) {
// reject if primary resolution rejected due to cancellation
if ($cancelled) {
$reject($e1);
return;
}
// start fallback query if primary query rejected
$promise = $fallback->query($query)->then($resolve, function (\Exception $e2) use ($e1, $reject) {
$append = $e2->getMessage();
if (($pos = strpos($append, ':')) !== false) {
$append = substr($append, $pos + 2);
}
// reject with combined error message if both queries fail
$reject(new \RuntimeException($e1->getMessage() . '. ' . $append));
});
});
}, function () use (&$promise, &$cancelled) {
// cancel pending query (primary or fallback)
$cancelled = true;
$promise->cancel();
});
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace React\Dns\Query;
use React\Dns\Config\HostsFile;
use React\Dns\Model\Message;
use React\Dns\Model\Record;
use React\Promise;
/**
* Resolves hosts from the given HostsFile or falls back to another executor
*
* If the host is found in the hosts file, it will not be passed to the actual
* DNS executor. If the host is not found in the hosts file, it will be passed
* to the DNS executor as a fallback.
*/
final class HostsFileExecutor implements ExecutorInterface
{
private $hosts;
private $fallback;
public function __construct(HostsFile $hosts, ExecutorInterface $fallback)
{
$this->hosts = $hosts;
$this->fallback = $fallback;
}
public function query(Query $query)
{
if ($query->class === Message::CLASS_IN && ($query->type === Message::TYPE_A || $query->type === Message::TYPE_AAAA)) {
// forward lookup for type A or AAAA
$records = array();
$expectsColon = $query->type === Message::TYPE_AAAA;
foreach ($this->hosts->getIpsForHost($query->name) as $ip) {
// ensure this is an IPv4/IPV6 address according to query type
if ((strpos($ip, ':') !== false) === $expectsColon) {
$records[] = new Record($query->name, $query->type, $query->class, 0, $ip);
}
}
if ($records) {
return Promise\resolve(
Message::createResponseWithAnswersForQuery($query, $records)
);
}
} elseif ($query->class === Message::CLASS_IN && $query->type === Message::TYPE_PTR) {
// reverse lookup: extract IPv4 or IPv6 from special `.arpa` domain
$ip = $this->getIpFromHost($query->name);
if ($ip !== null) {
$records = array();
foreach ($this->hosts->getHostsForIp($ip) as $host) {
$records[] = new Record($query->name, $query->type, $query->class, 0, $host);
}
if ($records) {
return Promise\resolve(
Message::createResponseWithAnswersForQuery($query, $records)
);
}
}
}
return $this->fallback->query($query);
}
private function getIpFromHost($host)
{
if (substr($host, -13) === '.in-addr.arpa') {
// IPv4: read as IP and reverse bytes
$ip = @inet_pton(substr($host, 0, -13));
if ($ip === false || isset($ip[4])) {
return null;
}
return inet_ntop(strrev($ip));
} elseif (substr($host, -9) === '.ip6.arpa') {
// IPv6: replace dots, reverse nibbles and interpret as hexadecimal string
$ip = @inet_ntop(pack('H*', strrev(str_replace('.', '', substr($host, 0, -9)))));
if ($ip === false) {
return null;
}
return $ip;
} else {
return null;
}
}
}

69
vendor/react/dns/src/Query/Query.php vendored Executable file
View File

@@ -0,0 +1,69 @@
<?php
namespace React\Dns\Query;
use React\Dns\Model\Message;
/**
* This class represents a single question in a query/response message
*
* It uses a structure similar to `\React\Dns\Message\Record`, but does not
* contain fields for resulting TTL and resulting record data (IPs etc.).
*
* @link https://tools.ietf.org/html/rfc1035#section-4.1.2
* @see \React\Dns\Message\Record
*/
final class Query
{
/**
* @var string query name, i.e. hostname to look up
*/
public $name;
/**
* @var int query type (aka QTYPE), see Message::TYPE_* constants
*/
public $type;
/**
* @var int query class (aka QCLASS), see Message::CLASS_IN constant
*/
public $class;
/**
* @param string $name query name, i.e. hostname to look up
* @param int $type query type, see Message::TYPE_* constants
* @param int $class query class, see Message::CLASS_IN constant
*/
public function __construct($name, $type, $class)
{
$this->name = $name;
$this->type = $type;
$this->class = $class;
}
/**
* Describes the hostname and query type/class for this query
*
* The output format is supposed to be human readable and is subject to change.
* The format is inspired by RFC 3597 when handling unkown types/classes.
*
* @return string "example.com (A)" or "example.com (CLASS0 TYPE1234)"
* @link https://tools.ietf.org/html/rfc3597
*/
public function describe()
{
$class = $this->class !== Message::CLASS_IN ? 'CLASS' . $this->class . ' ' : '';
$type = 'TYPE' . $this->type;
$ref = new \ReflectionClass('React\Dns\Model\Message');
foreach ($ref->getConstants() as $name => $value) {
if ($value === $this->type && \strpos($name, 'TYPE_') === 0) {
$type = \substr($name, 5);
break;
}
}
return $this->name . ' (' . $class . $type . ')';
}
}

85
vendor/react/dns/src/Query/RetryExecutor.php vendored Executable file
View File

@@ -0,0 +1,85 @@
<?php
namespace React\Dns\Query;
use React\Promise\Deferred;
use React\Promise\PromiseInterface;
final class RetryExecutor implements ExecutorInterface
{
private $executor;
private $retries;
public function __construct(ExecutorInterface $executor, $retries = 2)
{
$this->executor = $executor;
$this->retries = $retries;
}
public function query(Query $query)
{
return $this->tryQuery($query, $this->retries);
}
public function tryQuery(Query $query, $retries)
{
$deferred = new Deferred(function () use (&$promise) {
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
$promise->cancel();
}
});
$success = function ($value) use ($deferred, &$errorback) {
$errorback = null;
$deferred->resolve($value);
};
$executor = $this->executor;
$errorback = function ($e) use ($deferred, &$promise, $query, $success, &$errorback, &$retries, $executor) {
if (!$e instanceof TimeoutException) {
$errorback = null;
$deferred->reject($e);
} elseif ($retries <= 0) {
$errorback = null;
$deferred->reject($e = new \RuntimeException(
'DNS query for ' . $query->describe() . ' failed: too many retries',
0,
$e
));
// avoid garbage references by replacing all closures in call stack.
// what a lovely piece of code!
$r = new \ReflectionProperty('Exception', 'trace');
$r->setAccessible(true);
$trace = $r->getValue($e);
// Exception trace arguments are not available on some PHP 7.4 installs
// @codeCoverageIgnoreStart
foreach ($trace as $ti => $one) {
if (isset($one['args'])) {
foreach ($one['args'] as $ai => $arg) {
if ($arg instanceof \Closure) {
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
}
}
}
}
// @codeCoverageIgnoreEnd
$r->setValue($e, $trace);
} else {
--$retries;
$promise = $executor->query($query)->then(
$success,
$errorback
);
}
};
$promise = $this->executor->query($query)->then(
$success,
$errorback
);
return $deferred->promise();
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace React\Dns\Query;
use React\Promise\Promise;
/**
* Send DNS queries over a UDP or TCP/IP stream transport.
*
* This class will automatically choose the correct transport protocol to send
* a DNS query to your DNS server. It will always try to send it over the more
* efficient UDP transport first. If this query yields a size related issue
* (truncated messages), it will retry over a streaming TCP/IP transport.
*
* For more advanced usages one can utilize this class directly.
* The following example looks up the `IPv6` address for `reactphp.org`.
*
* ```php
* $executor = new SelectiveTransportExecutor($udpExecutor, $tcpExecutor);
*
* $executor->query(
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
* )->then(function (Message $message) {
* foreach ($message->answers as $answer) {
* echo 'IPv6: ' . $answer->data . PHP_EOL;
* }
* }, 'printf');
* ```
*
* Note that this executor only implements the logic to select the correct
* transport for the given DNS query. Implementing the correct transport logic,
* implementing timeouts and any retry logic is left up to the given executors,
* see also [`UdpTransportExecutor`](#udptransportexecutor) and
* [`TcpTransportExecutor`](#tcptransportexecutor) for more details.
*
* Note that this executor is entirely async and as such allows you to execute
* any number of queries concurrently. You should probably limit the number of
* concurrent queries in your application or you're very likely going to face
* rate limitations and bans on the resolver end. For many common applications,
* you may want to avoid sending the same query multiple times when the first
* one is still pending, so you will likely want to use this in combination with
* a `CoopExecutor` like this:
*
* ```php
* $executor = new CoopExecutor(
* new SelectiveTransportExecutor(
* $datagramExecutor,
* $streamExecutor
* )
* );
* ```
*/
class SelectiveTransportExecutor implements ExecutorInterface
{
private $datagramExecutor;
private $streamExecutor;
public function __construct(ExecutorInterface $datagramExecutor, ExecutorInterface $streamExecutor)
{
$this->datagramExecutor = $datagramExecutor;
$this->streamExecutor = $streamExecutor;
}
public function query(Query $query)
{
$stream = $this->streamExecutor;
$pending = $this->datagramExecutor->query($query);
return new Promise(function ($resolve, $reject) use (&$pending, $stream, $query) {
$pending->then(
$resolve,
function ($e) use (&$pending, $stream, $query, $resolve, $reject) {
if ($e->getCode() === (\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90)) {
$pending = $stream->query($query)->then($resolve, $reject);
} else {
$reject($e);
}
}
);
}, function () use (&$pending) {
$pending->cancel();
$pending = null;
});
}
}

View File

@@ -0,0 +1,382 @@
<?php
namespace React\Dns\Query;
use React\Dns\Model\Message;
use React\Dns\Protocol\BinaryDumper;
use React\Dns\Protocol\Parser;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
/**
* Send DNS queries over a TCP/IP stream transport.
*
* This is one of the main classes that send a DNS query to your DNS server.
*
* For more advanced usages one can utilize this class directly.
* The following example looks up the `IPv6` address for `reactphp.org`.
*
* ```php
* $executor = new TcpTransportExecutor('8.8.8.8:53');
*
* $executor->query(
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
* )->then(function (Message $message) {
* foreach ($message->answers as $answer) {
* echo 'IPv6: ' . $answer->data . PHP_EOL;
* }
* }, 'printf');
* ```
*
* See also [example #92](examples).
*
* Note that this executor does not implement a timeout, so you will very likely
* want to use this in combination with a `TimeoutExecutor` like this:
*
* ```php
* $executor = new TimeoutExecutor(
* new TcpTransportExecutor($nameserver),
* 3.0
* );
* ```
*
* Unlike the `UdpTransportExecutor`, this class uses a reliable TCP/IP
* transport, so you do not necessarily have to implement any retry logic.
*
* Note that this executor is entirely async and as such allows you to execute
* queries concurrently. The first query will establish a TCP/IP socket
* connection to the DNS server which will be kept open for a short period.
* Additional queries will automatically reuse this existing socket connection
* to the DNS server, will pipeline multiple requests over this single
* connection and will keep an idle connection open for a short period. The
* initial TCP/IP connection overhead may incur a slight delay if you only send
* occasional queries when sending a larger number of concurrent queries over
* an existing connection, it becomes increasingly more efficient and avoids
* creating many concurrent sockets like the UDP-based executor. You may still
* want to limit the number of (concurrent) queries in your application or you
* may be facing rate limitations and bans on the resolver end. For many common
* applications, you may want to avoid sending the same query multiple times
* when the first one is still pending, so you will likely want to use this in
* combination with a `CoopExecutor` like this:
*
* ```php
* $executor = new CoopExecutor(
* new TimeoutExecutor(
* new TcpTransportExecutor($nameserver),
* 3.0
* )
* );
* ```
*
* > Internally, this class uses PHP's TCP/IP sockets and does not take advantage
* of [react/socket](https://github.com/reactphp/socket) purely for
* organizational reasons to avoid a cyclic dependency between the two
* packages. Higher-level components should take advantage of the Socket
* component instead of reimplementing this socket logic from scratch.
*/
class TcpTransportExecutor implements ExecutorInterface
{
private $nameserver;
private $loop;
private $parser;
private $dumper;
/**
* @var ?resource
*/
private $socket;
/**
* @var Deferred[]
*/
private $pending = array();
/**
* @var string[]
*/
private $names = array();
/**
* Maximum idle time when socket is current unused (i.e. no pending queries outstanding)
*
* If a new query is to be sent during the idle period, we can reuse the
* existing socket without having to wait for a new socket connection.
* This uses a rather small, hard-coded value to not keep any unneeded
* sockets open and to not keep the loop busy longer than needed.
*
* A future implementation may take advantage of `edns-tcp-keepalive` to keep
* the socket open for longer periods. This will likely require explicit
* configuration because this may consume additional resources and also keep
* the loop busy for longer than expected in some applications.
*
* @var float
* @link https://tools.ietf.org/html/rfc7766#section-6.2.1
* @link https://tools.ietf.org/html/rfc7828
*/
private $idlePeriod = 0.001;
/**
* @var ?\React\EventLoop\TimerInterface
*/
private $idleTimer;
private $writeBuffer = '';
private $writePending = false;
private $readBuffer = '';
private $readPending = false;
/** @var string */
private $readChunk = 0xffff;
/**
* @param string $nameserver
* @param ?LoopInterface $loop
*/
public function __construct($nameserver, $loop = null)
{
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
$nameserver = '[' . $nameserver . ']';
}
$parts = \parse_url((\strpos($nameserver, '://') === false ? 'tcp://' : '') . $nameserver);
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'tcp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
throw new \InvalidArgumentException('Invalid nameserver address given');
}
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->nameserver = 'tcp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
$this->loop = $loop ?: Loop::get();
$this->parser = new Parser();
$this->dumper = new BinaryDumper();
}
public function query(Query $query)
{
$request = Message::createRequestForQuery($query);
// keep shuffing message ID to avoid using the same message ID for two pending queries at the same time
while (isset($this->pending[$request->id])) {
$request->id = \mt_rand(0, 0xffff); // @codeCoverageIgnore
}
$queryData = $this->dumper->toBinary($request);
$length = \strlen($queryData);
if ($length > 0xffff) {
return \React\Promise\reject(new \RuntimeException(
'DNS query for ' . $query->describe() . ' failed: Query too large for TCP transport'
));
}
$queryData = \pack('n', $length) . $queryData;
if ($this->socket === null) {
// create async TCP/IP connection (may take a while)
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT);
if ($socket === false) {
return \React\Promise\reject(new \RuntimeException(
'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
$errno
));
}
// set socket to non-blocking and wait for it to become writable (connection success/rejected)
\stream_set_blocking($socket, false);
if (\function_exists('stream_set_chunk_size')) {
\stream_set_chunk_size($socket, $this->readChunk); // @codeCoverageIgnore
}
$this->socket = $socket;
}
if ($this->idleTimer !== null) {
$this->loop->cancelTimer($this->idleTimer);
$this->idleTimer = null;
}
// wait for socket to become writable to actually write out data
$this->writeBuffer .= $queryData;
if (!$this->writePending) {
$this->writePending = true;
$this->loop->addWriteStream($this->socket, array($this, 'handleWritable'));
}
$names =& $this->names;
$that = $this;
$deferred = new Deferred(function () use ($that, &$names, $request) {
// remove from list of pending names, but remember pending query
$name = $names[$request->id];
unset($names[$request->id]);
$that->checkIdle();
throw new CancellationException('DNS query for ' . $name . ' has been cancelled');
});
$this->pending[$request->id] = $deferred;
$this->names[$request->id] = $query->describe();
return $deferred->promise();
}
/**
* @internal
*/
public function handleWritable()
{
if ($this->readPending === false) {
$name = @\stream_socket_get_name($this->socket, true);
if ($name === false) {
// Connection failed? Check socket error if available for underlying errno/errstr.
// @codeCoverageIgnoreStart
if (\function_exists('socket_import_stream')) {
$socket = \socket_import_stream($this->socket);
$errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
$errstr = \socket_strerror($errno);
} else {
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
$errstr = 'Connection refused';
}
// @codeCoverageIgnoreEnd
$this->closeError('Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')', $errno);
return;
}
$this->readPending = true;
$this->loop->addReadStream($this->socket, array($this, 'handleRead'));
}
$errno = 0;
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
// Match errstr from PHP's warning message.
// fwrite(): Send of 327712 bytes failed with errno=32 Broken pipe
\preg_match('/errno=(\d+) (.+)/', $error, $m);
$errno = isset($m[1]) ? (int) $m[1] : 0;
$errstr = isset($m[2]) ? $m[2] : $error;
});
$written = \fwrite($this->socket, $this->writeBuffer);
\restore_error_handler();
if ($written === false || $written === 0) {
$this->closeError(
'Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
$errno
);
return;
}
if (isset($this->writeBuffer[$written])) {
$this->writeBuffer = \substr($this->writeBuffer, $written);
} else {
$this->loop->removeWriteStream($this->socket);
$this->writePending = false;
$this->writeBuffer = '';
}
}
/**
* @internal
*/
public function handleRead()
{
// read one chunk of data from the DNS server
// any error is fatal, this is a stream of TCP/IP data
$chunk = @\fread($this->socket, $this->readChunk);
if ($chunk === false || $chunk === '') {
$this->closeError('Connection to DNS server ' . $this->nameserver . ' lost');
return;
}
// reassemble complete message by concatenating all chunks.
$this->readBuffer .= $chunk;
// response message header contains at least 12 bytes
while (isset($this->readBuffer[11])) {
// read response message length from first 2 bytes and ensure we have length + data in buffer
list(, $length) = \unpack('n', $this->readBuffer);
if (!isset($this->readBuffer[$length + 1])) {
return;
}
$data = \substr($this->readBuffer, 2, $length);
$this->readBuffer = (string)substr($this->readBuffer, $length + 2);
try {
$response = $this->parser->parseMessage($data);
} catch (\Exception $e) {
// reject all pending queries if we received an invalid message from remote server
$this->closeError('Invalid message received from DNS server ' . $this->nameserver);
return;
}
// reject all pending queries if we received an unexpected response ID or truncated response
if (!isset($this->pending[$response->id]) || $response->tc) {
$this->closeError('Invalid response message received from DNS server ' . $this->nameserver);
return;
}
$deferred = $this->pending[$response->id];
unset($this->pending[$response->id], $this->names[$response->id]);
$deferred->resolve($response);
$this->checkIdle();
}
}
/**
* @internal
* @param string $reason
* @param int $code
*/
public function closeError($reason, $code = 0)
{
$this->readBuffer = '';
if ($this->readPending) {
$this->loop->removeReadStream($this->socket);
$this->readPending = false;
}
$this->writeBuffer = '';
if ($this->writePending) {
$this->loop->removeWriteStream($this->socket);
$this->writePending = false;
}
if ($this->idleTimer !== null) {
$this->loop->cancelTimer($this->idleTimer);
$this->idleTimer = null;
}
@\fclose($this->socket);
$this->socket = null;
foreach ($this->names as $id => $name) {
$this->pending[$id]->reject(new \RuntimeException(
'DNS query for ' . $name . ' failed: ' . $reason,
$code
));
}
$this->pending = $this->names = array();
}
/**
* @internal
*/
public function checkIdle()
{
if ($this->idleTimer === null && !$this->names) {
$that = $this;
$this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () use ($that) {
$that->closeError('Idle timeout');
});
}
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns\Query;
final class TimeoutException extends \Exception
{
}

View File

@@ -0,0 +1,78 @@
<?php
namespace React\Dns\Query;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise\Promise;
final class TimeoutExecutor implements ExecutorInterface
{
private $executor;
private $loop;
private $timeout;
/**
* @param ExecutorInterface $executor
* @param float $timeout
* @param ?LoopInterface $loop
*/
public function __construct(ExecutorInterface $executor, $timeout, $loop = null)
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->executor = $executor;
$this->loop = $loop ?: Loop::get();
$this->timeout = $timeout;
}
public function query(Query $query)
{
$promise = $this->executor->query($query);
$loop = $this->loop;
$time = $this->timeout;
return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $query) {
$timer = null;
$promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
if ($timer) {
$loop->cancelTimer($timer);
}
$timer = false;
$resolve($v);
}, function ($v) use (&$timer, $loop, $reject) {
if ($timer) {
$loop->cancelTimer($timer);
}
$timer = false;
$reject($v);
});
// promise already resolved => no need to start timer
if ($timer === false) {
return;
}
// start timeout timer which will cancel the pending promise
$timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $query) {
$reject(new TimeoutException(
'DNS query for ' . $query->describe() . ' timed out'
));
// Cancel pending query to clean up any underlying resources and references.
// Avoid garbage references in call stack by passing pending promise by reference.
assert(\method_exists($promise, 'cancel'));
$promise->cancel();
$promise = null;
});
}, function () use (&$promise) {
// Cancelling this promise will cancel the pending query, thus triggering the rejection logic above.
// Avoid garbage references in call stack by passing pending promise by reference.
assert(\method_exists($promise, 'cancel'));
$promise->cancel();
$promise = null;
});
}
}

View File

@@ -0,0 +1,221 @@
<?php
namespace React\Dns\Query;
use React\Dns\Model\Message;
use React\Dns\Protocol\BinaryDumper;
use React\Dns\Protocol\Parser;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
/**
* Send DNS queries over a UDP transport.
*
* This is the main class that sends a DNS query to your DNS server and is used
* internally by the `Resolver` for the actual message transport.
*
* For more advanced usages one can utilize this class directly.
* The following example looks up the `IPv6` address for `igor.io`.
*
* ```php
* $executor = new UdpTransportExecutor('8.8.8.8:53');
*
* $executor->query(
* new Query($name, Message::TYPE_AAAA, Message::CLASS_IN)
* )->then(function (Message $message) {
* foreach ($message->answers as $answer) {
* echo 'IPv6: ' . $answer->data . PHP_EOL;
* }
* }, 'printf');
* ```
*
* See also the [fourth example](examples).
*
* Note that this executor does not implement a timeout, so you will very likely
* want to use this in combination with a `TimeoutExecutor` like this:
*
* ```php
* $executor = new TimeoutExecutor(
* new UdpTransportExecutor($nameserver),
* 3.0
* );
* ```
*
* Also note that this executor uses an unreliable UDP transport and that it
* does not implement any retry logic, so you will likely want to use this in
* combination with a `RetryExecutor` like this:
*
* ```php
* $executor = new RetryExecutor(
* new TimeoutExecutor(
* new UdpTransportExecutor($nameserver),
* 3.0
* )
* );
* ```
*
* Note that this executor is entirely async and as such allows you to execute
* any number of queries concurrently. You should probably limit the number of
* concurrent queries in your application or you're very likely going to face
* rate limitations and bans on the resolver end. For many common applications,
* you may want to avoid sending the same query multiple times when the first
* one is still pending, so you will likely want to use this in combination with
* a `CoopExecutor` like this:
*
* ```php
* $executor = new CoopExecutor(
* new RetryExecutor(
* new TimeoutExecutor(
* new UdpTransportExecutor($nameserver),
* 3.0
* )
* )
* );
* ```
*
* > Internally, this class uses PHP's UDP sockets and does not take advantage
* of [react/datagram](https://github.com/reactphp/datagram) purely for
* organizational reasons to avoid a cyclic dependency between the two
* packages. Higher-level components should take advantage of the Datagram
* component instead of reimplementing this socket logic from scratch.
*/
final class UdpTransportExecutor implements ExecutorInterface
{
private $nameserver;
private $loop;
private $parser;
private $dumper;
/**
* maximum UDP packet size to send and receive
*
* @var int
*/
private $maxPacketSize = 512;
/**
* @param string $nameserver
* @param ?LoopInterface $loop
*/
public function __construct($nameserver, $loop = null)
{
if (\strpos($nameserver, '[') === false && \substr_count($nameserver, ':') >= 2 && \strpos($nameserver, '://') === false) {
// several colons, but not enclosed in square brackets => enclose IPv6 address in square brackets
$nameserver = '[' . $nameserver . ']';
}
$parts = \parse_url((\strpos($nameserver, '://') === false ? 'udp://' : '') . $nameserver);
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'udp' || @\inet_pton(\trim($parts['host'], '[]')) === false) {
throw new \InvalidArgumentException('Invalid nameserver address given');
}
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->nameserver = 'udp://' . $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 53);
$this->loop = $loop ?: Loop::get();
$this->parser = new Parser();
$this->dumper = new BinaryDumper();
}
public function query(Query $query)
{
$request = Message::createRequestForQuery($query);
$queryData = $this->dumper->toBinary($request);
if (isset($queryData[$this->maxPacketSize])) {
return \React\Promise\reject(new \RuntimeException(
'DNS query for ' . $query->describe() . ' failed: Query too large for UDP transport',
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
));
}
// UDP connections are instant, so try connection without a loop or timeout
$errno = 0;
$errstr = '';
$socket = @\stream_socket_client($this->nameserver, $errno, $errstr, 0);
if ($socket === false) {
return \React\Promise\reject(new \RuntimeException(
'DNS query for ' . $query->describe() . ' failed: Unable to connect to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
$errno
));
}
// set socket to non-blocking and immediately try to send (fill write buffer)
\stream_set_blocking($socket, false);
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
// Write may potentially fail, but most common errors are already caught by connection check above.
// Among others, macOS is known to report here when trying to send to broadcast address.
// This can also be reproduced by writing data exceeding `stream_set_chunk_size()` to a server refusing UDP data.
// fwrite(): send of 8192 bytes failed with errno=111 Connection refused
\preg_match('/errno=(\d+) (.+)/', $error, $m);
$errno = isset($m[1]) ? (int) $m[1] : 0;
$errstr = isset($m[2]) ? $m[2] : $error;
});
$written = \fwrite($socket, $queryData);
\restore_error_handler();
if ($written !== \strlen($queryData)) {
return \React\Promise\reject(new \RuntimeException(
'DNS query for ' . $query->describe() . ' failed: Unable to send query to DNS server ' . $this->nameserver . ' (' . $errstr . ')',
$errno
));
}
$loop = $this->loop;
$deferred = new Deferred(function () use ($loop, $socket, $query) {
// cancellation should remove socket from loop and close socket
$loop->removeReadStream($socket);
\fclose($socket);
throw new CancellationException('DNS query for ' . $query->describe() . ' has been cancelled');
});
$max = $this->maxPacketSize;
$parser = $this->parser;
$nameserver = $this->nameserver;
$loop->addReadStream($socket, function ($socket) use ($loop, $deferred, $query, $parser, $request, $max, $nameserver) {
// try to read a single data packet from the DNS server
// ignoring any errors, this is uses UDP packets and not a stream of data
$data = @\fread($socket, $max);
if ($data === false) {
return;
}
try {
$response = $parser->parseMessage($data);
} catch (\Exception $e) {
// ignore and await next if we received an invalid message from remote server
// this may as well be a fake response from an attacker (possible DOS)
return;
}
// ignore and await next if we received an unexpected response ID
// this may as well be a fake response from an attacker (possible cache poisoning)
if ($response->id !== $request->id) {
return;
}
// we only react to the first valid message, so remove socket from loop and close
$loop->removeReadStream($socket);
\fclose($socket);
if ($response->tc) {
$deferred->reject(new \RuntimeException(
'DNS query for ' . $query->describe() . ' failed: The DNS server ' . $nameserver . ' returned a truncated result for a UDP query',
\defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 90
));
return;
}
$deferred->resolve($response);
});
return $deferred->promise();
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Dns;
final class RecordNotFoundException extends \Exception
{
}

226
vendor/react/dns/src/Resolver/Factory.php vendored Executable file
View File

@@ -0,0 +1,226 @@
<?php
namespace React\Dns\Resolver;
use React\Cache\ArrayCache;
use React\Cache\CacheInterface;
use React\Dns\Config\Config;
use React\Dns\Config\HostsFile;
use React\Dns\Query\CachingExecutor;
use React\Dns\Query\CoopExecutor;
use React\Dns\Query\ExecutorInterface;
use React\Dns\Query\FallbackExecutor;
use React\Dns\Query\HostsFileExecutor;
use React\Dns\Query\RetryExecutor;
use React\Dns\Query\SelectiveTransportExecutor;
use React\Dns\Query\TcpTransportExecutor;
use React\Dns\Query\TimeoutExecutor;
use React\Dns\Query\UdpTransportExecutor;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
final class Factory
{
/**
* Creates a DNS resolver instance for the given DNS config
*
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
* single nameserver address. If the given config contains more than one DNS
* nameserver, all DNS nameservers will be used in order. The primary DNS
* server will always be used first before falling back to the secondary or
* tertiary DNS server.
*
* @param Config|string $config DNS Config object (recommended) or single nameserver address
* @param ?LoopInterface $loop
* @return \React\Dns\Resolver\ResolverInterface
* @throws \InvalidArgumentException for invalid DNS server address
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
*/
public function create($config, $loop = null)
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
$executor = $this->decorateHostsFileExecutor($this->createExecutor($config, $loop ?: Loop::get()));
return new Resolver($executor);
}
/**
* Creates a cached DNS resolver instance for the given DNS config and cache
*
* As of v1.7.0 it's recommended to pass a `Config` object instead of a
* single nameserver address. If the given config contains more than one DNS
* nameserver, all DNS nameservers will be used in order. The primary DNS
* server will always be used first before falling back to the secondary or
* tertiary DNS server.
*
* @param Config|string $config DNS Config object (recommended) or single nameserver address
* @param ?LoopInterface $loop
* @param ?CacheInterface $cache
* @return \React\Dns\Resolver\ResolverInterface
* @throws \InvalidArgumentException for invalid DNS server address
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
*/
public function createCached($config, $loop = null, $cache = null)
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
if ($cache !== null && !$cache instanceof CacheInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #3 ($cache) expected null|React\Cache\CacheInterface');
}
// default to keeping maximum of 256 responses in cache unless explicitly given
if (!($cache instanceof CacheInterface)) {
$cache = new ArrayCache(256);
}
$executor = $this->createExecutor($config, $loop ?: Loop::get());
$executor = new CachingExecutor($executor, $cache);
$executor = $this->decorateHostsFileExecutor($executor);
return new Resolver($executor);
}
/**
* Tries to load the hosts file and decorates the given executor on success
*
* @param ExecutorInterface $executor
* @return ExecutorInterface
* @codeCoverageIgnore
*/
private function decorateHostsFileExecutor(ExecutorInterface $executor)
{
try {
$executor = new HostsFileExecutor(
HostsFile::loadFromPathBlocking(),
$executor
);
} catch (\RuntimeException $e) {
// ignore this file if it can not be loaded
}
// Windows does not store localhost in hosts file by default but handles this internally
// To compensate for this, we explicitly use hard-coded defaults for localhost
if (DIRECTORY_SEPARATOR === '\\') {
$executor = new HostsFileExecutor(
new HostsFile("127.0.0.1 localhost\n::1 localhost"),
$executor
);
}
return $executor;
}
/**
* @param Config|string $nameserver
* @param LoopInterface $loop
* @return CoopExecutor
* @throws \InvalidArgumentException for invalid DNS server address
* @throws \UnderflowException when given DNS Config object has an empty list of nameservers
*/
private function createExecutor($nameserver, LoopInterface $loop)
{
if ($nameserver instanceof Config) {
if (!$nameserver->nameservers) {
throw new \UnderflowException('Empty config with no DNS servers');
}
// Hard-coded to check up to 3 DNS servers to match default limits in place in most systems (see MAXNS config).
// Note to future self: Recursion isn't too hard, but how deep do we really want to go?
$primary = reset($nameserver->nameservers);
$secondary = next($nameserver->nameservers);
$tertiary = next($nameserver->nameservers);
if ($tertiary !== false) {
// 3 DNS servers given => nest first with fallback for second and third
return new CoopExecutor(
new RetryExecutor(
new FallbackExecutor(
$this->createSingleExecutor($primary, $loop),
new FallbackExecutor(
$this->createSingleExecutor($secondary, $loop),
$this->createSingleExecutor($tertiary, $loop)
)
)
)
);
} elseif ($secondary !== false) {
// 2 DNS servers given => fallback from first to second
return new CoopExecutor(
new RetryExecutor(
new FallbackExecutor(
$this->createSingleExecutor($primary, $loop),
$this->createSingleExecutor($secondary, $loop)
)
)
);
} else {
// 1 DNS server given => use single executor
$nameserver = $primary;
}
}
return new CoopExecutor(new RetryExecutor($this->createSingleExecutor($nameserver, $loop)));
}
/**
* @param string $nameserver
* @param LoopInterface $loop
* @return ExecutorInterface
* @throws \InvalidArgumentException for invalid DNS server address
*/
private function createSingleExecutor($nameserver, LoopInterface $loop)
{
$parts = \parse_url($nameserver);
if (isset($parts['scheme']) && $parts['scheme'] === 'tcp') {
$executor = $this->createTcpExecutor($nameserver, $loop);
} elseif (isset($parts['scheme']) && $parts['scheme'] === 'udp') {
$executor = $this->createUdpExecutor($nameserver, $loop);
} else {
$executor = new SelectiveTransportExecutor(
$this->createUdpExecutor($nameserver, $loop),
$this->createTcpExecutor($nameserver, $loop)
);
}
return $executor;
}
/**
* @param string $nameserver
* @param LoopInterface $loop
* @return TimeoutExecutor
* @throws \InvalidArgumentException for invalid DNS server address
*/
private function createTcpExecutor($nameserver, LoopInterface $loop)
{
return new TimeoutExecutor(
new TcpTransportExecutor($nameserver, $loop),
5.0,
$loop
);
}
/**
* @param string $nameserver
* @param LoopInterface $loop
* @return TimeoutExecutor
* @throws \InvalidArgumentException for invalid DNS server address
*/
private function createUdpExecutor($nameserver, LoopInterface $loop)
{
return new TimeoutExecutor(
new UdpTransportExecutor(
$nameserver,
$loop
),
5.0,
$loop
);
}
}

147
vendor/react/dns/src/Resolver/Resolver.php vendored Executable file
View File

@@ -0,0 +1,147 @@
<?php
namespace React\Dns\Resolver;
use React\Dns\Model\Message;
use React\Dns\Query\ExecutorInterface;
use React\Dns\Query\Query;
use React\Dns\RecordNotFoundException;
/**
* @see ResolverInterface for the base interface
*/
final class Resolver implements ResolverInterface
{
private $executor;
public function __construct(ExecutorInterface $executor)
{
$this->executor = $executor;
}
public function resolve($domain)
{
return $this->resolveAll($domain, Message::TYPE_A)->then(function (array $ips) {
return $ips[array_rand($ips)];
});
}
public function resolveAll($domain, $type)
{
$query = new Query($domain, $type, Message::CLASS_IN);
$that = $this;
return $this->executor->query(
$query
)->then(function (Message $response) use ($query, $that) {
return $that->extractValues($query, $response);
});
}
/**
* [Internal] extract all resource record values from response for this query
*
* @param Query $query
* @param Message $response
* @return array
* @throws RecordNotFoundException when response indicates an error or contains no data
* @internal
*/
public function extractValues(Query $query, Message $response)
{
// reject if response code indicates this is an error response message
$code = $response->rcode;
if ($code !== Message::RCODE_OK) {
switch ($code) {
case Message::RCODE_FORMAT_ERROR:
$message = 'Format Error';
break;
case Message::RCODE_SERVER_FAILURE:
$message = 'Server Failure';
break;
case Message::RCODE_NAME_ERROR:
$message = 'Non-Existent Domain / NXDOMAIN';
break;
case Message::RCODE_NOT_IMPLEMENTED:
$message = 'Not Implemented';
break;
case Message::RCODE_REFUSED:
$message = 'Refused';
break;
default:
$message = 'Unknown error response code ' . $code;
}
throw new RecordNotFoundException(
'DNS query for ' . $query->describe() . ' returned an error response (' . $message . ')',
$code
);
}
$answers = $response->answers;
$addresses = $this->valuesByNameAndType($answers, $query->name, $query->type);
// reject if we did not receive a valid answer (domain is valid, but no record for this type could be found)
if (0 === count($addresses)) {
throw new RecordNotFoundException(
'DNS query for ' . $query->describe() . ' did not return a valid answer (NOERROR / NODATA)'
);
}
return array_values($addresses);
}
/**
* @param \React\Dns\Model\Record[] $answers
* @param string $name
* @param int $type
* @return array
*/
private function valuesByNameAndType(array $answers, $name, $type)
{
// return all record values for this name and type (if any)
$named = $this->filterByName($answers, $name);
$records = $this->filterByType($named, $type);
if ($records) {
return $this->mapRecordData($records);
}
// no matching records found? check if there are any matching CNAMEs instead
$cnameRecords = $this->filterByType($named, Message::TYPE_CNAME);
if ($cnameRecords) {
$cnames = $this->mapRecordData($cnameRecords);
foreach ($cnames as $cname) {
$records = array_merge(
$records,
$this->valuesByNameAndType($answers, $cname, $type)
);
}
}
return $records;
}
private function filterByName(array $answers, $name)
{
return $this->filterByField($answers, 'name', $name);
}
private function filterByType(array $answers, $type)
{
return $this->filterByField($answers, 'type', $type);
}
private function filterByField(array $answers, $field, $value)
{
$value = strtolower($value);
return array_filter($answers, function ($answer) use ($field, $value) {
return $value === strtolower($answer->$field);
});
}
private function mapRecordData(array $records)
{
return array_map(function ($record) {
return $record->data;
}, $records);
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace React\Dns\Resolver;
interface ResolverInterface
{
/**
* Resolves the given $domain name to a single IPv4 address (type `A` query).
*
* ```php
* $resolver->resolve('reactphp.org')->then(function ($ip) {
* echo 'IP for reactphp.org is ' . $ip . PHP_EOL;
* });
* ```
*
* This is one of the main methods in this package. It sends a DNS query
* for the given $domain name to your DNS server and returns a single IP
* address on success.
*
* If the DNS server sends a DNS response message that contains more than
* one IP address for this query, it will randomly pick one of the IP
* addresses from the response. If you want the full list of IP addresses
* or want to send a different type of query, you should use the
* [`resolveAll()`](#resolveall) method instead.
*
* If the DNS server sends a DNS response message that indicates an error
* code, this method will reject with a `RecordNotFoundException`. Its
* message and code can be used to check for the response code.
*
* If the DNS communication fails and the server does not respond with a
* valid response message, this message will reject with an `Exception`.
*
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
*
* ```php
* $promise = $resolver->resolve('reactphp.org');
*
* $promise->cancel();
* ```
*
* @param string $domain
* @return \React\Promise\PromiseInterface<string>
* resolves with a single IP address on success or rejects with an Exception on error.
*/
public function resolve($domain);
/**
* Resolves all record values for the given $domain name and query $type.
*
* ```php
* $resolver->resolveAll('reactphp.org', Message::TYPE_A)->then(function ($ips) {
* echo 'IPv4 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
* });
*
* $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA)->then(function ($ips) {
* echo 'IPv6 addresses for reactphp.org ' . implode(', ', $ips) . PHP_EOL;
* });
* ```
*
* This is one of the main methods in this package. It sends a DNS query
* for the given $domain name to your DNS server and returns a list with all
* record values on success.
*
* If the DNS server sends a DNS response message that contains one or more
* records for this query, it will return a list with all record values
* from the response. You can use the `Message::TYPE_*` constants to control
* which type of query will be sent. Note that this method always returns a
* list of record values, but each record value type depends on the query
* type. For example, it returns the IPv4 addresses for type `A` queries,
* the IPv6 addresses for type `AAAA` queries, the hostname for type `NS`,
* `CNAME` and `PTR` queries and structured data for other queries. See also
* the `Record` documentation for more details.
*
* If the DNS server sends a DNS response message that indicates an error
* code, this method will reject with a `RecordNotFoundException`. Its
* message and code can be used to check for the response code.
*
* If the DNS communication fails and the server does not respond with a
* valid response message, this message will reject with an `Exception`.
*
* Pending DNS queries can be cancelled by cancelling its pending promise like so:
*
* ```php
* $promise = $resolver->resolveAll('reactphp.org', Message::TYPE_AAAA);
*
* $promise->cancel();
* ```
*
* @param string $domain
* @return \React\Promise\PromiseInterface<array>
* Resolves with all record values on success or rejects with an Exception on error.
*/
public function resolveAll($domain, $type);
}

468
vendor/react/event-loop/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,468 @@
# Changelog
## 1.5.0 (2023-11-13)
* Feature: Improve performance by using `spl_object_id()` on PHP 7.2+.
(#267 by @samsonasik)
* Feature: Full PHP 8.3 compatibility.
(#269 by @clue)
* Update tests for `ext-uv` on PHP 8+ and legacy PHP.
(#270 by @clue and #268 by @SimonFrings)
## 1.4.0 (2023-05-05)
* Feature: Improve performance of `Loop` by avoiding unneeded method calls.
(#266 by @clue)
* Feature: Support checking `EINTR` constant from `ext-pcntl` without `ext-sockets`.
(#265 by @clue)
* Minor documentation improvements.
(#254 by @nhedger)
* Improve test suite, run tests on PHP 8.2 and report failed assertions.
(#258 by @WyriHaximus, #264 by @clue and #251, #261 and #262 by @SimonFrings)
## 1.3.0 (2022-03-17)
* Feature: Improve default `StreamSelectLoop` to report any warnings for invalid streams.
(#245 by @clue)
* Feature: Improve performance of `StreamSelectLoop` when no timers are scheduled.
(#246 by @clue)
* Fix: Fix periodic timer with zero interval for `ExtEvLoop` and legacy `ExtLibevLoop`.
(#243 by @lucasnetau)
* Minor documentation improvements, update PHP version references.
(#240, #248 and #250 by @SimonFrings, #241 by @dbu and #249 by @clue)
* Improve test suite and test against PHP 8.1.
(#238 by @WyriHaximus and #242 by @clue)
## 1.2.0 (2021-07-11)
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
* Feature: Introduce new concept of default loop with the new `Loop` class.
(#226 by @WyriHaximus, #229, #231 and #232 by @clue)
The `Loop` class exists as a convenient global accessor for the event loop.
It provides all methods that exist on the `LoopInterface` as static methods and
will automatically execute the loop at the end of the program:
```php
$timer = Loop::addPeriodicTimer(0.1, function () {
echo 'Tick' . PHP_EOL;
});
Loop::addTimer(1.0, function () use ($timer) {
Loop::cancelTimer($timer);
echo 'Done' . PHP_EOL;
});
```
The explicit loop instructions are still valid and may still be useful in some applications,
especially for a transition period towards the more concise style.
The `Loop::get()` method can be used to get the currently active event loop instance.
```php
// deprecated
$loop = React\EventLoop\Factory::create();
// new
$loop = React\EventLoop\Loop::get();
```
* Minor documentation improvements and mark legacy extensions as deprecated.
(#234 by @SimonFrings, #214 by @WyriHaximus and #233 and #235 by @nhedger)
* Improve test suite, use GitHub actions for continuous integration (CI),
update PHPUnit config and run tests on PHP 8.
(#212 and #215 by @SimonFrings and #230 by @clue)
## 1.1.1 (2020-01-01)
* Fix: Fix reporting connection refused errors with `ExtUvLoop` on Linux and `StreamSelectLoop` on Windows.
(#207 and #208 by @clue)
* Fix: Fix unsupported EventConfig and `SEGFAULT` on shutdown with `ExtEventLoop` on Windows.
(#205 by @clue)
* Fix: Prevent interval overflow for timers very far in the future with `ExtUvLoop`.
(#196 by @PabloKowalczyk)
* Fix: Check PCNTL functions for signal support instead of PCNTL extension with `StreamSelectLoop`.
(#195 by @clue)
* Add `.gitattributes` to exclude dev files from exports.
(#201 by @reedy)
* Improve test suite to fix testing `ExtUvLoop` on Travis,
fix Travis CI builds, do not install `libuv` on legacy PHP setups,
fix failing test cases due to inaccurate timers,
run tests on Windows via Travis CI and
run tests on PHP 7.4 and simplify test matrix and test setup.
(#197 by @WyriHaximus and #202, #203, #204 and #209 by @clue)
## 1.1.0 (2019-02-07)
* New UV based event loop (ext-uv).
(#112 by @WyriHaximus)
* Use high resolution timer on PHP 7.3+.
(#182 by @clue)
* Improve PCNTL signals by using async signal dispatching if available.
(#179 by @CharlotteDunois)
* Improve test suite and test suite set up.
(#174 by @WyriHaximus, #181 by @clue)
* Fix PCNTL signals edge case.
(#183 by @clue)
## 1.0.0 (2018-07-11)
* First stable LTS release, now following [SemVer](https://semver.org/).
We'd like to emphasize that this component is production ready and battle-tested.
We plan to support all long-term support (LTS) releases for at least 24 months,
so you have a rock-solid foundation to build on top of.
> Contains no other changes, so it's actually fully compatible with the v0.5.3 release.
## 0.5.3 (2018-07-09)
* Improve performance by importing global functions.
(#167 by @Ocramius)
* Improve test suite by simplifying test bootstrap by using dev autoloader.
(#169 by @lcobucci)
* Minor internal changes to improved backward compatibility with PHP 5.3.
(#166 by @Donatello-za)
## 0.5.2 (2018-04-24)
* Feature: Improve memory consumption and runtime performance for `StreamSelectLoop` timers.
(#164 by @clue)
* Improve test suite by removing I/O dependency at `StreamSelectLoopTest` to fix Mac OS X tests.
(#161 by @nawarian)
## 0.5.1 (2018-04-09)
* Feature: New `ExtEvLoop` (PECL ext-ev) (#148 by @kaduev13)
## 0.5.0 (2018-04-05)
A major feature release with a significant documentation overhaul and long overdue API cleanup!
This update involves a number of BC breaks due to dropped support for deprecated
functionality. We've tried hard to avoid BC breaks where possible and minimize
impact otherwise. We expect that most consumers of this package will actually
not be affected by any BC breaks, see below for more details.
We realize that the changes listed below may seem overwhelming, but we've tried
to be very clear about any possible BC breaks. Don't worry: In fact, all ReactPHP
components are already compatible and support both this new release as well as
providing backwards compatibility with the last release.
* Feature / BC break: Add support for signal handling via new
`LoopInterface::addSignal()` and `LoopInterface::removeSignal()` methods.
(#104 by @WyriHaximus and #111 and #150 by @clue)
```php
$loop->addSignal(SIGINT, function () {
echo 'CTRL-C';
});
```
* Feature: Significant documentation updates for `LoopInterface` and `Factory`.
(#100, #119, #126, #127, #159 and #160 by @clue, #113 by @WyriHaximus and #81 and #91 by @jsor)
* Feature: Add examples to ease getting started
(#99, #100 and #125 by @clue, #59 by @WyriHaximus and #143 by @jsor)
* Feature: Documentation for advanced timer concepts, such as monotonic time source vs wall-clock time
and high precision timers with millisecond accuracy or below.
(#130 and #157 by @clue)
* Feature: Documentation for advanced stream concepts, such as edge-triggered event listeners
and stream buffers and allow throwing Exception if stream resource is not supported.
(#129 and #158 by @clue)
* Feature: Throw `BadMethodCallException` on manual loop creation when required extension isn't installed.
(#153 by @WyriHaximus)
* Feature / BC break: First class support for legacy PHP 5.3 through PHP 7.2 and HHVM
and remove all `callable` type hints for consistency reasons.
(#141 and #151 by @clue)
* BC break: Documentation for timer API and clean up unneeded timer API.
(#102 by @clue)
Remove `TimerInterface::cancel()`, use `LoopInterface::cancelTimer()` instead:
```php
// old (method invoked on timer instance)
$timer->cancel();
// already supported before: invoke method on loop instance
$loop->cancelTimer($timer);
```
Remove unneeded `TimerInterface::setData()` and `TimerInterface::getData()`,
use closure binding to add arbitrary data to timer instead:
```php
// old (limited setData() and getData() only allows single variable)
$name = 'Tester';
$timer = $loop->addTimer(1.0, function ($timer) {
echo 'Hello ' . $timer->getData() . PHP_EOL;
});
$timer->setData($name);
// already supported before: closure binding allows any number of variables
$name = 'Tester';
$loop->addTimer(1.0, function () use ($name) {
echo 'Hello ' . $name . PHP_EOL;
});
```
Remove unneeded `TimerInterface::getLoop()`, use closure binding instead:
```php
// old (getLoop() called on timer instance)
$loop->addTimer(0.1, function ($timer) {
$timer->getLoop()->stop();
});
// already supported before: use closure binding as usual
$loop->addTimer(0.1, function () use ($loop) {
$loop->stop();
});
```
* BC break: Remove unneeded `LoopInterface::isTimerActive()` and
`TimerInterface::isActive()` to reduce API surface.
(#133 by @clue)
```php
// old (method on timer instance or on loop instance)
$timer->isActive();
$loop->isTimerActive($timer);
```
* BC break: Move `TimerInterface` one level up to `React\EventLoop\TimerInterface`.
(#138 by @WyriHaximus)
```php
// old (notice obsolete "Timer" namespace)
assert($timer instanceof React\EventLoop\Timer\TimerInterface);
// new
assert($timer instanceof React\EventLoop\TimerInterface);
```
* BC break: Remove unneeded `LoopInterface::nextTick()` (and internal `NextTickQueue`),
use `LoopInterface::futureTick()` instead.
(#30 by @clue)
```php
// old (removed)
$loop->nextTick(function () {
echo 'tick';
});
// already supported before
$loop->futureTick(function () {
echo 'tick';
});
```
* BC break: Remove unneeded `$loop` argument for `LoopInterface::futureTick()`
(and fix internal cyclic dependency).
(#103 by @clue)
```php
// old ($loop gets passed by default)
$loop->futureTick(function ($loop) {
$loop->stop();
});
// already supported before: use closure binding as usual
$loop->futureTick(function () use ($loop) {
$loop->stop();
});
```
* BC break: Remove unneeded `LoopInterface::tick()`.
(#72 by @jsor)
```php
// old (removed)
$loop->tick();
// suggested work around for testing purposes only
$loop->futureTick(function () use ($loop) {
$loop->stop();
});
```
* BC break: Documentation for advanced stream API and clean up unneeded stream API.
(#110 by @clue)
Remove unneeded `$loop` argument for `LoopInterface::addReadStream()`
and `LoopInterface::addWriteStream()`, use closure binding instead:
```php
// old ($loop gets passed by default)
$loop->addReadStream($stream, function ($stream, $loop) {
$loop->removeReadStream($stream);
});
// already supported before: use closure binding as usual
$loop->addReadStream($stream, function ($stream) use ($loop) {
$loop->removeReadStream($stream);
});
```
* BC break: Remove unneeded `LoopInterface::removeStream()` method,
use `LoopInterface::removeReadStream()` and `LoopInterface::removeWriteStream()` instead.
(#118 by @clue)
```php
// old
$loop->removeStream($stream);
// already supported before
$loop->removeReadStream($stream);
$loop->removeWriteStream($stream);
```
* BC break: Rename `LibEventLoop` to `ExtLibeventLoop` and `LibEvLoop` to `ExtLibevLoop`
for consistent naming for event loop implementations.
(#128 by @clue)
* BC break: Remove optional `EventBaseConfig` argument from `ExtEventLoop`
and make its `FEATURE_FDS` enabled by default.
(#156 by @WyriHaximus)
* BC break: Mark all classes as final to discourage inheritance.
(#131 by @clue)
* Fix: Fix `ExtEventLoop` to keep track of stream resources (refcount)
(#123 by @clue)
* Fix: Ensure large timer interval does not overflow on 32bit systems
(#132 by @clue)
* Fix: Fix separately removing readable and writable side of stream when closing
(#139 by @clue)
* Fix: Properly clean up event watchers for `ext-event` and `ext-libev`
(#149 by @clue)
* Fix: Minor code cleanup and remove unneeded references
(#145 by @seregazhuk)
* Fix: Discourage outdated `ext-libevent` on PHP 7
(#62 by @cboden)
* Improve test suite by adding forward compatibility with PHPUnit 6 and PHPUnit 5,
lock Travis distro so new defaults will not break the build,
improve test suite to be less fragile and increase test timeouts,
test against PHP 7.2 and reduce fwrite() call length to one chunk.
(#106 and #144 by @clue, #120 and #124 by @carusogabriel, #147 by nawarian and #92 by @kelunik)
* A number of changes were originally planned for this release but have been backported
to the last `v0.4.3` already: #74, #76, #79, #81 (refs #65, #66, #67), #88 and #93
## 0.4.3 (2017-04-27)
* Bug fix: Bugfix in the usage sample code #57 (@dandelionred)
* Improvement: Remove branch-alias definition #53 (@WyriHaximus)
* Improvement: StreamSelectLoop: Use fresh time so Timers added during stream events are accurate #51 (@andrewminerd)
* Improvement: Avoid deprecation warnings in test suite due to deprecation of getMock() in PHPUnit #68 (@martinschroeder)
* Improvement: Add PHPUnit 4.8 to require-dev #69 (@shaunbramley)
* Improvement: Increase test timeouts for HHVM and unify timeout handling #70 (@clue)
* Improvement: Travis improvements (backported from #74) #75 (@clue)
* Improvement: Test suite now uses socket pairs instead of memory streams #66 (@martinschroeder)
* Improvement: StreamSelectLoop: Test suite uses signal constant names in data provider #67 (@martinschroeder)
* Improvement: ExtEventLoop: No longer suppress all errors #65 (@mamciek)
* Improvement: Readme cleanup #89 (@jsor)
* Improvement: Restructure and improve README #90 (@jsor)
* Bug fix: StreamSelectLoop: Fix erroneous zero-time sleep (backport to 0.4) #94 (@jsor)
## 0.4.2 (2016-03-07)
* Bug fix: No longer error when signals sent to StreamSelectLoop
* Support HHVM and PHP7 (@ondrejmirtes, @cebe)
* Feature: Added support for EventConfig for ExtEventLoop (@steverhoades)
* Bug fix: Fixed an issue loading loop extension libs via autoloader (@czarpino)
## 0.4.1 (2014-04-13)
* Bug fix: null timeout in StreamSelectLoop causing 100% CPU usage (@clue)
* Bug fix: v0.3.4 changes merged for v0.4.1
## 0.4.0 (2014-02-02)
* Feature: Added `EventLoopInterface::nextTick()`, implemented in all event loops (@jmalloc)
* Feature: Added `EventLoopInterface::futureTick()`, implemented in all event loops (@jmalloc)
* Feature: Added `ExtEventLoop` implementation using pecl/event (@jmalloc)
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
* BC break: New method: `EventLoopInterface::nextTick()`
* BC break: New method: `EventLoopInterface::futureTick()`
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
## 0.3.5 (2016-12-28)
This is a compatibility release that eases upgrading to the v0.4 release branch.
You should consider upgrading to the v0.4 release branch.
* Feature: Cap min timer interval at 1µs, thus improving compatibility with v0.4
(#47 by @clue)
## 0.3.4 (2014-03-30)
* Bug fix: Changed StreamSelectLoop to use non-blocking behavior on tick() (@astephens25)
## 0.3.3 (2013-07-08)
* Bug fix: No error on removing non-existent streams (@clue)
* Bug fix: Do not silently remove feof listeners in `LibEvLoop`
## 0.3.0 (2013-04-14)
* BC break: New timers API (@nrk)
* BC break: Remove check on return value from stream callbacks (@nrk)
## 0.2.7 (2013-01-05)
* Bug fix: Fix libevent timers with PHP 5.3
* Bug fix: Fix libevent timer cancellation (@nrk)
## 0.2.6 (2012-12-26)
* Bug fix: Plug memory issue in libevent timers (@cameronjacobson)
* Bug fix: Correctly pause LibEvLoop on stop()
## 0.2.3 (2012-11-14)
* Feature: LibEvLoop, integration of `php-libev`
## 0.2.0 (2012-09-10)
* Version bump
## 0.1.1 (2012-07-12)
* Version bump
## 0.1.0 (2012-07-11)
* First tagged release

21
vendor/react/event-loop/LICENSE vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

930
vendor/react/event-loop/README.md vendored Executable file
View File

@@ -0,0 +1,930 @@
# EventLoop
[![CI status](https://github.com/reactphp/event-loop/actions/workflows/ci.yml/badge.svg)](https://github.com/reactphp/event-loop/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/event-loop?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/event-loop)
[ReactPHP](https://reactphp.org/)'s core reactor event loop that libraries can use for evented I/O.
In order for async based libraries to be interoperable, they need to use the
same event loop. This component provides a common `LoopInterface` that any
library can target. This allows them to be used in the same loop, with one
single [`run()`](#run) call that is controlled by the user.
**Table of contents**
* [Quickstart example](#quickstart-example)
* [Usage](#usage)
* [Loop](#loop)
* [Loop methods](#loop-methods)
* [Loop autorun](#loop-autorun)
* [get()](#get)
* [~~Factory~~](#factory)
* [~~create()~~](#create)
* [Loop implementations](#loop-implementations)
* [StreamSelectLoop](#streamselectloop)
* [ExtEventLoop](#exteventloop)
* [ExtEvLoop](#extevloop)
* [ExtUvLoop](#extuvloop)
* [~~ExtLibeventLoop~~](#extlibeventloop)
* [~~ExtLibevLoop~~](#extlibevloop)
* [LoopInterface](#loopinterface)
* [run()](#run)
* [stop()](#stop)
* [addTimer()](#addtimer)
* [addPeriodicTimer()](#addperiodictimer)
* [cancelTimer()](#canceltimer)
* [futureTick()](#futuretick)
* [addSignal()](#addsignal)
* [removeSignal()](#removesignal)
* [addReadStream()](#addreadstream)
* [addWriteStream()](#addwritestream)
* [removeReadStream()](#removereadstream)
* [removeWriteStream()](#removewritestream)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
* [More](#more)
## Quickstart example
Here is an async HTTP server built with just the event loop.
```php
<?php
use React\EventLoop\Loop;
require __DIR__ . '/vendor/autoload.php';
$server = stream_socket_server('tcp://127.0.0.1:8080');
stream_set_blocking($server, false);
Loop::addReadStream($server, function ($server) {
$conn = stream_socket_accept($server);
$data = "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nHi\n";
Loop::addWriteStream($conn, function ($conn) use (&$data) {
$written = fwrite($conn, $data);
if ($written === strlen($data)) {
fclose($conn);
Loop::removeWriteStream($conn);
} else {
$data = substr($data, $written);
}
});
});
Loop::addPeriodicTimer(5, function () {
$memory = memory_get_usage() / 1024;
$formatted = number_format($memory, 3).'K';
echo "Current memory usage: {$formatted}\n";
});
```
See also the [examples](examples).
## Usage
Typical applications would use the [`Loop` class](#loop) to use the default
event loop like this:
```php
use React\EventLoop\Loop;
$timer = Loop::addPeriodicTimer(0.1, function () {
echo 'Tick' . PHP_EOL;
});
Loop::addTimer(1.0, function () use ($timer) {
Loop::cancelTimer($timer);
echo 'Done' . PHP_EOL;
});
```
As an alternative, you can also explicitly create an event loop instance at the
beginning, reuse it throughout your program and finally run it at the end of the
program like this:
```php
$loop = React\EventLoop\Loop::get(); // or deprecated React\EventLoop\Factory::create();
$timer = $loop->addPeriodicTimer(0.1, function () {
echo 'Tick' . PHP_EOL;
});
$loop->addTimer(1.0, function () use ($loop, $timer) {
$loop->cancelTimer($timer);
echo 'Done' . PHP_EOL;
});
$loop->run();
```
While the former is more concise, the latter is more explicit.
In both cases, the program would perform the exact same steps.
1. The event loop instance is created at the beginning of the program. This is
implicitly done the first time you call the [`Loop` class](#loop) or
explicitly when using the deprecated [`Factory::create()` method](#create)
(or manually instantiating any of the [loop implementations](#loop-implementations)).
2. The event loop is used directly or passed as an instance to library and
application code. In this example, a periodic timer is registered with the
event loop which simply outputs `Tick` every fraction of a second until another
timer stops the periodic timer after a second.
3. The event loop is run at the end of the program. This is automatically done
when using the [`Loop` class](#loop) or explicitly with a single [`run()`](#run)
call at the end of the program.
As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop).
The explicit loop instructions are still valid and may still be useful in some
applications, especially for a transition period towards the more concise style.
### Loop
The `Loop` class exists as a convenient global accessor for the event loop.
#### Loop methods
The `Loop` class provides all methods that exist on the [`LoopInterface`](#loopinterface)
as static methods:
* [run()](#run)
* [stop()](#stop)
* [addTimer()](#addtimer)
* [addPeriodicTimer()](#addperiodictimer)
* [cancelTimer()](#canceltimer)
* [futureTick()](#futuretick)
* [addSignal()](#addsignal)
* [removeSignal()](#removesignal)
* [addReadStream()](#addreadstream)
* [addWriteStream()](#addwritestream)
* [removeReadStream()](#removereadstream)
* [removeWriteStream()](#removewritestream)
If you're working with the event loop in your application code, it's often
easiest to directly interface with the static methods defined on the `Loop` class
like this:
```php
use React\EventLoop\Loop;
$timer = Loop::addPeriodicTimer(0.1, function () {
echo 'Tick' . PHP_EOL;
});
Loop::addTimer(1.0, function () use ($timer) {
Loop::cancelTimer($timer);
echo 'Done' . PHP_EOL;
});
```
On the other hand, if you're familiar with object-oriented programming (OOP) and
dependency injection (DI), you may want to inject an event loop instance and
invoke instance methods on the `LoopInterface` like this:
```php
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
class Greeter
{
private $loop;
public function __construct(LoopInterface $loop)
{
$this->loop = $loop;
}
public function greet(string $name)
{
$this->loop->addTimer(1.0, function () use ($name) {
echo 'Hello ' . $name . '!' . PHP_EOL;
});
}
}
$greeter = new Greeter(Loop::get());
$greeter->greet('Alice');
$greeter->greet('Bob');
```
Each static method call will be forwarded as-is to the underlying event loop
instance by using the [`Loop::get()`](#get) call internally.
See [`LoopInterface`](#loopinterface) for more details about available methods.
#### Loop autorun
When using the `Loop` class, it will automatically execute the loop at the end of
the program. This means the following example will schedule a timer and will
automatically execute the program until the timer event fires:
```php
use React\EventLoop\Loop;
Loop::addTimer(1.0, function () {
echo 'Hello' . PHP_EOL;
});
```
As of `v1.2.0`, we highly recommend using the `Loop` class this way and omitting any
explicit [`run()`](#run) calls. For BC reasons, the explicit [`run()`](#run)
method is still valid and may still be useful in some applications, especially
for a transition period towards the more concise style.
If you don't want the `Loop` to run automatically, you can either explicitly
[`run()`](#run) or [`stop()`](#stop) it. This can be useful if you're using
a global exception handler like this:
```php
use React\EventLoop\Loop;
Loop::addTimer(10.0, function () {
echo 'Never happens';
});
set_exception_handler(function (Throwable $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
Loop::stop();
});
throw new RuntimeException('Demo');
```
#### get()
The `get(): LoopInterface` method can be used to
get the currently active event loop instance.
This method will always return the same event loop instance throughout the
lifetime of your application.
```php
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
$loop = Loop::get();
assert($loop instanceof LoopInterface);
assert($loop === Loop::get());
```
This is particularly useful if you're using object-oriented programming (OOP)
and dependency injection (DI). In this case, you may want to inject an event
loop instance and invoke instance methods on the `LoopInterface` like this:
```php
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
class Greeter
{
private $loop;
public function __construct(LoopInterface $loop)
{
$this->loop = $loop;
}
public function greet(string $name)
{
$this->loop->addTimer(1.0, function () use ($name) {
echo 'Hello ' . $name . '!' . PHP_EOL;
});
}
}
$greeter = new Greeter(Loop::get());
$greeter->greet('Alice');
$greeter->greet('Bob');
```
See [`LoopInterface`](#loopinterface) for more details about available methods.
### ~~Factory~~
> Deprecated since v1.2.0, see [`Loop` class](#loop) instead.
The deprecated `Factory` class exists as a convenient way to pick the best available
[event loop implementation](#loop-implementations).
#### ~~create()~~
> Deprecated since v1.2.0, see [`Loop::get()`](#get) instead.
The deprecated `create(): LoopInterface` method can be used to
create a new event loop instance:
```php
// deprecated
$loop = React\EventLoop\Factory::create();
// new
$loop = React\EventLoop\Loop::get();
```
This method always returns an instance implementing [`LoopInterface`](#loopinterface),
the actual [event loop implementation](#loop-implementations) is an implementation detail.
This method should usually only be called once at the beginning of the program.
### Loop implementations
In addition to the [`LoopInterface`](#loopinterface), there are a number of
event loop implementations provided.
All of the event loops support these features:
* File descriptor polling
* One-off timers
* Periodic timers
* Deferred execution on future loop tick
For most consumers of this package, the underlying event loop implementation is
an implementation detail.
You should use the [`Loop` class](#loop) to automatically create a new instance.
Advanced! If you explicitly need a certain event loop implementation, you can
manually instantiate one of the following classes.
Note that you may have to install the required PHP extensions for the respective
event loop implementation first or they will throw a `BadMethodCallException` on creation.
#### StreamSelectLoop
A `stream_select()` based event loop.
This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
function and is the only implementation that works out of the box with PHP.
This event loop works out of the box on PHP 5.3 through PHP 8+ and HHVM.
This means that no installation is required and this library works on all
platforms and supported PHP versions.
Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory)
will use this event loop by default if you do not install any of the event loop
extensions listed below.
Under the hood, it does a simple `select` system call.
This system call is limited to the maximum file descriptor number of
`FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
(`m` being the maximum file descriptor number passed).
This means that you may run into issues when handling thousands of streams
concurrently and you may want to look into using one of the alternative
event loop implementations listed below in this case.
If your use case is among the many common use cases that involve handling only
dozens or a few hundred streams at once, then this event loop implementation
performs really well.
If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
this event loop implementation requires `ext-pcntl`.
This extension is only available for Unix-like platforms and does not support
Windows.
It is commonly installed as part of many PHP distributions.
If this extension is missing (or you're running on Windows), signal handling is
not supported and throws a `BadMethodCallException` instead.
This event loop is known to rely on wall-clock time to schedule future timers
when using any version before PHP 7.3, because a monotonic time source is
only available as of PHP 7.3 (`hrtime()`).
While this does not affect many common use cases, this is an important
distinction for programs that rely on a high time precision or on systems
that are subject to discontinuous time adjustments (time jumps).
This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and
then adjust your system time forward by 20s, the timer may trigger in 10s.
See also [`addTimer()`](#addtimer) for more details.
#### ExtEventLoop
An `ext-event` based event loop.
This uses the [`event` PECL extension](https://pecl.php.net/package/event),
that provides an interface to `libevent` library.
`libevent` itself supports a number of system-specific backends (epoll, kqueue).
This loop is known to work with PHP 5.4 through PHP 8+.
#### ExtEvLoop
An `ext-ev` based event loop.
This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
that provides an interface to `libev` library.
`libev` itself supports a number of system-specific backends (epoll, kqueue).
This loop is known to work with PHP 5.4 through PHP 8+.
#### ExtUvLoop
An `ext-uv` based event loop.
This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
that provides an interface to `libuv` library.
`libuv` itself supports a number of system-specific backends (epoll, kqueue).
This loop is known to work with PHP 7+.
#### ~~ExtLibeventLoop~~
> Deprecated since v1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
An `ext-libevent` based event loop.
This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
that provides an interface to `libevent` library.
`libevent` itself supports a number of system-specific backends (epoll, kqueue).
This event loop does only work with PHP 5.
An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
To reiterate: Using this event loop on PHP 7 is not recommended.
Accordingly, neither the [`Loop` class](#loop) nor the deprecated
[`Factory` class](#factory) will try to use this event loop on PHP 7.
This event loop is known to trigger a readable listener only if
the stream *becomes* readable (edge-triggered) and may not trigger if the
stream has already been readable from the beginning.
This also implies that a stream may not be recognized as readable when data
is still left in PHP's internal stream buffers.
As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
to disable PHP's internal read buffer in this case.
See also [`addReadStream()`](#addreadstream) for more details.
#### ~~ExtLibevLoop~~
> Deprecated since v1.2.0, use [`ExtEvLoop`](#extevloop) instead.
An `ext-libev` based event loop.
This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev),
that provides an interface to `libev` library.
`libev` itself supports a number of system-specific backends (epoll, kqueue).
This loop does only work with PHP 5.
An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
to happen any time soon.
### LoopInterface
#### run()
The `run(): void` method can be used to
run the event loop until there are no more tasks to perform.
For many applications, this method is the only directly visible
invocation on the event loop.
As a rule of thumb, it is usually recommended to attach everything to the
same loop instance and then run the loop once at the bottom end of the
application.
```php
$loop->run();
```
This method will keep the loop running until there are no more tasks
to perform. In other words: This method will block until the last
timer, stream and/or signal has been removed.
Likewise, it is imperative to ensure the application actually invokes
this method once. Adding listeners to the loop and missing to actually
run it will result in the application exiting without actually waiting
for any of the attached listeners.
This method MUST NOT be called while the loop is already running.
This method MAY be called more than once after it has explicitly been
[`stop()`ped](#stop) or after it automatically stopped because it
previously did no longer have anything to do.
#### stop()
The `stop(): void` method can be used to
instruct a running event loop to stop.
This method is considered advanced usage and should be used with care.
As a rule of thumb, it is usually recommended to let the loop stop
only automatically when it no longer has anything to do.
This method can be used to explicitly instruct the event loop to stop:
```php
$loop->addTimer(3.0, function () use ($loop) {
$loop->stop();
});
```
Calling this method on a loop instance that is not currently running or
on a loop instance that has already been stopped has no effect.
#### addTimer()
The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to
enqueue a callback to be invoked once after the given interval.
The second parameter MUST be a timer callback function that accepts
the timer instance as its only parameter.
If you don't use the timer instance inside your timer callback function
you MAY use a function which has no parameters at all.
The timer callback function MUST NOT throw an `Exception`.
The return value of the timer callback function will be ignored and has
no effect, so for performance reasons you're recommended to not return
any excessive data structures.
This method returns a timer instance. The same timer instance will also be
passed into the timer callback function as described above.
You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
the callback will be invoked only once after the given interval.
```php
$loop->addTimer(0.8, function () {
echo 'world!' . PHP_EOL;
});
$loop->addTimer(0.3, function () {
echo 'hello ';
});
```
See also [example #1](examples).
If you want to access any variables within your callback function, you
can bind arbitrary data to a callback closure like this:
```php
function hello($name, LoopInterface $loop)
{
$loop->addTimer(1.0, function () use ($name) {
echo "hello $name\n";
});
}
hello('Tester', $loop);
```
This interface does not enforce any particular timer resolution, so
special care may have to be taken if you rely on very high precision with
millisecond accuracy or below. Event loop implementations SHOULD work on
a best effort basis and SHOULD provide at least millisecond accuracy
unless otherwise noted. Many existing event loop implementations are
known to provide microsecond accuracy, but it's generally not recommended
to rely on this high precision.
Similarly, the execution order of timers scheduled to execute at the
same time (within its possible accuracy) is not guaranteed.
This interface suggests that event loop implementations SHOULD use a
monotonic time source if available. Given that a monotonic time source is
only available as of PHP 7.3 by default, event loop implementations MAY
fall back to using wall-clock time.
While this does not affect many common use cases, this is an important
distinction for programs that rely on a high time precision or on systems
that are subject to discontinuous time adjustments (time jumps).
This means that if you schedule a timer to trigger in 30s and then adjust
your system time forward by 20s, the timer SHOULD still trigger in 30s.
See also [event loop implementations](#loop-implementations) for more details.
#### addPeriodicTimer()
The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to
enqueue a callback to be invoked repeatedly after the given interval.
The second parameter MUST be a timer callback function that accepts
the timer instance as its only parameter.
If you don't use the timer instance inside your timer callback function
you MAY use a function which has no parameters at all.
The timer callback function MUST NOT throw an `Exception`.
The return value of the timer callback function will be ignored and has
no effect, so for performance reasons you're recommended to not return
any excessive data structures.
This method returns a timer instance. The same timer instance will also be
passed into the timer callback function as described above.
Unlike [`addTimer()`](#addtimer), this method will ensure the callback
will be invoked infinitely after the given interval or until you invoke
[`cancelTimer`](#canceltimer).
```php
$timer = $loop->addPeriodicTimer(0.1, function () {
echo 'tick!' . PHP_EOL;
});
$loop->addTimer(1.0, function () use ($loop, $timer) {
$loop->cancelTimer($timer);
echo 'Done' . PHP_EOL;
});
```
See also [example #2](examples).
If you want to limit the number of executions, you can bind
arbitrary data to a callback closure like this:
```php
function hello($name, LoopInterface $loop)
{
$n = 3;
$loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
if ($n > 0) {
--$n;
echo "hello $name\n";
} else {
$loop->cancelTimer($timer);
}
});
}
hello('Tester', $loop);
```
This interface does not enforce any particular timer resolution, so
special care may have to be taken if you rely on very high precision with
millisecond accuracy or below. Event loop implementations SHOULD work on
a best effort basis and SHOULD provide at least millisecond accuracy
unless otherwise noted. Many existing event loop implementations are
known to provide microsecond accuracy, but it's generally not recommended
to rely on this high precision.
Similarly, the execution order of timers scheduled to execute at the
same time (within its possible accuracy) is not guaranteed.
This interface suggests that event loop implementations SHOULD use a
monotonic time source if available. Given that a monotonic time source is
only available as of PHP 7.3 by default, event loop implementations MAY
fall back to using wall-clock time.
While this does not affect many common use cases, this is an important
distinction for programs that rely on a high time precision or on systems
that are subject to discontinuous time adjustments (time jumps).
This means that if you schedule a timer to trigger in 30s and then adjust
your system time forward by 20s, the timer SHOULD still trigger in 30s.
See also [event loop implementations](#loop-implementations) for more details.
Additionally, periodic timers may be subject to timer drift due to
re-scheduling after each invocation. As such, it's generally not
recommended to rely on this for high precision intervals with millisecond
accuracy or below.
#### cancelTimer()
The `cancelTimer(TimerInterface $timer): void` method can be used to
cancel a pending timer.
See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
Calling this method on a timer instance that has not been added to this
loop instance or on a timer that has already been cancelled has no effect.
#### futureTick()
The `futureTick(callable $listener): void` method can be used to
schedule a callback to be invoked on a future tick of the event loop.
This works very much similar to timers with an interval of zero seconds,
but does not require the overhead of scheduling a timer queue.
The tick callback function MUST be able to accept zero parameters.
The tick callback function MUST NOT throw an `Exception`.
The return value of the tick callback function will be ignored and has
no effect, so for performance reasons you're recommended to not return
any excessive data structures.
If you want to access any variables within your callback function, you
can bind arbitrary data to a callback closure like this:
```php
function hello($name, LoopInterface $loop)
{
$loop->futureTick(function () use ($name) {
echo "hello $name\n";
});
}
hello('Tester', $loop);
```
Unlike timers, tick callbacks are guaranteed to be executed in the order
they are enqueued.
Also, once a callback is enqueued, there's no way to cancel this operation.
This is often used to break down bigger tasks into smaller steps (a form
of cooperative multitasking).
```php
$loop->futureTick(function () {
echo 'b';
});
$loop->futureTick(function () {
echo 'c';
});
echo 'a';
```
See also [example #3](examples).
#### addSignal()
The `addSignal(int $signal, callable $listener): void` method can be used to
register a listener to be notified when a signal has been caught by this process.
This is useful to catch user interrupt signals or shutdown signals from
tools like `supervisor` or `systemd`.
The second parameter MUST be a listener callback function that accepts
the signal as its only parameter.
If you don't use the signal inside your listener callback function
you MAY use a function which has no parameters at all.
The listener callback function MUST NOT throw an `Exception`.
The return value of the listener callback function will be ignored and has
no effect, so for performance reasons you're recommended to not return
any excessive data structures.
```php
$loop->addSignal(SIGINT, function (int $signal) {
echo 'Caught user interrupt signal' . PHP_EOL;
});
```
See also [example #4](examples).
Signaling is only available on Unix-like platforms, Windows isn't
supported due to operating system limitations.
This method may throw a `BadMethodCallException` if signals aren't
supported on this platform, for example when required extensions are
missing.
**Note: A listener can only be added once to the same signal, any
attempts to add it more than once will be ignored.**
#### removeSignal()
The `removeSignal(int $signal, callable $listener): void` method can be used to
remove a previously added signal listener.
```php
$loop->removeSignal(SIGINT, $listener);
```
Any attempts to remove listeners that aren't registered will be ignored.
#### addReadStream()
> Advanced! Note that this low-level API is considered advanced usage.
Most use cases should probably use the higher-level
[readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
instead.
The `addReadStream(resource $stream, callable $callback): void` method can be used to
register a listener to be notified when a stream is ready to read.
The first parameter MUST be a valid stream resource that supports
checking whether it is ready to read by this loop implementation.
A single stream resource MUST NOT be added more than once.
Instead, either call [`removeReadStream()`](#removereadstream) first or
react to this event with a single listener and then dispatch from this
listener. This method MAY throw an `Exception` if the given resource type
is not supported by this loop implementation.
The second parameter MUST be a listener callback function that accepts
the stream resource as its only parameter.
If you don't use the stream resource inside your listener callback function
you MAY use a function which has no parameters at all.
The listener callback function MUST NOT throw an `Exception`.
The return value of the listener callback function will be ignored and has
no effect, so for performance reasons you're recommended to not return
any excessive data structures.
If you want to access any variables within your callback function, you
can bind arbitrary data to a callback closure like this:
```php
$loop->addReadStream($stream, function ($stream) use ($name) {
echo $name . ' said: ' . fread($stream);
});
```
See also [example #11](examples).
You can invoke [`removeReadStream()`](#removereadstream) to remove the
read event listener for this stream.
The execution order of listeners when multiple streams become ready at
the same time is not guaranteed.
Some event loop implementations are known to only trigger the listener if
the stream *becomes* readable (edge-triggered) and may not trigger if the
stream has already been readable from the beginning.
This also implies that a stream may not be recognized as readable when data
is still left in PHP's internal stream buffers.
As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
to disable PHP's internal read buffer in this case.
#### addWriteStream()
> Advanced! Note that this low-level API is considered advanced usage.
Most use cases should probably use the higher-level
[writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
instead.
The `addWriteStream(resource $stream, callable $callback): void` method can be used to
register a listener to be notified when a stream is ready to write.
The first parameter MUST be a valid stream resource that supports
checking whether it is ready to write by this loop implementation.
A single stream resource MUST NOT be added more than once.
Instead, either call [`removeWriteStream()`](#removewritestream) first or
react to this event with a single listener and then dispatch from this
listener. This method MAY throw an `Exception` if the given resource type
is not supported by this loop implementation.
The second parameter MUST be a listener callback function that accepts
the stream resource as its only parameter.
If you don't use the stream resource inside your listener callback function
you MAY use a function which has no parameters at all.
The listener callback function MUST NOT throw an `Exception`.
The return value of the listener callback function will be ignored and has
no effect, so for performance reasons you're recommended to not return
any excessive data structures.
If you want to access any variables within your callback function, you
can bind arbitrary data to a callback closure like this:
```php
$loop->addWriteStream($stream, function ($stream) use ($name) {
fwrite($stream, 'Hello ' . $name);
});
```
See also [example #12](examples).
You can invoke [`removeWriteStream()`](#removewritestream) to remove the
write event listener for this stream.
The execution order of listeners when multiple streams become ready at
the same time is not guaranteed.
#### removeReadStream()
The `removeReadStream(resource $stream): void` method can be used to
remove the read event listener for the given stream.
Removing a stream from the loop that has already been removed or trying
to remove a stream that was never added or is invalid has no effect.
#### removeWriteStream()
The `removeWriteStream(resource $stream): void` method can be used to
remove the write event listener for the given stream.
Removing a stream from the loop that has already been removed or trying
to remove a stream that was never added or is invalid has no effect.
## Install
The recommended way to install this library is [through Composer](https://getcomposer.org/).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This project follows [SemVer](https://semver.org/).
This will install the latest supported version:
```bash
composer require react/event-loop:^1.5
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
HHVM.
It's *highly recommended to use the latest supported PHP version* for this project.
Installing any of the event loop extensions is suggested, but entirely optional.
See also [event loop implementations](#loop-implementations) for more details.
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org/):
```bash
composer install
```
To run the test suite, go to the project root and run:
```bash
vendor/bin/phpunit
```
## License
MIT, see [LICENSE file](LICENSE).
## More
* See our [Stream component](https://github.com/reactphp/stream) for more
information on how streams are used in real-world applications.
* See our [users wiki](https://github.com/reactphp/react/wiki/Users) and the
[dependents on Packagist](https://packagist.org/packages/react/event-loop/dependents)
for a list of packages that use the EventLoop in real-world applications.

47
vendor/react/event-loop/composer.json vendored Executable file
View File

@@ -0,0 +1,47 @@
{
"name": "react/event-loop",
"description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
"keywords": ["event-loop", "asynchronous"],
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"homepage": "https://clue.engineering/",
"email": "christian@clue.engineering"
},
{
"name": "Cees-Jan Kiewiet",
"homepage": "https://wyrihaximus.net/",
"email": "reactphp@ceesjankiewiet.nl"
},
{
"name": "Jan Sorgalla",
"homepage": "https://sorgalla.com/",
"email": "jsorgalla@gmail.com"
},
{
"name": "Chris Boden",
"homepage": "https://cboden.dev/",
"email": "cboden@gmail.com"
}
],
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"suggest": {
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
},
"autoload": {
"psr-4": {
"React\\EventLoop\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"React\\Tests\\EventLoop\\": "tests/"
}
}
}

253
vendor/react/event-loop/src/ExtEvLoop.php vendored Executable file
View File

@@ -0,0 +1,253 @@
<?php
namespace React\EventLoop;
use Ev;
use EvIo;
use EvLoop;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* An `ext-ev` based event loop.
*
* This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
* that provides an interface to `libev` library.
* `libev` itself supports a number of system-specific backends (epoll, kqueue).
*
* This loop is known to work with PHP 5.4 through PHP 8+.
*
* @see http://php.net/manual/en/book.ev.php
* @see https://bitbucket.org/osmanov/pecl-ev/overview
*/
class ExtEvLoop implements LoopInterface
{
/**
* @var EvLoop
*/
private $loop;
/**
* @var FutureTickQueue
*/
private $futureTickQueue;
/**
* @var SplObjectStorage
*/
private $timers;
/**
* @var EvIo[]
*/
private $readStreams = array();
/**
* @var EvIo[]
*/
private $writeStreams = array();
/**
* @var bool
*/
private $running;
/**
* @var SignalsHandler
*/
private $signals;
/**
* @var \EvSignal[]
*/
private $signalEvents = array();
public function __construct()
{
$this->loop = new EvLoop();
$this->futureTickQueue = new FutureTickQueue();
$this->timers = new SplObjectStorage();
$this->signals = new SignalsHandler();
}
public function addReadStream($stream, $listener)
{
$key = (int)$stream;
if (isset($this->readStreams[$key])) {
return;
}
$callback = $this->getStreamListenerClosure($stream, $listener);
$event = $this->loop->io($stream, Ev::READ, $callback);
$this->readStreams[$key] = $event;
}
/**
* @param resource $stream
* @param callable $listener
*
* @return \Closure
*/
private function getStreamListenerClosure($stream, $listener)
{
return function () use ($stream, $listener) {
\call_user_func($listener, $stream);
};
}
public function addWriteStream($stream, $listener)
{
$key = (int)$stream;
if (isset($this->writeStreams[$key])) {
return;
}
$callback = $this->getStreamListenerClosure($stream, $listener);
$event = $this->loop->io($stream, Ev::WRITE, $callback);
$this->writeStreams[$key] = $event;
}
public function removeReadStream($stream)
{
$key = (int)$stream;
if (!isset($this->readStreams[$key])) {
return;
}
$this->readStreams[$key]->stop();
unset($this->readStreams[$key]);
}
public function removeWriteStream($stream)
{
$key = (int)$stream;
if (!isset($this->writeStreams[$key])) {
return;
}
$this->writeStreams[$key]->stop();
unset($this->writeStreams[$key]);
}
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$that = $this;
$timers = $this->timers;
$callback = function () use ($timer, $timers, $that) {
\call_user_func($timer->getCallback(), $timer);
if ($timers->contains($timer)) {
$that->cancelTimer($timer);
}
};
$event = $this->loop->timer($timer->getInterval(), 0.0, $callback);
$this->timers->attach($timer, $event);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$callback = function () use ($timer) {
\call_user_func($timer->getCallback(), $timer);
};
$event = $this->loop->timer($timer->getInterval(), $timer->getInterval(), $callback);
$this->timers->attach($timer, $event);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
if (!isset($this->timers[$timer])) {
return;
}
$event = $this->timers[$timer];
$event->stop();
$this->timers->detach($timer);
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
$wasJustStopped = !$this->running;
$nothingLeftToDo = !$this->readStreams
&& !$this->writeStreams
&& !$this->timers->count()
&& $this->signals->isEmpty();
$flags = Ev::RUN_ONCE;
if ($wasJustStopped || $hasPendingCallbacks) {
$flags |= Ev::RUN_NOWAIT;
} elseif ($nothingLeftToDo) {
break;
}
$this->loop->run($flags);
}
}
public function stop()
{
$this->running = false;
}
public function __destruct()
{
/** @var TimerInterface $timer */
foreach ($this->timers as $timer) {
$this->cancelTimer($timer);
}
foreach ($this->readStreams as $key => $stream) {
$this->removeReadStream($key);
}
foreach ($this->writeStreams as $key => $stream) {
$this->removeWriteStream($key);
}
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) {
$this->signals->call($signal);
});
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal])) {
$this->signalEvents[$signal]->stop();
unset($this->signalEvents[$signal]);
}
}
}

275
vendor/react/event-loop/src/ExtEventLoop.php vendored Executable file
View File

@@ -0,0 +1,275 @@
<?php
namespace React\EventLoop;
use BadMethodCallException;
use Event;
use EventBase;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* An `ext-event` based event loop.
*
* This uses the [`event` PECL extension](https://pecl.php.net/package/event),
* that provides an interface to `libevent` library.
* `libevent` itself supports a number of system-specific backends (epoll, kqueue).
*
* This loop is known to work with PHP 5.4 through PHP 8+.
*
* @link https://pecl.php.net/package/event
*/
final class ExtEventLoop implements LoopInterface
{
private $eventBase;
private $futureTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $readEvents = array();
private $writeEvents = array();
private $readListeners = array();
private $writeListeners = array();
private $readRefs = array();
private $writeRefs = array();
private $running;
private $signals;
private $signalEvents = array();
public function __construct()
{
if (!\class_exists('EventBase', false)) {
throw new BadMethodCallException('Cannot create ExtEventLoop, ext-event extension missing');
}
// support arbitrary file descriptors and not just sockets
// Windows only has limited file descriptor support, so do not require this (will fail otherwise)
// @link http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html#_setting_up_a_complicated_event_base
$config = new \EventConfig();
if (\DIRECTORY_SEPARATOR !== '\\') {
$config->requireFeatures(\EventConfig::FEATURE_FDS);
}
$this->eventBase = new EventBase($config);
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();
$this->signals = new SignalsHandler();
$this->createTimerCallback();
$this->createStreamCallback();
}
public function __destruct()
{
// explicitly clear all references to Event objects to prevent SEGFAULTs on Windows
foreach ($this->timerEvents as $timer) {
$this->timerEvents->detach($timer);
}
$this->readEvents = array();
$this->writeEvents = array();
}
public function addReadStream($stream, $listener)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
return;
}
$event = new Event($this->eventBase, $stream, Event::PERSIST | Event::READ, $this->streamCallback);
$event->add();
$this->readEvents[$key] = $event;
$this->readListeners[$key] = $listener;
// ext-event does not increase refcount on stream resources for PHP 7+
// manually keep track of stream resource to prevent premature garbage collection
if (\PHP_VERSION_ID >= 70000) {
$this->readRefs[$key] = $stream;
}
}
public function addWriteStream($stream, $listener)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
return;
}
$event = new Event($this->eventBase, $stream, Event::PERSIST | Event::WRITE, $this->streamCallback);
$event->add();
$this->writeEvents[$key] = $event;
$this->writeListeners[$key] = $listener;
// ext-event does not increase refcount on stream resources for PHP 7+
// manually keep track of stream resource to prevent premature garbage collection
if (\PHP_VERSION_ID >= 70000) {
$this->writeRefs[$key] = $stream;
}
}
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readEvents[$key])) {
$this->readEvents[$key]->free();
unset(
$this->readEvents[$key],
$this->readListeners[$key],
$this->readRefs[$key]
);
}
}
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeEvents[$key])) {
$this->writeEvents[$key]->free();
unset(
$this->writeEvents[$key],
$this->writeListeners[$key],
$this->writeRefs[$key]
);
}
}
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$this->scheduleTimer($timer);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$this->scheduleTimer($timer);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
if ($this->timerEvents->contains($timer)) {
$this->timerEvents[$timer]->free();
$this->timerEvents->detach($timer);
}
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, array($this->signals, 'call'));
$this->signalEvents[$signal]->add();
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
$this->signalEvents[$signal]->free();
unset($this->signalEvents[$signal]);
}
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$flags = EventBase::LOOP_ONCE;
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
$flags |= EventBase::LOOP_NONBLOCK;
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
break;
}
$this->eventBase->loop($flags);
}
}
public function stop()
{
$this->running = false;
}
/**
* Schedule a timer for execution.
*
* @param TimerInterface $timer
*/
private function scheduleTimer(TimerInterface $timer)
{
$flags = Event::TIMEOUT;
if ($timer->isPeriodic()) {
$flags |= Event::PERSIST;
}
$event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
$this->timerEvents[$timer] = $event;
$event->add($timer->getInterval());
}
/**
* Create a callback used as the target of timer events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createTimerCallback()
{
$timers = $this->timerEvents;
$this->timerCallback = function ($_, $__, $timer) use ($timers) {
\call_user_func($timer->getCallback(), $timer);
if (!$timer->isPeriodic() && $timers->contains($timer)) {
$this->cancelTimer($timer);
}
};
}
/**
* Create a callback used as the target of stream events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createStreamCallback()
{
$read =& $this->readListeners;
$write =& $this->writeListeners;
$this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
$key = (int) $stream;
if (Event::READ === (Event::READ & $flags) && isset($read[$key])) {
\call_user_func($read[$key], $stream);
}
if (Event::WRITE === (Event::WRITE & $flags) && isset($write[$key])) {
\call_user_func($write[$key], $stream);
}
};
}
}

201
vendor/react/event-loop/src/ExtLibevLoop.php vendored Executable file
View File

@@ -0,0 +1,201 @@
<?php
namespace React\EventLoop;
use BadMethodCallException;
use libev\EventLoop;
use libev\IOEvent;
use libev\SignalEvent;
use libev\TimerEvent;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* [Deprecated] An `ext-libev` based event loop.
*
* This uses an [unofficial `libev` extension](https://github.com/m4rw3r/php-libev),
* that provides an interface to `libev` library.
* `libev` itself supports a number of system-specific backends (epoll, kqueue).
*
* This loop does only work with PHP 5.
* An update for PHP 7 is [unlikely](https://github.com/m4rw3r/php-libev/issues/8)
* to happen any time soon.
*
* @see https://github.com/m4rw3r/php-libev
* @see https://gist.github.com/1688204
* @deprecated 1.2.0, use [`ExtEvLoop`](#extevloop) instead.
*/
final class ExtLibevLoop implements LoopInterface
{
private $loop;
private $futureTickQueue;
private $timerEvents;
private $readEvents = array();
private $writeEvents = array();
private $running;
private $signals;
private $signalEvents = array();
public function __construct()
{
if (!\class_exists('libev\EventLoop', false)) {
throw new BadMethodCallException('Cannot create ExtLibevLoop, ext-libev extension missing');
}
$this->loop = new EventLoop();
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();
$this->signals = new SignalsHandler();
}
public function addReadStream($stream, $listener)
{
if (isset($this->readEvents[(int) $stream])) {
return;
}
$callback = function () use ($stream, $listener) {
\call_user_func($listener, $stream);
};
$event = new IOEvent($callback, $stream, IOEvent::READ);
$this->loop->add($event);
$this->readEvents[(int) $stream] = $event;
}
public function addWriteStream($stream, $listener)
{
if (isset($this->writeEvents[(int) $stream])) {
return;
}
$callback = function () use ($stream, $listener) {
\call_user_func($listener, $stream);
};
$event = new IOEvent($callback, $stream, IOEvent::WRITE);
$this->loop->add($event);
$this->writeEvents[(int) $stream] = $event;
}
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readEvents[$key])) {
$this->readEvents[$key]->stop();
$this->loop->remove($this->readEvents[$key]);
unset($this->readEvents[$key]);
}
}
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeEvents[$key])) {
$this->writeEvents[$key]->stop();
$this->loop->remove($this->writeEvents[$key]);
unset($this->writeEvents[$key]);
}
}
public function addTimer($interval, $callback)
{
$timer = new Timer( $interval, $callback, false);
$that = $this;
$timers = $this->timerEvents;
$callback = function () use ($timer, $timers, $that) {
\call_user_func($timer->getCallback(), $timer);
if ($timers->contains($timer)) {
$that->cancelTimer($timer);
}
};
$event = new TimerEvent($callback, $timer->getInterval());
$this->timerEvents->attach($timer, $event);
$this->loop->add($event);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$callback = function () use ($timer) {
\call_user_func($timer->getCallback(), $timer);
};
$event = new TimerEvent($callback, $timer->getInterval(), $timer->getInterval());
$this->timerEvents->attach($timer, $event);
$this->loop->add($event);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
if (isset($this->timerEvents[$timer])) {
$this->loop->remove($this->timerEvents[$timer]);
$this->timerEvents->detach($timer);
}
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$signals = $this->signals;
$this->signalEvents[$signal] = new SignalEvent(function () use ($signals, $signal) {
$signals->call($signal);
}, $signal);
$this->loop->add($this->signalEvents[$signal]);
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
$this->signalEvents[$signal]->stop();
$this->loop->remove($this->signalEvents[$signal]);
unset($this->signalEvents[$signal]);
}
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$flags = EventLoop::RUN_ONCE;
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
$flags |= EventLoop::RUN_NOWAIT;
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
break;
}
$this->loop->run($flags);
}
}
public function stop()
{
$this->running = false;
}
}

View File

@@ -0,0 +1,285 @@
<?php
namespace React\EventLoop;
use BadMethodCallException;
use Event;
use EventBase;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* [Deprecated] An `ext-libevent` based event loop.
*
* This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
* that provides an interface to `libevent` library.
* `libevent` itself supports a number of system-specific backends (epoll, kqueue).
*
* This event loop does only work with PHP 5.
* An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
* PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
* To reiterate: Using this event loop on PHP 7 is not recommended.
* Accordingly, neither the [`Loop` class](#loop) nor the deprecated
* [`Factory` class](#factory) will try to use this event loop on PHP 7.
*
* This event loop is known to trigger a readable listener only if
* the stream *becomes* readable (edge-triggered) and may not trigger if the
* stream has already been readable from the beginning.
* This also implies that a stream may not be recognized as readable when data
* is still left in PHP's internal stream buffers.
* As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
* to disable PHP's internal read buffer in this case.
* See also [`addReadStream()`](#addreadstream) for more details.
*
* @link https://pecl.php.net/package/libevent
* @deprecated 1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
*/
final class ExtLibeventLoop implements LoopInterface
{
/** @internal */
const MICROSECONDS_PER_SECOND = 1000000;
private $eventBase;
private $futureTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $readEvents = array();
private $writeEvents = array();
private $readListeners = array();
private $writeListeners = array();
private $running;
private $signals;
private $signalEvents = array();
public function __construct()
{
if (!\function_exists('event_base_new')) {
throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing');
}
$this->eventBase = \event_base_new();
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();
$this->signals = new SignalsHandler();
$this->createTimerCallback();
$this->createStreamCallback();
}
public function addReadStream($stream, $listener)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
return;
}
$event = \event_new();
\event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback);
\event_base_set($event, $this->eventBase);
\event_add($event);
$this->readEvents[$key] = $event;
$this->readListeners[$key] = $listener;
}
public function addWriteStream($stream, $listener)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
return;
}
$event = \event_new();
\event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback);
\event_base_set($event, $this->eventBase);
\event_add($event);
$this->writeEvents[$key] = $event;
$this->writeListeners[$key] = $listener;
}
public function removeReadStream($stream)
{
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
$event = $this->readEvents[$key];
\event_del($event);
\event_free($event);
unset(
$this->readEvents[$key],
$this->readListeners[$key]
);
}
}
public function removeWriteStream($stream)
{
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
$event = $this->writeEvents[$key];
\event_del($event);
\event_free($event);
unset(
$this->writeEvents[$key],
$this->writeListeners[$key]
);
}
}
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$this->scheduleTimer($timer);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$this->scheduleTimer($timer);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
if ($this->timerEvents->contains($timer)) {
$event = $this->timerEvents[$timer];
\event_del($event);
\event_free($event);
$this->timerEvents->detach($timer);
}
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$this->signalEvents[$signal] = \event_new();
\event_set($this->signalEvents[$signal], $signal, \EV_PERSIST | \EV_SIGNAL, array($this->signals, 'call'));
\event_base_set($this->signalEvents[$signal], $this->eventBase);
\event_add($this->signalEvents[$signal]);
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
\event_del($this->signalEvents[$signal]);
\event_free($this->signalEvents[$signal]);
unset($this->signalEvents[$signal]);
}
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$flags = \EVLOOP_ONCE;
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
$flags |= \EVLOOP_NONBLOCK;
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
break;
}
\event_base_loop($this->eventBase, $flags);
}
}
public function stop()
{
$this->running = false;
}
/**
* Schedule a timer for execution.
*
* @param TimerInterface $timer
*/
private function scheduleTimer(TimerInterface $timer)
{
$this->timerEvents[$timer] = $event = \event_timer_new();
\event_timer_set($event, $this->timerCallback, $timer);
\event_base_set($event, $this->eventBase);
\event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
}
/**
* Create a callback used as the target of timer events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createTimerCallback()
{
$that = $this;
$timers = $this->timerEvents;
$this->timerCallback = function ($_, $__, $timer) use ($timers, $that) {
\call_user_func($timer->getCallback(), $timer);
// Timer already cancelled ...
if (!$timers->contains($timer)) {
return;
}
// Reschedule periodic timers ...
if ($timer->isPeriodic()) {
\event_add(
$timers[$timer],
$timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND
);
// Clean-up one shot timers ...
} else {
$that->cancelTimer($timer);
}
};
}
/**
* Create a callback used as the target of stream events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createStreamCallback()
{
$read =& $this->readListeners;
$write =& $this->writeListeners;
$this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
$key = (int) $stream;
if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) {
\call_user_func($read[$key], $stream);
}
if (\EV_WRITE === (\EV_WRITE & $flags) && isset($write[$key])) {
\call_user_func($write[$key], $stream);
}
};
}
}

342
vendor/react/event-loop/src/ExtUvLoop.php vendored Executable file
View File

@@ -0,0 +1,342 @@
<?php
namespace React\EventLoop;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use SplObjectStorage;
/**
* An `ext-uv` based event loop.
*
* This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
* that provides an interface to `libuv` library.
* `libuv` itself supports a number of system-specific backends (epoll, kqueue).
*
* This loop is known to work with PHP 7+.
*
* @see https://github.com/bwoebi/php-uv
*/
final class ExtUvLoop implements LoopInterface
{
private $uv;
private $futureTickQueue;
private $timers;
private $streamEvents = array();
private $readStreams = array();
private $writeStreams = array();
private $running;
private $signals;
private $signalEvents = array();
private $streamListener;
public function __construct()
{
if (!\function_exists('uv_loop_new')) {
throw new \BadMethodCallException('Cannot create LibUvLoop, ext-uv extension missing');
}
$this->uv = \uv_loop_new();
$this->futureTickQueue = new FutureTickQueue();
$this->timers = new SplObjectStorage();
$this->streamListener = $this->createStreamListener();
$this->signals = new SignalsHandler();
}
/**
* Returns the underlying ext-uv event loop. (Internal ReactPHP use only.)
*
* @internal
*
* @return resource
*/
public function getUvLoop()
{
return $this->uv;
}
/**
* {@inheritdoc}
*/
public function addReadStream($stream, $listener)
{
if (isset($this->readStreams[(int) $stream])) {
return;
}
$this->readStreams[(int) $stream] = $listener;
$this->addStream($stream);
}
/**
* {@inheritdoc}
*/
public function addWriteStream($stream, $listener)
{
if (isset($this->writeStreams[(int) $stream])) {
return;
}
$this->writeStreams[(int) $stream] = $listener;
$this->addStream($stream);
}
/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
return;
}
unset($this->readStreams[(int) $stream]);
$this->removeStream($stream);
}
/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
return;
}
unset($this->writeStreams[(int) $stream]);
$this->removeStream($stream);
}
/**
* {@inheritdoc}
*/
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$that = $this;
$timers = $this->timers;
$callback = function () use ($timer, $timers, $that) {
\call_user_func($timer->getCallback(), $timer);
if ($timers->contains($timer)) {
$that->cancelTimer($timer);
}
};
$event = \uv_timer_init($this->uv);
$this->timers->attach($timer, $event);
\uv_timer_start(
$event,
$this->convertFloatSecondsToMilliseconds($interval),
0,
$callback
);
return $timer;
}
/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$callback = function () use ($timer) {
\call_user_func($timer->getCallback(), $timer);
};
$interval = $this->convertFloatSecondsToMilliseconds($interval);
$event = \uv_timer_init($this->uv);
$this->timers->attach($timer, $event);
\uv_timer_start(
$event,
$interval,
(int) $interval === 0 ? 1 : $interval,
$callback
);
return $timer;
}
/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if (isset($this->timers[$timer])) {
@\uv_timer_stop($this->timers[$timer]);
$this->timers->detach($timer);
}
}
/**
* {@inheritdoc}
*/
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
$this->signals->add($signal, $listener);
if (!isset($this->signalEvents[$signal])) {
$signals = $this->signals;
$this->signalEvents[$signal] = \uv_signal_init($this->uv);
\uv_signal_start($this->signalEvents[$signal], function () use ($signals, $signal) {
$signals->call($signal);
}, $signal);
}
}
public function removeSignal($signal, $listener)
{
$this->signals->remove($signal, $listener);
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
\uv_signal_stop($this->signalEvents[$signal]);
unset($this->signalEvents[$signal]);
}
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
$wasJustStopped = !$this->running;
$nothingLeftToDo = !$this->readStreams
&& !$this->writeStreams
&& !$this->timers->count()
&& $this->signals->isEmpty();
// Use UV::RUN_ONCE when there are only I/O events active in the loop and block until one of those triggers,
// otherwise use UV::RUN_NOWAIT.
// @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run
$flags = \UV::RUN_ONCE;
if ($wasJustStopped || $hasPendingCallbacks) {
$flags = \UV::RUN_NOWAIT;
} elseif ($nothingLeftToDo) {
break;
}
\uv_run($this->uv, $flags);
}
}
/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}
private function addStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
$this->streamEvents[(int)$stream] = \uv_poll_init_socket($this->uv, $stream);
}
if ($this->streamEvents[(int) $stream] !== false) {
$this->pollStream($stream);
}
}
private function removeStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
return;
}
if (!isset($this->readStreams[(int) $stream])
&& !isset($this->writeStreams[(int) $stream])) {
\uv_poll_stop($this->streamEvents[(int) $stream]);
\uv_close($this->streamEvents[(int) $stream]);
unset($this->streamEvents[(int) $stream]);
return;
}
$this->pollStream($stream);
}
private function pollStream($stream)
{
if (!isset($this->streamEvents[(int) $stream])) {
return;
}
$flags = 0;
if (isset($this->readStreams[(int) $stream])) {
$flags |= \UV::READABLE;
}
if (isset($this->writeStreams[(int) $stream])) {
$flags |= \UV::WRITABLE;
}
\uv_poll_start($this->streamEvents[(int) $stream], $flags, $this->streamListener);
}
/**
* Create a stream listener
*
* @return callable Returns a callback
*/
private function createStreamListener()
{
$callback = function ($event, $status, $events, $stream) {
// libuv automatically stops polling on error, re-enable polling to match other loop implementations
if ($status !== 0) {
$this->pollStream($stream);
// libuv may report no events on error, but this should still invoke stream listeners to report closed connections
// re-enable both readable and writable, correct listeners will be checked below anyway
if ($events === 0) {
$events = \UV::READABLE | \UV::WRITABLE;
}
}
if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) {
\call_user_func($this->readStreams[(int) $stream], $stream);
}
if (isset($this->writeStreams[(int) $stream]) && ($events & \UV::WRITABLE)) {
\call_user_func($this->writeStreams[(int) $stream], $stream);
}
};
return $callback;
}
/**
* @param float $interval
* @return int
*/
private function convertFloatSecondsToMilliseconds($interval)
{
if ($interval < 0) {
return 0;
}
$maxValue = (int) (\PHP_INT_MAX / 1000);
$intInterval = (int) $interval;
if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) {
throw new \InvalidArgumentException(
"Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed."
);
}
return (int) \floor($interval * 1000);
}
}

75
vendor/react/event-loop/src/Factory.php vendored Executable file
View File

@@ -0,0 +1,75 @@
<?php
namespace React\EventLoop;
/**
* [Deprecated] The `Factory` class exists as a convenient way to pick the best available event loop implementation.
*
* @deprecated 1.2.0 See Loop instead.
* @see Loop
*/
final class Factory
{
/**
* [Deprecated] Creates a new event loop instance
*
* ```php
* // deprecated
* $loop = React\EventLoop\Factory::create();
*
* // new
* $loop = React\EventLoop\Loop::get();
* ```
*
* This method always returns an instance implementing `LoopInterface`,
* the actual event loop implementation is an implementation detail.
*
* This method should usually only be called once at the beginning of the program.
*
* @deprecated 1.2.0 See Loop::get() instead.
* @see Loop::get()
*
* @return LoopInterface
*/
public static function create()
{
$loop = self::construct();
Loop::set($loop);
return $loop;
}
/**
* @internal
* @return LoopInterface
*/
private static function construct()
{
// @codeCoverageIgnoreStart
if (\function_exists('uv_loop_new')) {
// only use ext-uv on PHP 7
return new ExtUvLoop();
}
if (\class_exists('libev\EventLoop', false)) {
return new ExtLibevLoop();
}
if (\class_exists('EvLoop', false)) {
return new ExtEvLoop();
}
if (\class_exists('EventBase', false)) {
return new ExtEventLoop();
}
if (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) {
// only use ext-libevent on PHP 5 for now
return new ExtLibeventLoop();
}
return new StreamSelectLoop();
// @codeCoverageIgnoreEnd
}
}

266
vendor/react/event-loop/src/Loop.php vendored Executable file
View File

@@ -0,0 +1,266 @@
<?php
namespace React\EventLoop;
/**
* The `Loop` class exists as a convenient way to get the currently relevant loop
*/
final class Loop
{
/**
* @var ?LoopInterface
*/
private static $instance;
/** @var bool */
private static $stopped = false;
/**
* Returns the event loop.
* When no loop is set, it will call the factory to create one.
*
* This method always returns an instance implementing `LoopInterface`,
* the actual event loop implementation is an implementation detail.
*
* This method is the preferred way to get the event loop and using
* Factory::create has been deprecated.
*
* @return LoopInterface
*/
public static function get()
{
if (self::$instance instanceof LoopInterface) {
return self::$instance;
}
self::$instance = $loop = Factory::create();
// Automatically run loop at end of program, unless already started or stopped explicitly.
// This is tested using child processes, so coverage is actually 100%, see BinTest.
// @codeCoverageIgnoreStart
$hasRun = false;
$loop->futureTick(function () use (&$hasRun) {
$hasRun = true;
});
$stopped =& self::$stopped;
register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) {
// Don't run if we're coming from a fatal error (uncaught exception).
$error = error_get_last();
if ((isset($error['type']) ? $error['type'] : 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) {
return;
}
if (!$hasRun && !$stopped) {
$loop->run();
}
});
// @codeCoverageIgnoreEnd
return self::$instance;
}
/**
* Internal undocumented method, behavior might change or throw in the
* future. Use with caution and at your own risk.
*
* @internal
* @return void
*/
public static function set(LoopInterface $loop)
{
self::$instance = $loop;
}
/**
* [Advanced] Register a listener to be notified when a stream is ready to read.
*
* @param resource $stream
* @param callable $listener
* @return void
* @throws \Exception
* @see LoopInterface::addReadStream()
*/
public static function addReadStream($stream, $listener)
{
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
if (self::$instance === null) {
self::get();
}
self::$instance->addReadStream($stream, $listener);
}
/**
* [Advanced] Register a listener to be notified when a stream is ready to write.
*
* @param resource $stream
* @param callable $listener
* @return void
* @throws \Exception
* @see LoopInterface::addWriteStream()
*/
public static function addWriteStream($stream, $listener)
{
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
if (self::$instance === null) {
self::get();
}
self::$instance->addWriteStream($stream, $listener);
}
/**
* Remove the read event listener for the given stream.
*
* @param resource $stream
* @return void
* @see LoopInterface::removeReadStream()
*/
public static function removeReadStream($stream)
{
if (self::$instance !== null) {
self::$instance->removeReadStream($stream);
}
}
/**
* Remove the write event listener for the given stream.
*
* @param resource $stream
* @return void
* @see LoopInterface::removeWriteStream()
*/
public static function removeWriteStream($stream)
{
if (self::$instance !== null) {
self::$instance->removeWriteStream($stream);
}
}
/**
* Enqueue a callback to be invoked once after the given interval.
*
* @param float $interval
* @param callable $callback
* @return TimerInterface
* @see LoopInterface::addTimer()
*/
public static function addTimer($interval, $callback)
{
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
if (self::$instance === null) {
self::get();
}
return self::$instance->addTimer($interval, $callback);
}
/**
* Enqueue a callback to be invoked repeatedly after the given interval.
*
* @param float $interval
* @param callable $callback
* @return TimerInterface
* @see LoopInterface::addPeriodicTimer()
*/
public static function addPeriodicTimer($interval, $callback)
{
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
if (self::$instance === null) {
self::get();
}
return self::$instance->addPeriodicTimer($interval, $callback);
}
/**
* Cancel a pending timer.
*
* @param TimerInterface $timer
* @return void
* @see LoopInterface::cancelTimer()
*/
public static function cancelTimer(TimerInterface $timer)
{
if (self::$instance !== null) {
self::$instance->cancelTimer($timer);
}
}
/**
* Schedule a callback to be invoked on a future tick of the event loop.
*
* @param callable $listener
* @return void
* @see LoopInterface::futureTick()
*/
public static function futureTick($listener)
{
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
if (self::$instance === null) {
self::get();
}
self::$instance->futureTick($listener);
}
/**
* Register a listener to be notified when a signal has been caught by this process.
*
* @param int $signal
* @param callable $listener
* @return void
* @see LoopInterface::addSignal()
*/
public static function addSignal($signal, $listener)
{
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
if (self::$instance === null) {
self::get();
}
self::$instance->addSignal($signal, $listener);
}
/**
* Removes a previously added signal listener.
*
* @param int $signal
* @param callable $listener
* @return void
* @see LoopInterface::removeSignal()
*/
public static function removeSignal($signal, $listener)
{
if (self::$instance !== null) {
self::$instance->removeSignal($signal, $listener);
}
}
/**
* Run the event loop until there are no more tasks to perform.
*
* @return void
* @see LoopInterface::run()
*/
public static function run()
{
// create loop instance on demand (legacy PHP < 7 doesn't like ternaries in method calls)
if (self::$instance === null) {
self::get();
}
self::$instance->run();
}
/**
* Instruct a running event loop to stop.
*
* @return void
* @see LoopInterface::stop()
*/
public static function stop()
{
self::$stopped = true;
if (self::$instance !== null) {
self::$instance->stop();
}
}
}

472
vendor/react/event-loop/src/LoopInterface.php vendored Executable file
View File

@@ -0,0 +1,472 @@
<?php
namespace React\EventLoop;
interface LoopInterface
{
/**
* [Advanced] Register a listener to be notified when a stream is ready to read.
*
* Note that this low-level API is considered advanced usage.
* Most use cases should probably use the higher-level
* [readable Stream API](https://github.com/reactphp/stream#readablestreaminterface)
* instead.
*
* The first parameter MUST be a valid stream resource that supports
* checking whether it is ready to read by this loop implementation.
* A single stream resource MUST NOT be added more than once.
* Instead, either call [`removeReadStream()`](#removereadstream) first or
* react to this event with a single listener and then dispatch from this
* listener. This method MAY throw an `Exception` if the given resource type
* is not supported by this loop implementation.
*
* The second parameter MUST be a listener callback function that accepts
* the stream resource as its only parameter.
* If you don't use the stream resource inside your listener callback function
* you MAY use a function which has no parameters at all.
*
* The listener callback function MUST NOT throw an `Exception`.
* The return value of the listener callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* If you want to access any variables within your callback function, you
* can bind arbitrary data to a callback closure like this:
*
* ```php
* $loop->addReadStream($stream, function ($stream) use ($name) {
* echo $name . ' said: ' . fread($stream);
* });
* ```
*
* See also [example #11](examples).
*
* You can invoke [`removeReadStream()`](#removereadstream) to remove the
* read event listener for this stream.
*
* The execution order of listeners when multiple streams become ready at
* the same time is not guaranteed.
*
* @param resource $stream The PHP stream resource to check.
* @param callable $listener Invoked when the stream is ready.
* @throws \Exception if the given resource type is not supported by this loop implementation
* @see self::removeReadStream()
*/
public function addReadStream($stream, $listener);
/**
* [Advanced] Register a listener to be notified when a stream is ready to write.
*
* Note that this low-level API is considered advanced usage.
* Most use cases should probably use the higher-level
* [writable Stream API](https://github.com/reactphp/stream#writablestreaminterface)
* instead.
*
* The first parameter MUST be a valid stream resource that supports
* checking whether it is ready to write by this loop implementation.
* A single stream resource MUST NOT be added more than once.
* Instead, either call [`removeWriteStream()`](#removewritestream) first or
* react to this event with a single listener and then dispatch from this
* listener. This method MAY throw an `Exception` if the given resource type
* is not supported by this loop implementation.
*
* The second parameter MUST be a listener callback function that accepts
* the stream resource as its only parameter.
* If you don't use the stream resource inside your listener callback function
* you MAY use a function which has no parameters at all.
*
* The listener callback function MUST NOT throw an `Exception`.
* The return value of the listener callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* If you want to access any variables within your callback function, you
* can bind arbitrary data to a callback closure like this:
*
* ```php
* $loop->addWriteStream($stream, function ($stream) use ($name) {
* fwrite($stream, 'Hello ' . $name);
* });
* ```
*
* See also [example #12](examples).
*
* You can invoke [`removeWriteStream()`](#removewritestream) to remove the
* write event listener for this stream.
*
* The execution order of listeners when multiple streams become ready at
* the same time is not guaranteed.
*
* Some event loop implementations are known to only trigger the listener if
* the stream *becomes* readable (edge-triggered) and may not trigger if the
* stream has already been readable from the beginning.
* This also implies that a stream may not be recognized as readable when data
* is still left in PHP's internal stream buffers.
* As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
* to disable PHP's internal read buffer in this case.
*
* @param resource $stream The PHP stream resource to check.
* @param callable $listener Invoked when the stream is ready.
* @throws \Exception if the given resource type is not supported by this loop implementation
* @see self::removeWriteStream()
*/
public function addWriteStream($stream, $listener);
/**
* Remove the read event listener for the given stream.
*
* Removing a stream from the loop that has already been removed or trying
* to remove a stream that was never added or is invalid has no effect.
*
* @param resource $stream The PHP stream resource.
*/
public function removeReadStream($stream);
/**
* Remove the write event listener for the given stream.
*
* Removing a stream from the loop that has already been removed or trying
* to remove a stream that was never added or is invalid has no effect.
*
* @param resource $stream The PHP stream resource.
*/
public function removeWriteStream($stream);
/**
* Enqueue a callback to be invoked once after the given interval.
*
* The second parameter MUST be a timer callback function that accepts
* the timer instance as its only parameter.
* If you don't use the timer instance inside your timer callback function
* you MAY use a function which has no parameters at all.
*
* The timer callback function MUST NOT throw an `Exception`.
* The return value of the timer callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* This method returns a timer instance. The same timer instance will also be
* passed into the timer callback function as described above.
* You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer.
* Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure
* the callback will be invoked only once after the given interval.
*
* ```php
* $loop->addTimer(0.8, function () {
* echo 'world!' . PHP_EOL;
* });
*
* $loop->addTimer(0.3, function () {
* echo 'hello ';
* });
* ```
*
* See also [example #1](examples).
*
* If you want to access any variables within your callback function, you
* can bind arbitrary data to a callback closure like this:
*
* ```php
* function hello($name, LoopInterface $loop)
* {
* $loop->addTimer(1.0, function () use ($name) {
* echo "hello $name\n";
* });
* }
*
* hello('Tester', $loop);
* ```
*
* This interface does not enforce any particular timer resolution, so
* special care may have to be taken if you rely on very high precision with
* millisecond accuracy or below. Event loop implementations SHOULD work on
* a best effort basis and SHOULD provide at least millisecond accuracy
* unless otherwise noted. Many existing event loop implementations are
* known to provide microsecond accuracy, but it's generally not recommended
* to rely on this high precision.
*
* Similarly, the execution order of timers scheduled to execute at the
* same time (within its possible accuracy) is not guaranteed.
*
* This interface suggests that event loop implementations SHOULD use a
* monotonic time source if available. Given that a monotonic time source is
* only available as of PHP 7.3 by default, event loop implementations MAY
* fall back to using wall-clock time.
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you schedule a timer to trigger in 30s and then adjust
* your system time forward by 20s, the timer SHOULD still trigger in 30s.
* See also [event loop implementations](#loop-implementations) for more details.
*
* @param int|float $interval The number of seconds to wait before execution.
* @param callable $callback The callback to invoke.
*
* @return TimerInterface
*/
public function addTimer($interval, $callback);
/**
* Enqueue a callback to be invoked repeatedly after the given interval.
*
* The second parameter MUST be a timer callback function that accepts
* the timer instance as its only parameter.
* If you don't use the timer instance inside your timer callback function
* you MAY use a function which has no parameters at all.
*
* The timer callback function MUST NOT throw an `Exception`.
* The return value of the timer callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* This method returns a timer instance. The same timer instance will also be
* passed into the timer callback function as described above.
* Unlike [`addTimer()`](#addtimer), this method will ensure the callback
* will be invoked infinitely after the given interval or until you invoke
* [`cancelTimer`](#canceltimer).
*
* ```php
* $timer = $loop->addPeriodicTimer(0.1, function () {
* echo 'tick!' . PHP_EOL;
* });
*
* $loop->addTimer(1.0, function () use ($loop, $timer) {
* $loop->cancelTimer($timer);
* echo 'Done' . PHP_EOL;
* });
* ```
*
* See also [example #2](examples).
*
* If you want to limit the number of executions, you can bind
* arbitrary data to a callback closure like this:
*
* ```php
* function hello($name, LoopInterface $loop)
* {
* $n = 3;
* $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) {
* if ($n > 0) {
* --$n;
* echo "hello $name\n";
* } else {
* $loop->cancelTimer($timer);
* }
* });
* }
*
* hello('Tester', $loop);
* ```
*
* This interface does not enforce any particular timer resolution, so
* special care may have to be taken if you rely on very high precision with
* millisecond accuracy or below. Event loop implementations SHOULD work on
* a best effort basis and SHOULD provide at least millisecond accuracy
* unless otherwise noted. Many existing event loop implementations are
* known to provide microsecond accuracy, but it's generally not recommended
* to rely on this high precision.
*
* Similarly, the execution order of timers scheduled to execute at the
* same time (within its possible accuracy) is not guaranteed.
*
* This interface suggests that event loop implementations SHOULD use a
* monotonic time source if available. Given that a monotonic time source is
* only available as of PHP 7.3 by default, event loop implementations MAY
* fall back to using wall-clock time.
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you schedule a timer to trigger in 30s and then adjust
* your system time forward by 20s, the timer SHOULD still trigger in 30s.
* See also [event loop implementations](#loop-implementations) for more details.
*
* Additionally, periodic timers may be subject to timer drift due to
* re-scheduling after each invocation. As such, it's generally not
* recommended to rely on this for high precision intervals with millisecond
* accuracy or below.
*
* @param int|float $interval The number of seconds to wait before execution.
* @param callable $callback The callback to invoke.
*
* @return TimerInterface
*/
public function addPeriodicTimer($interval, $callback);
/**
* Cancel a pending timer.
*
* See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples).
*
* Calling this method on a timer instance that has not been added to this
* loop instance or on a timer that has already been cancelled has no effect.
*
* @param TimerInterface $timer The timer to cancel.
*
* @return void
*/
public function cancelTimer(TimerInterface $timer);
/**
* Schedule a callback to be invoked on a future tick of the event loop.
*
* This works very much similar to timers with an interval of zero seconds,
* but does not require the overhead of scheduling a timer queue.
*
* The tick callback function MUST be able to accept zero parameters.
*
* The tick callback function MUST NOT throw an `Exception`.
* The return value of the tick callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* If you want to access any variables within your callback function, you
* can bind arbitrary data to a callback closure like this:
*
* ```php
* function hello($name, LoopInterface $loop)
* {
* $loop->futureTick(function () use ($name) {
* echo "hello $name\n";
* });
* }
*
* hello('Tester', $loop);
* ```
*
* Unlike timers, tick callbacks are guaranteed to be executed in the order
* they are enqueued.
* Also, once a callback is enqueued, there's no way to cancel this operation.
*
* This is often used to break down bigger tasks into smaller steps (a form
* of cooperative multitasking).
*
* ```php
* $loop->futureTick(function () {
* echo 'b';
* });
* $loop->futureTick(function () {
* echo 'c';
* });
* echo 'a';
* ```
*
* See also [example #3](examples).
*
* @param callable $listener The callback to invoke.
*
* @return void
*/
public function futureTick($listener);
/**
* Register a listener to be notified when a signal has been caught by this process.
*
* This is useful to catch user interrupt signals or shutdown signals from
* tools like `supervisor` or `systemd`.
*
* The second parameter MUST be a listener callback function that accepts
* the signal as its only parameter.
* If you don't use the signal inside your listener callback function
* you MAY use a function which has no parameters at all.
*
* The listener callback function MUST NOT throw an `Exception`.
* The return value of the listener callback function will be ignored and has
* no effect, so for performance reasons you're recommended to not return
* any excessive data structures.
*
* ```php
* $loop->addSignal(SIGINT, function (int $signal) {
* echo 'Caught user interrupt signal' . PHP_EOL;
* });
* ```
*
* See also [example #4](examples).
*
* Signaling is only available on Unix-like platforms, Windows isn't
* supported due to operating system limitations.
* This method may throw a `BadMethodCallException` if signals aren't
* supported on this platform, for example when required extensions are
* missing.
*
* **Note: A listener can only be added once to the same signal, any
* attempts to add it more than once will be ignored.**
*
* @param int $signal
* @param callable $listener
*
* @throws \BadMethodCallException when signals aren't supported on this
* platform, for example when required extensions are missing.
*
* @return void
*/
public function addSignal($signal, $listener);
/**
* Removes a previously added signal listener.
*
* ```php
* $loop->removeSignal(SIGINT, $listener);
* ```
*
* Any attempts to remove listeners that aren't registered will be ignored.
*
* @param int $signal
* @param callable $listener
*
* @return void
*/
public function removeSignal($signal, $listener);
/**
* Run the event loop until there are no more tasks to perform.
*
* For many applications, this method is the only directly visible
* invocation on the event loop.
* As a rule of thumb, it is usually recommended to attach everything to the
* same loop instance and then run the loop once at the bottom end of the
* application.
*
* ```php
* $loop->run();
* ```
*
* This method will keep the loop running until there are no more tasks
* to perform. In other words: This method will block until the last
* timer, stream and/or signal has been removed.
*
* Likewise, it is imperative to ensure the application actually invokes
* this method once. Adding listeners to the loop and missing to actually
* run it will result in the application exiting without actually waiting
* for any of the attached listeners.
*
* This method MUST NOT be called while the loop is already running.
* This method MAY be called more than once after it has explicitly been
* [`stop()`ped](#stop) or after it automatically stopped because it
* previously did no longer have anything to do.
*
* @return void
*/
public function run();
/**
* Instruct a running event loop to stop.
*
* This method is considered advanced usage and should be used with care.
* As a rule of thumb, it is usually recommended to let the loop stop
* only automatically when it no longer has anything to do.
*
* This method can be used to explicitly instruct the event loop to stop:
*
* ```php
* $loop->addTimer(3.0, function () use ($loop) {
* $loop->stop();
* });
* ```
*
* Calling this method on a loop instance that is not currently running or
* on a loop instance that has already been stopped has no effect.
*
* @return void
*/
public function stop();
}

View File

@@ -0,0 +1,63 @@
<?php
namespace React\EventLoop;
/**
* @internal
*/
final class SignalsHandler
{
private $signals = array();
public function add($signal, $listener)
{
if (!isset($this->signals[$signal])) {
$this->signals[$signal] = array();
}
if (\in_array($listener, $this->signals[$signal])) {
return;
}
$this->signals[$signal][] = $listener;
}
public function remove($signal, $listener)
{
if (!isset($this->signals[$signal])) {
return;
}
$index = \array_search($listener, $this->signals[$signal], true);
unset($this->signals[$signal][$index]);
if (isset($this->signals[$signal]) && \count($this->signals[$signal]) === 0) {
unset($this->signals[$signal]);
}
}
public function call($signal)
{
if (!isset($this->signals[$signal])) {
return;
}
foreach ($this->signals[$signal] as $listener) {
\call_user_func($listener, $signal);
}
}
public function count($signal)
{
if (!isset($this->signals[$signal])) {
return 0;
}
return \count($this->signals[$signal]);
}
public function isEmpty()
{
return !$this->signals;
}
}

View File

@@ -0,0 +1,330 @@
<?php
namespace React\EventLoop;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\Timers;
/**
* A `stream_select()` based event loop.
*
* This uses the [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
* function and is the only implementation that works out of the box with PHP.
*
* This event loop works out of the box on PHP 5.4 through PHP 8+ and HHVM.
* This means that no installation is required and this library works on all
* platforms and supported PHP versions.
* Accordingly, the [`Loop` class](#loop) and the deprecated [`Factory`](#factory)
* will use this event loop by default if you do not install any of the event loop
* extensions listed below.
*
* Under the hood, it does a simple `select` system call.
* This system call is limited to the maximum file descriptor number of
* `FD_SETSIZE` (platform dependent, commonly 1024) and scales with `O(m)`
* (`m` being the maximum file descriptor number passed).
* This means that you may run into issues when handling thousands of streams
* concurrently and you may want to look into using one of the alternative
* event loop implementations listed below in this case.
* If your use case is among the many common use cases that involve handling only
* dozens or a few hundred streams at once, then this event loop implementation
* performs really well.
*
* If you want to use signal handling (see also [`addSignal()`](#addsignal) below),
* this event loop implementation requires `ext-pcntl`.
* This extension is only available for Unix-like platforms and does not support
* Windows.
* It is commonly installed as part of many PHP distributions.
* If this extension is missing (or you're running on Windows), signal handling is
* not supported and throws a `BadMethodCallException` instead.
*
* This event loop is known to rely on wall-clock time to schedule future timers
* when using any version before PHP 7.3, because a monotonic time source is
* only available as of PHP 7.3 (`hrtime()`).
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you schedule a timer to trigger in 30s on PHP < 7.3 and
* then adjust your system time forward by 20s, the timer may trigger in 10s.
* See also [`addTimer()`](#addtimer) for more details.
*
* @link https://www.php.net/manual/en/function.stream-select.php
*/
final class StreamSelectLoop implements LoopInterface
{
/** @internal */
const MICROSECONDS_PER_SECOND = 1000000;
private $futureTickQueue;
private $timers;
private $readStreams = array();
private $readListeners = array();
private $writeStreams = array();
private $writeListeners = array();
private $running;
private $pcntl = false;
private $pcntlPoll = false;
private $signals;
public function __construct()
{
$this->futureTickQueue = new FutureTickQueue();
$this->timers = new Timers();
$this->pcntl = \function_exists('pcntl_signal') && \function_exists('pcntl_signal_dispatch');
$this->pcntlPoll = $this->pcntl && !\function_exists('pcntl_async_signals');
$this->signals = new SignalsHandler();
// prefer async signals if available (PHP 7.1+) or fall back to dispatching on each tick
if ($this->pcntl && !$this->pcntlPoll) {
\pcntl_async_signals(true);
}
}
public function addReadStream($stream, $listener)
{
$key = (int) $stream;
if (!isset($this->readStreams[$key])) {
$this->readStreams[$key] = $stream;
$this->readListeners[$key] = $listener;
}
}
public function addWriteStream($stream, $listener)
{
$key = (int) $stream;
if (!isset($this->writeStreams[$key])) {
$this->writeStreams[$key] = $stream;
$this->writeListeners[$key] = $listener;
}
}
public function removeReadStream($stream)
{
$key = (int) $stream;
unset(
$this->readStreams[$key],
$this->readListeners[$key]
);
}
public function removeWriteStream($stream)
{
$key = (int) $stream;
unset(
$this->writeStreams[$key],
$this->writeListeners[$key]
);
}
public function addTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, false);
$this->timers->add($timer);
return $timer;
}
public function addPeriodicTimer($interval, $callback)
{
$timer = new Timer($interval, $callback, true);
$this->timers->add($timer);
return $timer;
}
public function cancelTimer(TimerInterface $timer)
{
$this->timers->cancel($timer);
}
public function futureTick($listener)
{
$this->futureTickQueue->add($listener);
}
public function addSignal($signal, $listener)
{
if ($this->pcntl === false) {
throw new \BadMethodCallException('Event loop feature "signals" isn\'t supported by the "StreamSelectLoop"');
}
$first = $this->signals->count($signal) === 0;
$this->signals->add($signal, $listener);
if ($first) {
\pcntl_signal($signal, array($this->signals, 'call'));
}
}
public function removeSignal($signal, $listener)
{
if (!$this->signals->count($signal)) {
return;
}
$this->signals->remove($signal, $listener);
if ($this->signals->count($signal) === 0) {
\pcntl_signal($signal, \SIG_DFL);
}
}
public function run()
{
$this->running = true;
while ($this->running) {
$this->futureTickQueue->tick();
$this->timers->tick();
// Future-tick queue has pending callbacks ...
if (!$this->running || !$this->futureTickQueue->isEmpty()) {
$timeout = 0;
// There is a pending timer, only block until it is due ...
} elseif ($scheduledAt = $this->timers->getFirst()) {
$timeout = $scheduledAt - $this->timers->getTime();
if ($timeout < 0) {
$timeout = 0;
} else {
// Convert float seconds to int microseconds.
// Ensure we do not exceed maximum integer size, which may
// cause the loop to tick once every ~35min on 32bit systems.
$timeout *= self::MICROSECONDS_PER_SECOND;
$timeout = $timeout > \PHP_INT_MAX ? \PHP_INT_MAX : (int)$timeout;
}
// The only possible event is stream or signal activity, so wait forever ...
} elseif ($this->readStreams || $this->writeStreams || !$this->signals->isEmpty()) {
$timeout = null;
// There's nothing left to do ...
} else {
break;
}
$this->waitForStreamActivity($timeout);
}
}
public function stop()
{
$this->running = false;
}
/**
* Wait/check for stream activity, or until the next timer is due.
*
* @param integer|null $timeout Activity timeout in microseconds, or null to wait forever.
*/
private function waitForStreamActivity($timeout)
{
$read = $this->readStreams;
$write = $this->writeStreams;
$available = $this->streamSelect($read, $write, $timeout);
if ($this->pcntlPoll) {
\pcntl_signal_dispatch();
}
if (false === $available) {
// if a system call has been interrupted,
// we cannot rely on it's outcome
return;
}
foreach ($read as $stream) {
$key = (int) $stream;
if (isset($this->readListeners[$key])) {
\call_user_func($this->readListeners[$key], $stream);
}
}
foreach ($write as $stream) {
$key = (int) $stream;
if (isset($this->writeListeners[$key])) {
\call_user_func($this->writeListeners[$key], $stream);
}
}
}
/**
* Emulate a stream_select() implementation that does not break when passed
* empty stream arrays.
*
* @param array $read An array of read streams to select upon.
* @param array $write An array of write streams to select upon.
* @param int|null $timeout Activity timeout in microseconds, or null to wait forever.
*
* @return int|false The total number of streams that are ready for read/write.
* Can return false if stream_select() is interrupted by a signal.
*/
private function streamSelect(array &$read, array &$write, $timeout)
{
if ($read || $write) {
// We do not usually use or expose the `exceptfds` parameter passed to the underlying `select`.
// However, Windows does not report failed connection attempts in `writefds` passed to `select` like most other platforms.
// Instead, it uses `writefds` only for successful connection attempts and `exceptfds` for failed connection attempts.
// We work around this by adding all sockets that look like a pending connection attempt to `exceptfds` automatically on Windows and merge it back later.
// This ensures the public API matches other loop implementations across all platforms (see also test suite or rather test matrix).
// Lacking better APIs, every write-only socket that has not yet read any data is assumed to be in a pending connection attempt state.
// @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select
$except = null;
if (\DIRECTORY_SEPARATOR === '\\') {
$except = array();
foreach ($write as $key => $socket) {
if (!isset($read[$key]) && @\ftell($socket) === 0) {
$except[$key] = $socket;
}
}
}
/** @var ?callable $previous */
$previous = \set_error_handler(function ($errno, $errstr) use (&$previous) {
// suppress warnings that occur when `stream_select()` is interrupted by a signal
// PHP defines `EINTR` through `ext-sockets` or `ext-pcntl`, otherwise use common default (Linux & Mac)
$eintr = \defined('SOCKET_EINTR') ? \SOCKET_EINTR : (\defined('PCNTL_EINTR') ? \PCNTL_EINTR : 4);
if ($errno === \E_WARNING && \strpos($errstr, '[' . $eintr .']: ') !== false) {
return;
}
// forward any other error to registered error handler or print warning
return ($previous !== null) ? \call_user_func_array($previous, \func_get_args()) : false;
});
try {
$ret = \stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout);
\restore_error_handler();
} catch (\Throwable $e) { // @codeCoverageIgnoreStart
\restore_error_handler();
throw $e;
} catch (\Exception $e) {
\restore_error_handler();
throw $e;
} // @codeCoverageIgnoreEnd
if ($except) {
$write = \array_merge($write, $except);
}
return $ret;
}
if ($timeout > 0) {
\usleep($timeout);
} elseif ($timeout === null) {
// wait forever (we only reach this if we're only awaiting signals)
// this may be interrupted and return earlier when a signal is received
\sleep(PHP_INT_MAX);
}
return 0;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace React\EventLoop\Tick;
use SplQueue;
/**
* A tick queue implementation that can hold multiple callback functions
*
* This class should only be used internally, see LoopInterface instead.
*
* @see LoopInterface
* @internal
*/
final class FutureTickQueue
{
private $queue;
public function __construct()
{
$this->queue = new SplQueue();
}
/**
* Add a callback to be invoked on a future tick of the event loop.
*
* Callbacks are guaranteed to be executed in the order they are enqueued.
*
* @param callable $listener The callback to invoke.
*/
public function add($listener)
{
$this->queue->enqueue($listener);
}
/**
* Flush the callback queue.
*/
public function tick()
{
// Only invoke as many callbacks as were on the queue when tick() was called.
$count = $this->queue->count();
while ($count--) {
\call_user_func(
$this->queue->dequeue()
);
}
}
/**
* Check if the next tick queue is empty.
*
* @return boolean
*/
public function isEmpty()
{
return $this->queue->isEmpty();
}
}

55
vendor/react/event-loop/src/Timer/Timer.php vendored Executable file
View File

@@ -0,0 +1,55 @@
<?php
namespace React\EventLoop\Timer;
use React\EventLoop\TimerInterface;
/**
* The actual connection implementation for TimerInterface
*
* This class should only be used internally, see TimerInterface instead.
*
* @see TimerInterface
* @internal
*/
final class Timer implements TimerInterface
{
const MIN_INTERVAL = 0.000001;
private $interval;
private $callback;
private $periodic;
/**
* Constructor initializes the fields of the Timer
*
* @param float $interval The interval after which this timer will execute, in seconds
* @param callable $callback The callback that will be executed when this timer elapses
* @param bool $periodic Whether the time is periodic
*/
public function __construct($interval, $callback, $periodic = false)
{
if ($interval < self::MIN_INTERVAL) {
$interval = self::MIN_INTERVAL;
}
$this->interval = (float) $interval;
$this->callback = $callback;
$this->periodic = (bool) $periodic;
}
public function getInterval()
{
return $this->interval;
}
public function getCallback()
{
return $this->callback;
}
public function isPeriodic()
{
return $this->periodic;
}
}

113
vendor/react/event-loop/src/Timer/Timers.php vendored Executable file
View File

@@ -0,0 +1,113 @@
<?php
namespace React\EventLoop\Timer;
use React\EventLoop\TimerInterface;
/**
* A scheduler implementation that can hold multiple timer instances
*
* This class should only be used internally, see TimerInterface instead.
*
* @see TimerInterface
* @internal
*/
final class Timers
{
private $time;
private $timers = array();
private $schedule = array();
private $sorted = true;
private $useHighResolution;
public function __construct()
{
// prefer high-resolution timer, available as of PHP 7.3+
$this->useHighResolution = \function_exists('hrtime');
}
public function updateTime()
{
return $this->time = $this->useHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
}
public function getTime()
{
return $this->time ?: $this->updateTime();
}
public function add(TimerInterface $timer)
{
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
$this->timers[$id] = $timer;
$this->schedule[$id] = $timer->getInterval() + $this->updateTime();
$this->sorted = false;
}
public function contains(TimerInterface $timer)
{
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
return isset($this->timers[$id]);
}
public function cancel(TimerInterface $timer)
{
$id = \PHP_VERSION_ID < 70200 ? \spl_object_hash($timer) : \spl_object_id($timer);
unset($this->timers[$id], $this->schedule[$id]);
}
public function getFirst()
{
// ensure timers are sorted to simply accessing next (first) one
if (!$this->sorted) {
$this->sorted = true;
\asort($this->schedule);
}
return \reset($this->schedule);
}
public function isEmpty()
{
return \count($this->timers) === 0;
}
public function tick()
{
// hot path: skip timers if nothing is scheduled
if (!$this->schedule) {
return;
}
// ensure timers are sorted so we can execute in order
if (!$this->sorted) {
$this->sorted = true;
\asort($this->schedule);
}
$time = $this->updateTime();
foreach ($this->schedule as $id => $scheduled) {
// schedule is ordered, so loop until first timer that is not scheduled for execution now
if ($scheduled >= $time) {
break;
}
// skip any timers that are removed while we process the current schedule
if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) {
continue;
}
$timer = $this->timers[$id];
\call_user_func($timer->getCallback(), $timer);
// re-schedule if this is a periodic timer and it has not been cancelled explicitly already
if ($timer->isPeriodic() && isset($this->timers[$id])) {
$this->schedule[$id] = $timer->getInterval() + $time;
$this->sorted = false;
} else {
unset($this->timers[$id], $this->schedule[$id]);
}
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace React\EventLoop;
interface TimerInterface
{
/**
* Get the interval after which this timer will execute, in seconds
*
* @return float
*/
public function getInterval();
/**
* Get the callback that will be executed when this timer elapses
*
* @return callable
*/
public function getCallback();
/**
* Determine whether the time is periodic
*
* @return bool
*/
public function isPeriodic();
}

156
vendor/react/promise/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,156 @@
# Changelog
## 3.2.0 (2024-05-24)
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
(#260 by @Ayesh)
* Feature: Include previous exceptions when reporting unhandled promise rejections.
(#262 by @clue)
* Update test suite to improve PHP 8.4+ support.
(#261 by @SimonFrings)
## 3.1.0 (2023-11-16)
* Feature: Full PHP 8.3 compatibility.
(#255 by @clue)
* Feature: Describe all callable arguments with types for `Promise` and `Deferred`.
(#253 by @clue)
* Update test suite and minor documentation improvements.
(#251 by @ondrejmirtes and #250 by @SQKo)
## 3.0.0 (2023-07-11)
A major new feature release, see [**release announcement**](https://clue.engineering/2023/announcing-reactphp-promise-v3).
* We'd like to emphasize that this component is production ready and battle-tested.
We plan to support all long-term support (LTS) releases for at least 24 months,
so you have a rock-solid foundation to build on top of.
* The v3 release will be the way forward for this package. However, we will still
actively support v2 and v1 to provide a smooth upgrade path for those not yet
on the latest versions.
This update involves some major new features and a minor BC break over the
`v2.0.0` release. We've tried hard to avoid BC breaks where possible and
minimize impact otherwise. We expect that most consumers of this package will be
affected by BC breaks, but updating should take no longer than a few minutes.
See below for more details:
* BC break: PHP 8.1+ recommended, PHP 7.1+ required.
(#138 and #149 by @WyriHaximus)
* Feature / BC break: The `PromiseInterface` now includes the functionality of the old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~.
Each promise now always includes the `then()`, `catch()`, `finally()` and `cancel()` methods.
The new `catch()` and `finally()` methods replace the deprecated ~~`otherwise()`~~ and ~~`always()`~~ methods which continue to exist for BC reasons.
The old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~ are no longer needed and have been removed as a consequence.
(#75 by @jsor and #208 by @clue and @WyriHaximus)
```php
// old (multiple interfaces may or may not be implemented)
assert($promise instanceof PromiseInterface);
assert(method_exists($promise, 'then'));
if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'otherwise')); }
if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'always')); }
if ($promise instanceof CancellablePromiseInterface) { assert(method_exists($promise, 'cancel')); }
// new (single PromiseInterface with all methods)
assert($promise instanceof PromiseInterface);
assert(method_exists($promise, 'then'));
assert(method_exists($promise, 'catch'));
assert(method_exists($promise, 'finally'));
assert(method_exists($promise, 'cancel'));
```
* Feature / BC break: Improve type safety of promises. Require `mixed` fulfillment value argument and `Throwable` (or `Exception`) as rejection reason.
Add PHPStan template types to ensure strict types for `resolve(T $value): PromiseInterface<T>` and `reject(Throwable $reason): PromiseInterface<never>`.
It is no longer possible to resolve a promise without a value (use `null` instead) or reject a promise without a reason (use `Throwable` instead).
(#93, #141 and #142 by @jsor, #138, #149 and #247 by @WyriHaximus and #213 and #246 by @clue)
```php
// old (arguments used to be optional)
$promise = resolve();
$promise = reject();
// new (already supported before)
$promise = resolve(null);
$promise = reject(new RuntimeException());
```
* Feature / BC break: Report all unhandled rejections by default and remove ~~`done()`~~ method.
Add new `set_rejection_handler()` function to set the global rejection handler for unhandled promise rejections.
(#248, #249 and #224 by @clue)
```php
// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
reject(new RuntimeException('Unhandled'));
```
* BC break: Remove all deprecated APIs and reduce API surface.
Remove ~~`some()`~~, ~~`map()`~~, ~~`reduce()`~~ functions, use `any()` and `all()` functions instead.
Remove internal ~~`FulfilledPromise`~~ and ~~`RejectedPromise`~~ classes, use `resolve()` and `reject()` functions instead.
Remove legacy promise progress API (deprecated third argument to `then()` method) and deprecated ~~`LazyPromise`~~ class.
(#32 and #98 by @jsor and #164, #219 and #220 by @clue)
* BC break: Make all classes final to encourage composition over inheritance.
(#80 by @jsor)
* Feature / BC break: Require `array` (or `iterable`) type for `all()` + `race()` + `any()` functions and bring in line with ES6 specification.
These functions now require a single argument with a variable number of promises or values as input.
(#225 by @clue and #35 by @jsor)
* Fix / BC break: Fix `race()` to return a forever pending promise when called with an empty `array` (or `iterable`) and bring in line with ES6 specification.
(#83 by @jsor and #225 by @clue)
* Minor performance improvements by initializing `Deferred` in the constructor and avoiding `call_user_func()` calls.
(#151 by @WyriHaximus and #171 by @Kubo2)
* Minor documentation improvements.
(#110 by @seregazhuk, #132 by @CharlotteDunois, #145 by @danielecr, #178 by @WyriHaximus, #189 by @srdante, #212 by @clue, #214, #239 and #243 by @SimonFrings and #231 by @nhedger)
The following changes had to be ported to this release due to our branching
strategy, but also appeared in the [`2.x` branch](https://github.com/reactphp/promise/tree/2.x):
* Feature: Support union types and address deprecation of `ReflectionType::getClass()` (PHP 8+).
(#197 by @cdosoftei and @SimonFrings)
* Feature: Support intersection types (PHP 8.1+).
(#209 by @bzikarsky)
* Feature: Support DNS types (PHP 8.2+).
(#236 by @nhedger)
* Feature: Port all memory improvements from `2.x` to `3.x`.
(#150 by @clue and @WyriHaximus)
* Fix: Fix checking whether cancellable promise is an object and avoid possible warning.
(#161 by @smscr)
* Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
(#134 by @WyriHaximus)
* Improve test suite, update PHPUnit and PHP versions and add `.gitattributes` to exclude dev files from exports.
(#107 by @carusogabriel, #148 and #234 by @WyriHaximus, #153 by @reedy, #162, #230 and #240 by @clue, #173, #177, #185 and #199 by @SimonFrings, #193 by @woodongwong and #210 by @bzikarsky)
The following changes were originally planned for this release but later reverted
and are not part of the final release:
* Add iterative callback queue handler to avoid recursion (later removed to improve Fiber support).
(#28, #82 and #86 by @jsor, #158 by @WyriHaximus and #229 and #238 by @clue)
* Trigger an `E_USER_ERROR` instead of throwing an exception from `done()` (later removed entire `done()` method to globally report unhandled rejections).
(#97 by @jsor and #224 and #248 by @clue)
* Add type declarations for `some()` (later removed entire `some()` function).
(#172 by @WyriHaximus and #219 by @clue)
## 2.0.0 (2013-12-10)
See [`2.x` CHANGELOG](https://github.com/reactphp/promise/blob/2.x/CHANGELOG.md) for more details.
## 1.0.0 (2012-11-07)
See [`1.x` CHANGELOG](https://github.com/reactphp/promise/blob/1.x/CHANGELOG.md) for more details.

24
vendor/react/promise/LICENSE vendored Executable file
View File

@@ -0,0 +1,24 @@
The MIT License (MIT)
Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

722
vendor/react/promise/README.md vendored Executable file
View File

@@ -0,0 +1,722 @@
Promise
=======
A lightweight implementation of
[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
[![CI status](https://github.com/reactphp/promise/workflows/CI/badge.svg)](https://github.com/reactphp/promise/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/promise?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/promise)
Table of Contents
-----------------
1. [Introduction](#introduction)
2. [Concepts](#concepts)
* [Deferred](#deferred)
* [Promise](#promise-1)
3. [API](#api)
* [Deferred](#deferred-1)
* [Deferred::promise()](#deferredpromise)
* [Deferred::resolve()](#deferredresolve)
* [Deferred::reject()](#deferredreject)
* [PromiseInterface](#promiseinterface)
* [PromiseInterface::then()](#promiseinterfacethen)
* [PromiseInterface::catch()](#promiseinterfacecatch)
* [PromiseInterface::finally()](#promiseinterfacefinally)
* [PromiseInterface::cancel()](#promiseinterfacecancel)
* [~~PromiseInterface::otherwise()~~](#promiseinterfaceotherwise)
* [~~PromiseInterface::always()~~](#promiseinterfacealways)
* [Promise](#promise-2)
* [Functions](#functions)
* [resolve()](#resolve)
* [reject()](#reject)
* [all()](#all)
* [race()](#race)
* [any()](#any)
* [set_rejection_handler()](#set_rejection_handler)
4. [Examples](#examples)
* [How to use Deferred](#how-to-use-deferred)
* [How promise forwarding works](#how-promise-forwarding-works)
* [Resolution forwarding](#resolution-forwarding)
* [Rejection forwarding](#rejection-forwarding)
* [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
5. [Install](#install)
6. [Tests](#tests)
7. [Credits](#credits)
8. [License](#license)
Introduction
------------
Promise is a library implementing
[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
It also provides several other useful promise-related concepts, such as joining
multiple promises and mapping and reducing collections of promises.
If you've never heard about promises before,
[read this first](https://gist.github.com/domenic/3889970).
Concepts
--------
### Deferred
A **Deferred** represents a computation or unit of work that may not have
completed yet. Typically (but not always), that computation will be something
that executes asynchronously and completes at some point in the future.
### Promise
While a deferred represents the computation itself, a **Promise** represents
the result of that computation. Thus, each deferred has a promise that acts as
a placeholder for its actual result.
API
---
### Deferred
A deferred represents an operation whose resolution is pending. It has separate
promise and resolver parts.
```php
$deferred = new React\Promise\Deferred();
$promise = $deferred->promise();
$deferred->resolve(mixed $value);
$deferred->reject(\Throwable $reason);
```
The `promise` method returns the promise of the deferred.
The `resolve` and `reject` methods control the state of the deferred.
The constructor of the `Deferred` accepts an optional `$canceller` argument.
See [Promise](#promise-2) for more information.
#### Deferred::promise()
```php
$promise = $deferred->promise();
```
Returns the promise of the deferred, which you can hand out to others while
keeping the authority to modify its state to yourself.
#### Deferred::resolve()
```php
$deferred->resolve(mixed $value);
```
Resolves the promise returned by `promise()`. All consumers are notified by
having `$onFulfilled` (which they registered via `$promise->then()`) called with
`$value`.
If `$value` itself is a promise, the promise will transition to the state of
this promise once it is resolved.
See also the [`resolve()` function](#resolve).
#### Deferred::reject()
```php
$deferred->reject(\Throwable $reason);
```
Rejects the promise returned by `promise()`, signalling that the deferred's
computation failed.
All consumers are notified by having `$onRejected` (which they registered via
`$promise->then()`) called with `$reason`.
See also the [`reject()` function](#reject).
### PromiseInterface
The promise interface provides the common interface for all promise
implementations.
See [Promise](#promise-2) for the only public implementation exposed by this
package.
A promise represents an eventual outcome, which is either fulfillment (success)
and an associated value, or rejection (failure) and an associated reason.
Once in the fulfilled or rejected state, a promise becomes immutable.
Neither its state nor its result (or error) can be modified.
#### PromiseInterface::then()
```php
$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null);
```
Transforms a promise's value by applying a function to the promise's fulfillment
or rejection value. Returns a new promise for the transformed result.
The `then()` method registers new fulfilled and rejection handlers with a promise
(all parameters are optional):
* `$onFulfilled` will be invoked once the promise is fulfilled and passed
the result as the first argument.
* `$onRejected` will be invoked once the promise is rejected and passed the
reason as the first argument.
It returns a new promise that will fulfill with the return value of either
`$onFulfilled` or `$onRejected`, whichever is called, or will reject with
the thrown exception if either throws.
A promise makes the following guarantees about handlers registered in
the same call to `then()`:
1. Only one of `$onFulfilled` or `$onRejected` will be called,
never both.
2. `$onFulfilled` and `$onRejected` will never be called more
than once.
#### See also
* [resolve()](#resolve) - Creating a resolved promise
* [reject()](#reject) - Creating a rejected promise
#### PromiseInterface::catch()
```php
$promise->catch(callable $onRejected);
```
Registers a rejection handler for promise. It is a shortcut for:
```php
$promise->then(null, $onRejected);
```
Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
only specific errors.
```php
$promise
->catch(function (\RuntimeException $reason) {
// Only catch \RuntimeException instances
// All other types of errors will propagate automatically
})
->catch(function (\Throwable $reason) {
// Catch other errors
});
```
#### PromiseInterface::finally()
```php
$newPromise = $promise->finally(callable $onFulfilledOrRejected);
```
Allows you to execute "cleanup" type tasks in a promise chain.
It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
when the promise is either fulfilled or rejected.
* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
`$newPromise` will fulfill with the same value as `$promise`.
* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
rejected promise, `$newPromise` will reject with the thrown exception or
rejected promise's reason.
* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
`$newPromise` will reject with the same reason as `$promise`.
* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
rejected promise, `$newPromise` will reject with the thrown exception or
rejected promise's reason.
`finally()` behaves similarly to the synchronous finally statement. When combined
with `catch()`, `finally()` allows you to write code that is similar to the familiar
synchronous catch/finally pair.
Consider the following synchronous code:
```php
try {
return doSomething();
} catch (\Throwable $e) {
return handleError($e);
} finally {
cleanup();
}
```
Similar asynchronous code (with `doSomething()` that returns a promise) can be
written:
```php
return doSomething()
->catch('handleError')
->finally('cleanup');
```
#### PromiseInterface::cancel()
``` php
$promise->cancel();
```
The `cancel()` method notifies the creator of the promise that there is no
further interest in the results of the operation.
Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
a promise has no effect.
#### ~~PromiseInterface::otherwise()~~
> Deprecated since v3.0.0, see [`catch()`](#promiseinterfacecatch) instead.
The `otherwise()` method registers a rejection handler for a promise.
This method continues to exist only for BC reasons and to ease upgrading
between versions. It is an alias for:
```php
$promise->catch($onRejected);
```
#### ~~PromiseInterface::always()~~
> Deprecated since v3.0.0, see [`finally()`](#promiseinterfacefinally) instead.
The `always()` method allows you to execute "cleanup" type tasks in a promise chain.
This method continues to exist only for BC reasons and to ease upgrading
between versions. It is an alias for:
```php
$promise->finally($onFulfilledOrRejected);
```
### Promise
Creates a promise whose state is controlled by the functions passed to
`$resolver`.
```php
$resolver = function (callable $resolve, callable $reject) {
// Do some work, possibly asynchronously, and then
// resolve or reject.
$resolve($awesomeResult);
// or throw new Exception('Promise rejected');
// or $resolve($anotherPromise);
// or $reject($nastyError);
};
$canceller = function () {
// Cancel/abort any running operations like network connections, streams etc.
// Reject promise by throwing an exception
throw new Exception('Promise cancelled');
};
$promise = new React\Promise\Promise($resolver, $canceller);
```
The promise constructor receives a resolver function and an optional canceller
function which both will be called with two arguments:
* `$resolve($value)` - Primary function that seals the fate of the
returned promise. Accepts either a non-promise value, or another promise.
When called with a non-promise value, fulfills promise with that value.
When called with another promise, e.g. `$resolve($otherPromise)`, promise's
fate will be equivalent to that of `$otherPromise`.
* `$reject($reason)` - Function that rejects the promise. It is recommended to
just throw an exception instead of using `$reject()`.
If the resolver or canceller throw an exception, the promise will be rejected
with that thrown exception as the rejection reason.
The resolver function will be called immediately, the canceller function only
once all consumers called the `cancel()` method of the promise.
### Functions
Useful functions for creating and joining collections of promises.
All functions working on promise collections (like `all()`, `race()`,
etc.) support cancellation. This means, if you call `cancel()` on the returned
promise, all promises in the collection are cancelled.
#### resolve()
```php
$promise = React\Promise\resolve(mixed $promiseOrValue);
```
Creates a promise for the supplied `$promiseOrValue`.
If `$promiseOrValue` is a value, it will be the resolution value of the
returned promise.
If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
a trusted promise that follows the state of the thenable is returned.
If `$promiseOrValue` is a promise, it will be returned as is.
The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface)
and can be consumed like any other promise:
```php
$promise = React\Promise\resolve(42);
$promise->then(function (int $result): void {
var_dump($result);
}, function (\Throwable $e): void {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
#### reject()
```php
$promise = React\Promise\reject(\Throwable $reason);
```
Creates a rejected promise for the supplied `$reason`.
Note that the [`\Throwable`](https://www.php.net/manual/en/class.throwable.php) interface introduced in PHP 7 covers
both user land [`\Exception`](https://www.php.net/manual/en/class.exception.php)'s and
[`\Error`](https://www.php.net/manual/en/class.error.php) internal PHP errors. By enforcing `\Throwable` as reason to
reject a promise, any language error or user land exception can be used to reject a promise.
The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface)
and can be consumed like any other promise:
```php
$promise = React\Promise\reject(new RuntimeException('Request failed'));
$promise->then(function (int $result): void {
var_dump($result);
}, function (\Throwable $e): void {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
Note that rejected promises should always be handled similar to how any
exceptions should always be caught in a `try` + `catch` block. If you remove the
last reference to a rejected promise that has not been handled, it will
report an unhandled promise rejection:
```php
function incorrect(): int
{
$promise = React\Promise\reject(new RuntimeException('Request failed'));
// Commented out: No rejection handler registered here.
// $promise->then(null, function (\Throwable $e): void { /* ignore */ });
// Returning from a function will remove all local variable references, hence why
// this will report an unhandled promise rejection here.
return 42;
}
// Calling this function will log an error message plus its stack trace:
// Unhandled promise rejection with RuntimeException: Request failed in example.php:10
incorrect();
```
A rejected promise will be considered "handled" if you catch the rejection
reason with either the [`then()` method](#promiseinterfacethen), the
[`catch()` method](#promiseinterfacecatch), or the
[`finally()` method](#promiseinterfacefinally). Note that each of these methods
return a new promise that may again be rejected if you re-throw an exception.
A rejected promise will also be considered "handled" if you abort the operation
with the [`cancel()` method](#promiseinterfacecancel) (which in turn would
usually reject the promise if it is still pending).
See also the [`set_rejection_handler()` function](#set_rejection_handler).
#### all()
```php
$promise = React\Promise\all(iterable $promisesOrValues);
```
Returns a promise that will resolve only once all the items in
`$promisesOrValues` have resolved. The resolution value of the returned promise
will be an array containing the resolution values of each of the items in
`$promisesOrValues`.
#### race()
```php
$promise = React\Promise\race(iterable $promisesOrValues);
```
Initiates a competitive race that allows one winner. Returns a promise which is
resolved in the same way the first settled promise resolves.
The returned promise will become **infinitely pending** if `$promisesOrValues`
contains 0 items.
#### any()
```php
$promise = React\Promise\any(iterable $promisesOrValues);
```
Returns a promise that will resolve when any one of the items in
`$promisesOrValues` resolves. The resolution value of the returned promise
will be the resolution value of the triggering item.
The returned promise will only reject if *all* items in `$promisesOrValues` are
rejected. The rejection value will be a `React\Promise\Exception\CompositeException`
which holds all rejection reasons. The rejection reasons can be obtained with
`CompositeException::getThrowables()`.
The returned promise will also reject with a `React\Promise\Exception\LengthException`
if `$promisesOrValues` contains 0 items.
#### set_rejection_handler()
```php
React\Promise\set_rejection_handler(?callable $callback): ?callable;
```
Sets the global rejection handler for unhandled promise rejections.
Note that rejected promises should always be handled similar to how any
exceptions should always be caught in a `try` + `catch` block. If you remove
the last reference to a rejected promise that has not been handled, it will
report an unhandled promise rejection. See also the [`reject()` function](#reject)
for more details.
The `?callable $callback` argument MUST be a valid callback function that
accepts a single `Throwable` argument or a `null` value to restore the
default promise rejection handler. The return value of the callback function
will be ignored and has no effect, so you SHOULD return a `void` value. The
callback function MUST NOT throw or the program will be terminated with a
fatal error.
The function returns the previous rejection handler or `null` if using the
default promise rejection handler.
The default promise rejection handler will log an error message plus its stack
trace:
```php
// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
React\Promise\reject(new RuntimeException('Unhandled'));
```
The promise rejection handler may be used to use customize the log message or
write to custom log targets. As a rule of thumb, this function should only be
used as a last resort and promise rejections are best handled with either the
[`then()` method](#promiseinterfacethen), the
[`catch()` method](#promiseinterfacecatch), or the
[`finally()` method](#promiseinterfacefinally).
See also the [`reject()` function](#reject) for more details.
Examples
--------
### How to use Deferred
```php
function getAwesomeResultPromise()
{
$deferred = new React\Promise\Deferred();
// Execute a Node.js-style function using the callback pattern
computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) {
if ($error) {
$deferred->reject($error);
} else {
$deferred->resolve($result);
}
});
// Return the promise
return $deferred->promise();
}
getAwesomeResultPromise()
->then(
function ($value) {
// Deferred resolved, do something with $value
},
function (\Throwable $reason) {
// Deferred rejected, do something with $reason
}
);
```
### How promise forwarding works
A few simple examples to show how the mechanics of Promises/A forwarding works.
These examples are contrived, of course, and in real usage, promise chains will
typically be spread across several function calls, or even several levels of
your application architecture.
#### Resolution forwarding
Resolved promises forward resolution values to the next promise.
The first promise, `$deferred->promise()`, will resolve with the value passed
to `$deferred->resolve()` below.
Each call to `then()` returns a new promise that will resolve with the return
value of the previous handler. This creates a promise "pipeline".
```php
$deferred = new React\Promise\Deferred();
$deferred->promise()
->then(function ($x) {
// $x will be the value passed to $deferred->resolve() below
// and returns a *new promise* for $x + 1
return $x + 1;
})
->then(function ($x) {
// $x === 2
// This handler receives the return value of the
// previous handler.
return $x + 1;
})
->then(function ($x) {
// $x === 3
// This handler receives the return value of the
// previous handler.
return $x + 1;
})
->then(function ($x) {
// $x === 4
// This handler receives the return value of the
// previous handler.
echo 'Resolve ' . $x;
});
$deferred->resolve(1); // Prints "Resolve 4"
```
#### Rejection forwarding
Rejected promises behave similarly, and also work similarly to try/catch:
When you catch an exception, you must rethrow for it to propagate.
Similarly, when you handle a rejected promise, to propagate the rejection,
"rethrow" it by either returning a rejected promise, or actually throwing
(since promise translates thrown exceptions into rejections)
```php
$deferred = new React\Promise\Deferred();
$deferred->promise()
->then(function ($x) {
throw new \Exception($x + 1);
})
->catch(function (\Exception $x) {
// Propagate the rejection
throw $x;
})
->catch(function (\Exception $x) {
// Can also propagate by returning another rejection
return React\Promise\reject(
new \Exception($x->getMessage() + 1)
);
})
->catch(function ($x) {
echo 'Reject ' . $x->getMessage(); // 3
});
$deferred->resolve(1); // Prints "Reject 3"
```
#### Mixed resolution and rejection forwarding
Just like try/catch, you can choose to propagate or not. Mixing resolutions and
rejections will still forward handler results in a predictable way.
```php
$deferred = new React\Promise\Deferred();
$deferred->promise()
->then(function ($x) {
return $x + 1;
})
->then(function ($x) {
throw new \Exception($x + 1);
})
->catch(function (\Exception $x) {
// Handle the rejection, and don't propagate.
// This is like catch without a rethrow
return $x->getMessage() + 1;
})
->then(function ($x) {
echo 'Mixed ' . $x; // 4
});
$deferred->resolve(1); // Prints "Mixed 4"
```
Install
-------
The recommended way to install this library is [through Composer](https://getcomposer.org/).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This project follows [SemVer](https://semver.org/).
This will install the latest supported version from this branch:
```bash
composer require react/promise:^3.2
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on PHP 7.1 through current PHP 8+.
It's *highly recommended to use the latest supported PHP version* for this project.
We're committed to providing long-term support (LTS) options and to provide a
smooth upgrade path. If you're using an older PHP version, you may use the
[`2.x` branch](https://github.com/reactphp/promise/tree/2.x) (PHP 5.4+) or
[`1.x` branch](https://github.com/reactphp/promise/tree/1.x) (PHP 5.3+) which both
provide a compatible API but do not take advantage of newer language features.
You may target multiple versions at the same time to support a wider range of
PHP versions like this:
```bash
composer require "react/promise:^3 || ^2 || ^1"
```
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org/):
```bash
composer install
```
To run the test suite, go to the project root and run:
```bash
vendor/bin/phpunit
```
On top of this, we use PHPStan on max level to ensure type safety across the project:
```bash
vendor/bin/phpstan
```
Credits
-------
Promise is a port of [when.js](https://github.com/cujojs/when)
by [Brian Cavalier](https://github.com/briancavalier).
Also, large parts of the documentation have been ported from the when.js
[Wiki](https://github.com/cujojs/when/wiki) and the
[API docs](https://github.com/cujojs/when/blob/master/docs/api.md).
License
-------
Released under the [MIT](LICENSE) license.

57
vendor/react/promise/composer.json vendored Executable file
View File

@@ -0,0 +1,57 @@
{
"name": "react/promise",
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"license": "MIT",
"authors": [
{
"name": "Jan Sorgalla",
"homepage": "https://sorgalla.com/",
"email": "jsorgalla@gmail.com"
},
{
"name": "Christian Lück",
"homepage": "https://clue.engineering/",
"email": "christian@clue.engineering"
},
{
"name": "Cees-Jan Kiewiet",
"homepage": "https://wyrihaximus.net/",
"email": "reactphp@ceesjankiewiet.nl"
},
{
"name": "Chris Boden",
"homepage": "https://cboden.dev/",
"email": "cboden@gmail.com"
}
],
"require": {
"php": ">=7.1.0"
},
"require-dev": {
"phpstan/phpstan": "1.12.28 || 1.4.10",
"phpunit/phpunit": "^9.6 || ^7.5"
},
"autoload": {
"psr-4": {
"React\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"autoload-dev": {
"psr-4": {
"React\\Promise\\": [
"tests/fixtures/",
"tests/"
]
},
"files": [
"tests/Fiber.php"
]
},
"keywords": [
"promise",
"promises"
]
}

52
vendor/react/promise/src/Deferred.php vendored Executable file
View File

@@ -0,0 +1,52 @@
<?php
namespace React\Promise;
/**
* @template T
*/
final class Deferred
{
/**
* @var PromiseInterface<T>
*/
private $promise;
/** @var callable(T):void */
private $resolveCallback;
/** @var callable(\Throwable):void */
private $rejectCallback;
/**
* @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller
*/
public function __construct(?callable $canceller = null)
{
$this->promise = new Promise(function ($resolve, $reject): void {
$this->resolveCallback = $resolve;
$this->rejectCallback = $reject;
}, $canceller);
}
/**
* @return PromiseInterface<T>
*/
public function promise(): PromiseInterface
{
return $this->promise;
}
/**
* @param T $value
*/
public function resolve($value): void
{
($this->resolveCallback)($value);
}
public function reject(\Throwable $reason): void
{
($this->rejectCallback)($reason);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace React\Promise\Exception;
/**
* Represents an exception that is a composite of one or more other exceptions.
*
* This exception is useful in situations where a promise must be rejected
* with multiple exceptions. It is used for example to reject the returned
* promise from `some()` and `any()` when too many input promises reject.
*/
class CompositeException extends \Exception
{
/** @var \Throwable[] */
private $throwables;
/** @param \Throwable[] $throwables */
public function __construct(array $throwables, string $message = '', int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->throwables = $throwables;
}
/**
* @return \Throwable[]
*/
public function getThrowables(): array
{
return $this->throwables;
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Promise\Exception;
class LengthException extends \LengthException
{
}

View File

@@ -0,0 +1,64 @@
<?php
namespace React\Promise\Internal;
/**
* @internal
*/
final class CancellationQueue
{
/** @var bool */
private $started = false;
/** @var object[] */
private $queue = [];
public function __invoke(): void
{
if ($this->started) {
return;
}
$this->started = true;
$this->drain();
}
/**
* @param mixed $cancellable
*/
public function enqueue($cancellable): void
{
if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
return;
}
$length = \array_push($this->queue, $cancellable);
if ($this->started && 1 === $length) {
$this->drain();
}
}
private function drain(): void
{
for ($i = \key($this->queue); isset($this->queue[$i]); $i++) {
$cancellable = $this->queue[$i];
assert(\method_exists($cancellable, 'cancel'));
$exception = null;
try {
$cancellable->cancel();
} catch (\Throwable $exception) {
}
unset($this->queue[$i]);
if ($exception) {
throw $exception;
}
}
$this->queue = [];
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace React\Promise\Internal;
use React\Promise\PromiseInterface;
use function React\Promise\resolve;
/**
* @internal
*
* @template T
* @template-implements PromiseInterface<T>
*/
final class FulfilledPromise implements PromiseInterface
{
/** @var T */
private $value;
/**
* @param T $value
* @throws \InvalidArgumentException
*/
public function __construct($value = null)
{
if ($value instanceof PromiseInterface) {
throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
}
$this->value = $value;
}
/**
* @template TFulfilled
* @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
* @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)>
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
{
if (null === $onFulfilled) {
return $this;
}
try {
/**
* @var PromiseInterface<T>|T $result
*/
$result = $onFulfilled($this->value);
return resolve($result);
} catch (\Throwable $exception) {
return new RejectedPromise($exception);
}
}
public function catch(callable $onRejected): PromiseInterface
{
return $this;
}
public function finally(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface {
/** @var T $value */
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
return $value;
});
});
}
public function cancel(): void
{
}
/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->catch($onRejected);
}
/**
* @deprecated 3.0.0 Use `finally()` instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace React\Promise\Internal;
use React\Promise\PromiseInterface;
use function React\Promise\_checkTypehint;
use function React\Promise\resolve;
use function React\Promise\set_rejection_handler;
/**
* @internal
*
* @template-implements PromiseInterface<never>
*/
final class RejectedPromise implements PromiseInterface
{
/** @var \Throwable */
private $reason;
/** @var bool */
private $handled = false;
/**
* @param \Throwable $reason
*/
public function __construct(\Throwable $reason)
{
$this->reason = $reason;
}
/** @throws void */
public function __destruct()
{
if ($this->handled) {
return;
}
$handler = set_rejection_handler(null);
if ($handler === null) {
$message = 'Unhandled promise rejection with ' . $this->reason;
\error_log($message);
return;
}
try {
$handler($this->reason);
} catch (\Throwable $e) {
\preg_match('/^([^:\s]++)(.*+)$/sm', (string) $e, $match);
\assert(isset($match[1], $match[2]));
$message = 'Fatal error: Uncaught ' . $match[1] . ' from unhandled promise rejection handler' . $match[2];
\error_log($message);
exit(255);
}
}
/**
* @template TRejected
* @param ?callable $onFulfilled
* @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
* @return PromiseInterface<($onRejected is null ? never : TRejected)>
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
{
if (null === $onRejected) {
return $this;
}
$this->handled = true;
try {
return resolve($onRejected($this->reason));
} catch (\Throwable $exception) {
return new RejectedPromise($exception);
}
}
/**
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<TRejected>
*/
public function catch(callable $onRejected): PromiseInterface
{
if (!_checkTypehint($onRejected, $this->reason)) {
return $this;
}
/**
* @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
*/
return $this->then(null, $onRejected);
}
public function finally(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->then(null, function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
return resolve($onFulfilledOrRejected())->then(function () use ($reason): PromiseInterface {
return new RejectedPromise($reason);
});
});
}
public function cancel(): void
{
$this->handled = true;
}
/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->catch($onRejected);
}
/**
* @deprecated 3.0.0 Use `always()` instead
* @see self::always()
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}
}

304
vendor/react/promise/src/Promise.php vendored Executable file
View File

@@ -0,0 +1,304 @@
<?php
namespace React\Promise;
use React\Promise\Internal\RejectedPromise;
/**
* @template T
* @template-implements PromiseInterface<T>
*/
final class Promise implements PromiseInterface
{
/** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */
private $canceller;
/** @var ?PromiseInterface<T> */
private $result;
/** @var list<callable(PromiseInterface<T>):void> */
private $handlers = [];
/** @var int */
private $requiredCancelRequests = 0;
/** @var bool */
private $cancelled = false;
/**
* @param callable(callable(T):void,callable(\Throwable):void):void $resolver
* @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller
*/
public function __construct(callable $resolver, ?callable $canceller = null)
{
$this->canceller = $canceller;
// Explicitly overwrite arguments with null values before invoking
// resolver function. This ensure that these arguments do not show up
// in the stack trace in PHP 7+ only.
$cb = $resolver;
$resolver = $canceller = null;
$this->call($cb);
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
{
if (null !== $this->result) {
return $this->result->then($onFulfilled, $onRejected);
}
if (null === $this->canceller) {
return new static($this->resolver($onFulfilled, $onRejected));
}
// This promise has a canceller, so we create a new child promise which
// has a canceller that invokes the parent canceller if all other
// followers are also cancelled. We keep a reference to this promise
// instance for the static canceller function and clear this to avoid
// keeping a cyclic reference between parent and follower.
$parent = $this;
++$parent->requiredCancelRequests;
return new static(
$this->resolver($onFulfilled, $onRejected),
static function () use (&$parent): void {
assert($parent instanceof self);
--$parent->requiredCancelRequests;
if ($parent->requiredCancelRequests <= 0) {
$parent->cancel();
}
$parent = null;
}
);
}
/**
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
*/
public function catch(callable $onRejected): PromiseInterface
{
return $this->then(null, static function (\Throwable $reason) use ($onRejected) {
if (!_checkTypehint($onRejected, $reason)) {
return new RejectedPromise($reason);
}
/**
* @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
*/
return $onRejected($reason);
});
}
public function finally(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface {
/** @var T $value */
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
return $value;
});
}, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise {
return new RejectedPromise($reason);
});
});
}
public function cancel(): void
{
$this->cancelled = true;
$canceller = $this->canceller;
$this->canceller = null;
$parentCanceller = null;
if (null !== $this->result) {
// Forward cancellation to rejected promise to avoid reporting unhandled rejection
if ($this->result instanceof RejectedPromise) {
$this->result->cancel();
}
// Go up the promise chain and reach the top most promise which is
// itself not following another promise
$root = $this->unwrap($this->result);
// Return if the root promise is already resolved or a
// FulfilledPromise or RejectedPromise
if (!$root instanceof self || null !== $root->result) {
return;
}
$root->requiredCancelRequests--;
if ($root->requiredCancelRequests <= 0) {
$parentCanceller = [$root, 'cancel'];
}
}
if (null !== $canceller) {
$this->call($canceller);
}
// For BC, we call the parent canceller after our own canceller
if ($parentCanceller) {
$parentCanceller();
}
}
/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->catch($onRejected);
}
/**
* @deprecated 3.0.0 Use `finally()` instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}
private function resolver(?callable $onFulfilled = null, ?callable $onRejected = null): callable
{
return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void {
$this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void {
$promise = $promise->then($onFulfilled, $onRejected);
if ($promise instanceof self && $promise->result === null) {
$promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void {
$promise->then($resolve, $reject);
};
} else {
$promise->then($resolve, $reject);
}
};
};
}
private function reject(\Throwable $reason): void
{
if (null !== $this->result) {
return;
}
$this->settle(reject($reason));
}
/**
* @param PromiseInterface<T> $result
*/
private function settle(PromiseInterface $result): void
{
$result = $this->unwrap($result);
if ($result === $this) {
$result = new RejectedPromise(
new \LogicException('Cannot resolve a promise with itself.')
);
}
if ($result instanceof self) {
$result->requiredCancelRequests++;
} else {
// Unset canceller only when not following a pending promise
$this->canceller = null;
}
$handlers = $this->handlers;
$this->handlers = [];
$this->result = $result;
foreach ($handlers as $handler) {
$handler($result);
}
// Forward cancellation to rejected promise to avoid reporting unhandled rejection
if ($this->cancelled && $result instanceof RejectedPromise) {
$result->cancel();
}
}
/**
* @param PromiseInterface<T> $promise
* @return PromiseInterface<T>
*/
private function unwrap(PromiseInterface $promise): PromiseInterface
{
while ($promise instanceof self && null !== $promise->result) {
/** @var PromiseInterface<T> $promise */
$promise = $promise->result;
}
return $promise;
}
/**
* @param callable(callable(mixed):void,callable(\Throwable):void):void $cb
*/
private function call(callable $cb): void
{
// Explicitly overwrite argument with null value. This ensure that this
// argument does not show up in the stack trace in PHP 7+ only.
$callback = $cb;
$cb = null;
// Use reflection to inspect number of arguments expected by this callback.
// We did some careful benchmarking here: Using reflection to avoid unneeded
// function arguments is actually faster than blindly passing them.
// Also, this helps avoiding unnecessary function arguments in the call stack
// if the callback creates an Exception (creating garbage cycles).
if (\is_array($callback)) {
$ref = new \ReflectionMethod($callback[0], $callback[1]);
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
$ref = new \ReflectionMethod($callback, '__invoke');
} else {
assert($callback instanceof \Closure || \is_string($callback));
$ref = new \ReflectionFunction($callback);
}
$args = $ref->getNumberOfParameters();
try {
if ($args === 0) {
$callback();
} else {
// Keep references to this promise instance for the static resolve/reject functions.
// By using static callbacks that are not bound to this instance
// and passing the target promise instance by reference, we can
// still execute its resolving logic and still clear this
// reference when settling the promise. This helps avoiding
// garbage cycles if any callback creates an Exception.
// These assumptions are covered by the test suite, so if you ever feel like
// refactoring this, go ahead, any alternative suggestions are welcome!
$target =& $this;
$callback(
static function ($value) use (&$target): void {
if ($target !== null) {
$target->settle(resolve($value));
$target = null;
}
},
static function (\Throwable $reason) use (&$target): void {
if ($target !== null) {
$target->reject($reason);
$target = null;
}
}
);
}
} catch (\Throwable $e) {
$target = null;
$this->reject($e);
}
}
}

152
vendor/react/promise/src/PromiseInterface.php vendored Executable file
View File

@@ -0,0 +1,152 @@
<?php
namespace React\Promise;
/**
* @template-covariant T
*/
interface PromiseInterface
{
/**
* Transforms a promise's value by applying a function to the promise's fulfillment
* or rejection value. Returns a new promise for the transformed result.
*
* The `then()` method registers new fulfilled and rejection handlers with a promise
* (all parameters are optional):
*
* * `$onFulfilled` will be invoked once the promise is fulfilled and passed
* the result as the first argument.
* * `$onRejected` will be invoked once the promise is rejected and passed the
* reason as the first argument.
*
* It returns a new promise that will fulfill with the return value of either
* `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
* the thrown exception if either throws.
*
* A promise makes the following guarantees about handlers registered in
* the same call to `then()`:
*
* 1. Only one of `$onFulfilled` or `$onRejected` will be called,
* never both.
* 2. `$onFulfilled` and `$onRejected` will never be called more
* than once.
*
* @template TFulfilled
* @template TRejected
* @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
* @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
* @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))>
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface;
/**
* Registers a rejection handler for promise. It is a shortcut for:
*
* ```php
* $promise->then(null, $onRejected);
* ```
*
* Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
* only specific errors.
*
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
*/
public function catch(callable $onRejected): PromiseInterface;
/**
* Allows you to execute "cleanup" type tasks in a promise chain.
*
* It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
* when the promise is either fulfilled or rejected.
*
* * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
* `$newPromise` will fulfill with the same value as `$promise`.
* * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
* rejected promise, `$newPromise` will reject with the thrown exception or
* rejected promise's reason.
* * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
* `$newPromise` will reject with the same reason as `$promise`.
* * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
* rejected promise, `$newPromise` will reject with the thrown exception or
* rejected promise's reason.
*
* `finally()` behaves similarly to the synchronous finally statement. When combined
* with `catch()`, `finally()` allows you to write code that is similar to the familiar
* synchronous catch/finally pair.
*
* Consider the following synchronous code:
*
* ```php
* try {
* return doSomething();
* } catch(\Exception $e) {
* return handleError($e);
* } finally {
* cleanup();
* }
* ```
*
* Similar asynchronous code (with `doSomething()` that returns a promise) can be
* written:
*
* ```php
* return doSomething()
* ->catch('handleError')
* ->finally('cleanup');
* ```
*
* @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
* @return PromiseInterface<T>
*/
public function finally(callable $onFulfilledOrRejected): PromiseInterface;
/**
* The `cancel()` method notifies the creator of the promise that there is no
* further interest in the results of the operation.
*
* Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
* a promise has no effect.
*
* @return void
*/
public function cancel(): void;
/**
* [Deprecated] Registers a rejection handler for a promise.
*
* This method continues to exist only for BC reasons and to ease upgrading
* between versions. It is an alias for:
*
* ```php
* $promise->catch($onRejected);
* ```
*
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
* @deprecated 3.0.0 Use catch() instead
* @see self::catch()
*/
public function otherwise(callable $onRejected): PromiseInterface;
/**
* [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain.
*
* This method continues to exist only for BC reasons and to ease upgrading
* between versions. It is an alias for:
*
* ```php
* $promise->finally($onFulfilledOrRejected);
* ```
*
* @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
* @return PromiseInterface<T>
* @deprecated 3.0.0 Use finally() instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected): PromiseInterface;
}

345
vendor/react/promise/src/functions.php vendored Executable file
View File

@@ -0,0 +1,345 @@
<?php
namespace React\Promise;
use React\Promise\Exception\CompositeException;
use React\Promise\Internal\FulfilledPromise;
use React\Promise\Internal\RejectedPromise;
/**
* Creates a promise for the supplied `$promiseOrValue`.
*
* If `$promiseOrValue` is a value, it will be the resolution value of the
* returned promise.
*
* If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
* a trusted promise that follows the state of the thenable is returned.
*
* If `$promiseOrValue` is a promise, it will be returned as is.
*
* @template T
* @param PromiseInterface<T>|T $promiseOrValue
* @return PromiseInterface<T>
*/
function resolve($promiseOrValue): PromiseInterface
{
if ($promiseOrValue instanceof PromiseInterface) {
return $promiseOrValue;
}
if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
$canceller = null;
if (\method_exists($promiseOrValue, 'cancel')) {
$canceller = [$promiseOrValue, 'cancel'];
assert(\is_callable($canceller));
}
/** @var Promise<T> */
return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void {
$promiseOrValue->then($resolve, $reject);
}, $canceller);
}
return new FulfilledPromise($promiseOrValue);
}
/**
* Creates a rejected promise for the supplied `$reason`.
*
* If `$reason` is a value, it will be the rejection value of the
* returned promise.
*
* If `$reason` is a promise, its completion value will be the rejected
* value of the returned promise.
*
* This can be useful in situations where you need to reject a promise without
* throwing an exception. For example, it allows you to propagate a rejection with
* the value of another promise.
*
* @return PromiseInterface<never>
*/
function reject(\Throwable $reason): PromiseInterface
{
return new RejectedPromise($reason);
}
/**
* Returns a promise that will resolve only once all the items in
* `$promisesOrValues` have resolved. The resolution value of the returned promise
* will be an array containing the resolution values of each of the items in
* `$promisesOrValues`.
*
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<array<T>>
*/
function all(iterable $promisesOrValues): PromiseInterface
{
$cancellationQueue = new Internal\CancellationQueue();
/** @var Promise<array<T>> */
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
$toResolve = 0;
/** @var bool */
$continue = true;
$values = [];
foreach ($promisesOrValues as $i => $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
$values[$i] = null;
++$toResolve;
resolve($promiseOrValue)->then(
function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void {
$values[$i] = $value;
if (0 === --$toResolve && !$continue) {
$resolve($values);
}
},
function (\Throwable $reason) use (&$continue, $reject): void {
$continue = false;
$reject($reason);
}
);
if (!$continue && !\is_array($promisesOrValues)) {
break;
}
}
$continue = false;
if ($toResolve === 0) {
$resolve($values);
}
}, $cancellationQueue);
}
/**
* Initiates a competitive race that allows one winner. Returns a promise which is
* resolved in the same way the first settled promise resolves.
*
* The returned promise will become **infinitely pending** if `$promisesOrValues`
* contains 0 items.
*
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<T>
*/
function race(iterable $promisesOrValues): PromiseInterface
{
$cancellationQueue = new Internal\CancellationQueue();
/** @var Promise<T> */
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
$continue = true;
foreach ($promisesOrValues as $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void {
$continue = false;
});
if (!$continue && !\is_array($promisesOrValues)) {
break;
}
}
}, $cancellationQueue);
}
/**
* Returns a promise that will resolve when any one of the items in
* `$promisesOrValues` resolves. The resolution value of the returned promise
* will be the resolution value of the triggering item.
*
* The returned promise will only reject if *all* items in `$promisesOrValues` are
* rejected. The rejection value will be an array of all rejection reasons.
*
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
* if `$promisesOrValues` contains 0 items.
*
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<T>
*/
function any(iterable $promisesOrValues): PromiseInterface
{
$cancellationQueue = new Internal\CancellationQueue();
/** @var Promise<T> */
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
$toReject = 0;
$continue = true;
$reasons = [];
foreach ($promisesOrValues as $i => $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
++$toReject;
resolve($promiseOrValue)->then(
function ($value) use ($resolve, &$continue): void {
$continue = false;
$resolve($value);
},
function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void {
$reasons[$i] = $reason;
if (0 === --$toReject && !$continue) {
$reject(new CompositeException(
$reasons,
'All promises rejected.'
));
}
}
);
if (!$continue && !\is_array($promisesOrValues)) {
break;
}
}
$continue = false;
if ($toReject === 0 && !$reasons) {
$reject(new Exception\LengthException(
'Must contain at least 1 item but contains only 0 items.'
));
} elseif ($toReject === 0) {
$reject(new CompositeException(
$reasons,
'All promises rejected.'
));
}
}, $cancellationQueue);
}
/**
* Sets the global rejection handler for unhandled promise rejections.
*
* Note that rejected promises should always be handled similar to how any
* exceptions should always be caught in a `try` + `catch` block. If you remove
* the last reference to a rejected promise that has not been handled, it will
* report an unhandled promise rejection. See also the [`reject()` function](#reject)
* for more details.
*
* The `?callable $callback` argument MUST be a valid callback function that
* accepts a single `Throwable` argument or a `null` value to restore the
* default promise rejection handler. The return value of the callback function
* will be ignored and has no effect, so you SHOULD return a `void` value. The
* callback function MUST NOT throw or the program will be terminated with a
* fatal error.
*
* The function returns the previous rejection handler or `null` if using the
* default promise rejection handler.
*
* The default promise rejection handler will log an error message plus its
* stack trace:
*
* ```php
* // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
* React\Promise\reject(new RuntimeException('Unhandled'));
* ```
*
* The promise rejection handler may be used to use customize the log message or
* write to custom log targets. As a rule of thumb, this function should only be
* used as a last resort and promise rejections are best handled with either the
* [`then()` method](#promiseinterfacethen), the
* [`catch()` method](#promiseinterfacecatch), or the
* [`finally()` method](#promiseinterfacefinally).
* See also the [`reject()` function](#reject) for more details.
*
* @param callable(\Throwable):void|null $callback
* @return callable(\Throwable):void|null
*/
function set_rejection_handler(?callable $callback): ?callable
{
static $current = null;
$previous = $current;
$current = $callback;
return $previous;
}
/**
* @internal
*/
function _checkTypehint(callable $callback, \Throwable $reason): bool
{
if (\is_array($callback)) {
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
$callbackReflection = new \ReflectionMethod($callback, '__invoke');
} else {
assert($callback instanceof \Closure || \is_string($callback));
$callbackReflection = new \ReflectionFunction($callback);
}
$parameters = $callbackReflection->getParameters();
if (!isset($parameters[0])) {
return true;
}
$expectedException = $parameters[0];
// Extract the type of the argument and handle different possibilities
$type = $expectedException->getType();
$isTypeUnion = true;
$types = [];
switch (true) {
case $type === null:
break;
case $type instanceof \ReflectionNamedType:
$types = [$type];
break;
case $type instanceof \ReflectionIntersectionType:
$isTypeUnion = false;
case $type instanceof \ReflectionUnionType:
$types = $type->getTypes();
break;
default:
throw new \LogicException('Unexpected return value of ReflectionParameter::getType');
}
// If there is no type restriction, it matches
if (empty($types)) {
return true;
}
foreach ($types as $type) {
if ($type instanceof \ReflectionIntersectionType) {
foreach ($type->getTypes() as $typeToMatch) {
assert($typeToMatch instanceof \ReflectionNamedType);
$name = $typeToMatch->getName();
if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) {
break;
}
}
assert(isset($matches));
} else {
assert($type instanceof \ReflectionNamedType);
$name = $type->getName();
$matches = !$type->isBuiltin() && $reason instanceof $name;
}
// If we look for a single match (union), we can return early on match
// If we look for a full match (intersection), we can return early on mismatch
if ($matches) {
if ($isTypeUnion) {
return true;
}
} else {
if (!$isTypeUnion) {
return false;
}
}
}
// If we look for a single match (union) and did not return early, we matched no type and are false
// If we look for a full match (intersection) and did not return early, we matched all types and are true
return $isTypeUnion ? false : true;
}

View File

@@ -0,0 +1,5 @@
<?php
if (!\function_exists('React\Promise\resolve')) {
require __DIR__.'/functions.php';
}

785
vendor/react/socket/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,785 @@
# Changelog
## 1.16.0 (2024-07-26)
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
(#318 by @clue)
## 1.15.0 (2023-12-15)
* Feature: Full PHP 8.3 compatibility.
(#310 by @clue)
* Fix: Fix cancelling during the 50ms resolution delay when DNS is still pending.
(#311 by @clue)
## 1.14.0 (2023-08-25)
* Feature: Improve Promise v3 support and use template types.
(#307 and #309 by @clue)
* Improve test suite and update to collect all garbage cycles.
(#308 by @clue)
## 1.13.0 (2023-06-07)
* Feature: Include timeout logic to avoid dependency on reactphp/promise-timer.
(#305 by @clue)
* Feature: Improve errno detection for failed connections without `ext-sockets`.
(#304 by @clue)
* Improve test suite, clean up leftover `.sock` files and report failed assertions.
(#299, #300, #301 and #306 by @clue)
## 1.12.0 (2022-08-25)
* Feature: Forward compatibility with react/promise 3.
(#214 by @WyriHaximus and @clue)
* Feature: Full support for PHP 8.2 release.
(#298 by @WyriHaximus)
* Feature: Avoid unneeded syscall on socket close.
(#292 by @clue)
* Feature / Fix: Improve error reporting when custom error handler is used.
(#290 by @clue)
* Fix: Fix invalid references in exception stack trace.
(#284 by @clue)
* Minor documentation improvements, update to use new reactphp/async package instead of clue/reactphp-block.
(#296 by @clue, #285 by @SimonFrings and #295 by @nhedger)
* Improve test suite, update macOS and HHVM environment, fix optional tests for `ENETUNREACH`.
(#288, #289 and #297 by @clue)
## 1.11.0 (2022-01-14)
* Feature: Full support for PHP 8.1 release.
(#277 by @clue)
* Feature: Avoid dependency on `ext-filter`.
(#279 by @clue)
* Improve test suite to skip FD test when hitting memory limit
and skip legacy TLS 1.0 tests if disabled by system.
(#278 and #281 by @clue and #283 by @SimonFrings)
## 1.10.0 (2021-11-29)
* Feature: Support listening on existing file descriptors (FDs) with `SocketServer`.
(#269 by @clue)
```php
$socket = new React\Socket\SocketSever('php://fd/3');
```
This is particularly useful when using [systemd socket activation](https://www.freedesktop.org/software/systemd/man/systemd.socket.html) like this:
```bash
$ systemd-socket-activate -l 8000 php examples/03-http-server.php php://fd/3
```
* Feature: Improve error messages for failed connection attempts with `errno` and `errstr`.
(#265, #266, #267, #270 and #271 by @clue and #268 by @SimonFrings)
All error messages now always include the appropriate `errno` and `errstr` to
give more details about the error reason when available. Along with these
error details exposed by the underlying system functions, it will also
include the appropriate error constant name (such as `ECONNREFUSED`) when
available. Accordingly, failed TCP/IP connections will now report the actual
underlying error condition instead of a generic "Connection refused" error.
Higher-level error messages will now consistently report the connection URI
scheme and hostname used in all error messages.
For most common use cases this means that simply reporting the `Exception`
message should give the most relevant details for any connection issues:
```php
$connector = new React\Socket\Connector();
$connector->connect($uri)->then(function (React\Socket\ConnectionInterface $conn) {
// …
}, function (Exception $e) {
echo 'Error:' . $e->getMessage() . PHP_EOL;
});
```
* Improve test suite, test against PHP 8.1 release.
(#274 by @SimonFrings)
## 1.9.0 (2021-08-03)
* Feature: Add new `SocketServer` and deprecate `Server` to avoid class name collisions.
(#263 by @clue)
The new `SocketServer` class has been added with an improved constructor signature
as a replacement for the previous `Server` class in order to avoid any ambiguities.
The previous name has been deprecated and should not be used anymore.
In its most basic form, the deprecated `Server` can now be considered an alias for new `SocketServer`.
```php
// deprecated
$socket = new React\Socket\Server(0);
$socket = new React\Socket\Server('127.0.0.1:8000');
$socket = new React\Socket\Server('127.0.0.1:8000', null, $context);
$socket = new React\Socket\Server('127.0.0.1:8000', $loop, $context);
// new
$socket = new React\Socket\SocketServer('127.0.0.1:0');
$socket = new React\Socket\SocketServer('127.0.0.1:8000');
$socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
$socket = new React\Socket\SocketServer('127.0.0.1:8000', $context, $loop);
```
* Feature: Update `Connector` signature to take optional `$context` as first argument.
(#264 by @clue)
The new signature has been added to match the new `SocketServer` and
consistently move the now commonly unneeded loop argument to the last argument.
The previous signature has been deprecated and should not be used anymore.
In its most basic form, both signatures are compatible.
```php
// deprecated
$connector = new React\Socket\Connector(null, $context);
$connector = new React\Socket\Connector($loop, $context);
// new
$connector = new React\Socket\Connector($context);
$connector = new React\Socket\Connector($context, $loop);
```
## 1.8.0 (2021-07-11)
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
(#260 by @clue)
```php
// old (still supported)
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
$connector = new React\Socket\Connector($loop);
// new (using default loop)
$socket = new React\Socket\Server('127.0.0.1:8080');
$connector = new React\Socket\Connector();
```
## 1.7.0 (2021-06-25)
* Feature: Support falling back to multiple DNS servers from DNS config.
(#257 by @clue)
If you're using the default `Connector`, it will now use all DNS servers
configured on your system. If you have multiple DNS servers configured and
connectivity to the primary DNS server is broken, it will now fall back to
your other DNS servers, thus providing improved connectivity and redundancy
for broken DNS configurations.
* Feature: Use round robin for happy eyeballs DNS responses (load balancing).
(#247 by @clue)
If you're using the default `Connector`, it will now randomize the order of
the IP addresses resolved via DNS when connecting. This allows the load to
be distributed more evenly across all returned IP addresses. This can be
used as a very basic DNS load balancing mechanism.
* Internal improvement to avoid unhandled rejection for future Promise API.
(#258 by @clue)
* Improve test suite, use GitHub actions for continuous integration (CI).
(#254 by @SimonFrings)
## 1.6.0 (2020-08-28)
* Feature: Support upcoming PHP 8 release.
(#246 by @clue)
* Feature: Change default socket backlog size to 511.
(#242 by @clue)
* Fix: Fix closing connection when cancelling during TLS handshake.
(#241 by @clue)
* Fix: Fix blocking during possible `accept()` race condition
when multiple socket servers listen on same socket address.
(#244 by @clue)
* Improve test suite, update PHPUnit config and add full core team to the license.
(#243 by @SimonFrings and #245 by @WyriHaximus)
## 1.5.0 (2020-07-01)
* Feature / Fix: Improve error handling and reporting for happy eyeballs and
immediately try next connection when one connection attempt fails.
(#230, #231, #232 and #233 by @clue)
Error messages for failed connection attempts now include more details to
ease debugging. Additionally, the happy eyeballs algorithm has been improved
to avoid having to wait for some timers to expire which significantly
improves connection setup times (in particular when IPv6 isn't available).
* Improve test suite, minor code cleanup and improve code coverage to 100%.
Update to PHPUnit 9 and skip legacy TLS 1.0 / TLS 1.1 tests if disabled by
system. Run tests on Windows and simplify Travis CI test matrix for Mac OS X
setup and skip all TLS tests on legacy HHVM.
(#229, #235, #236 and #238 by @clue and #239 by @SimonFrings)
## 1.4.0 (2020-03-12)
A major new feature release, see [**release announcement**](https://clue.engineering/2020/introducing-ipv6-for-reactphp).
* Feature: Add IPv6 support to `Connector` (implement "Happy Eyeballs" algorithm to support IPv6 probing).
IPv6 support is turned on by default, use new `happy_eyeballs` option in `Connector` to toggle behavior.
(#196, #224 and #225 by @WyriHaximus and @clue)
* Feature: Default to using DNS cache (with max 256 entries) for `Connector`.
(#226 by @clue)
* Add `.gitattributes` to exclude dev files from exports and some minor code style fixes.
(#219 by @reedy and #218 by @mmoreram)
* Improve test suite to fix failing test cases when using new DNS component,
significantly improve test performance by awaiting events instead of sleeping,
exclude TLS 1.3 test on PHP 7.3, run tests on PHP 7.4 and simplify test matrix.
(#208, #209, #210, #217 and #223 by @clue)
## 1.3.0 (2019-07-10)
* Feature: Forward compatibility with upcoming stable DNS component.
(#206 by @clue)
## 1.2.1 (2019-06-03)
* Avoid uneeded fragmented TLS work around for PHP 7.3.3+ and
work around failing test case detecting EOF on TLS 1.3 socket streams.
(#201 and #202 by @clue)
* Improve TLS certificate/passphrase example.
(#190 by @jsor)
## 1.2.0 (2019-01-07)
* Feature / Fix: Improve TLS 1.3 support.
(#186 by @clue)
TLS 1.3 is now an official standard as of August 2018! :tada:
The protocol has major improvements in the areas of security, performance, and privacy.
TLS 1.3 is supported by default as of [OpenSSL 1.1.1](https://www.openssl.org/blog/blog/2018/09/11/release111/).
For example, this version ships with Ubuntu 18.10 (and newer) by default, meaning that recent installations support TLS 1.3 out of the box :shipit:
* Fix: Avoid possibility of missing remote address when TLS handshake fails.
(#188 by @clue)
* Improve performance by prefixing all global functions calls with `\` to skip the look up and resolve process and go straight to the global function.
(#183 by @WyriHaximus)
* Update documentation to use full class names with namespaces.
(#187 by @clue)
* Improve test suite to avoid some possible race conditions,
test against PHP 7.3 on Travis and
use dedicated `assertInstanceOf()` assertions.
(#185 by @clue, #178 by @WyriHaximus and #181 by @carusogabriel)
## 1.1.0 (2018-10-01)
* Feature: Improve error reporting for failed connection attempts and improve
cancellation forwarding during DNS lookup, TCP/IP connection or TLS handshake.
(#168, #169, #170, #171, #176 and #177 by @clue)
All error messages now always contain a reference to the remote URI to give
more details which connection actually failed and the reason for this error.
Accordingly, failures during DNS lookup will now mention both the remote URI
as well as the DNS error reason. TCP/IP connection issues and errors during
a secure TLS handshake will both mention the remote URI as well as the
underlying socket error. Similarly, lost/dropped connections during a TLS
handshake will now report a lost connection instead of an empty error reason.
For most common use cases this means that simply reporting the `Exception`
message should give the most relevant details for any connection issues:
```php
$promise = $connector->connect('tls://example.com:443');
$promise->then(function (ConnectionInterface $conn) use ($loop) {
// …
}, function (Exception $e) {
echo $e->getMessage();
});
```
## 1.0.0 (2018-07-11)
* First stable LTS release, now following [SemVer](https://semver.org/).
We'd like to emphasize that this component is production ready and battle-tested.
We plan to support all long-term support (LTS) releases for at least 24 months,
so you have a rock-solid foundation to build on top of.
> Contains no other changes, so it's actually fully compatible with the v0.8.12 release.
## 0.8.12 (2018-06-11)
* Feature: Improve memory consumption for failed and cancelled connection attempts.
(#161 by @clue)
* Improve test suite to fix Travis config to test against legacy PHP 5.3 again.
(#162 by @clue)
## 0.8.11 (2018-04-24)
* Feature: Improve memory consumption for cancelled connection attempts and
simplify skipping DNS lookup when connecting to IP addresses.
(#159 and #160 by @clue)
## 0.8.10 (2018-02-28)
* Feature: Update DNS dependency to support loading system default DNS
nameserver config on all supported platforms
(`/etc/resolv.conf` on Unix/Linux/Mac/Docker/WSL and WMIC on Windows)
(#152 by @clue)
This means that connecting to hosts that are managed by a local DNS server,
such as a corporate DNS server or when using Docker containers, will now
work as expected across all platforms with no changes required:
```php
$connector = new Connector($loop);
$connector->connect('intranet.example:80')->then(function ($connection) {
// …
});
```
## 0.8.9 (2018-01-18)
* Feature: Support explicitly choosing TLS version to negotiate with remote side
by respecting `crypto_method` context parameter for all classes.
(#149 by @clue)
By default, all connector and server classes support TLSv1.0+ and exclude
support for legacy SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly
choose the TLS version you want to negotiate with the remote side:
```php
// new: now supports 'crypto_method` context parameter for all classes
$connector = new Connector($loop, array(
'tls' => array(
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
)
));
```
* Minor internal clean up to unify class imports
(#148 by @clue)
## 0.8.8 (2018-01-06)
* Improve test suite by adding test group to skip integration tests relying on
internet connection and fix minor documentation typo.
(#146 by @clue and #145 by @cn007b)
## 0.8.7 (2017-12-24)
* Fix: Fix closing socket resource before removing from loop
(#141 by @clue)
This fixes the root cause of an uncaught `Exception` that only manifested
itself after the recent Stream v0.7.4 component update and only if you're
using `ext-event` (`ExtEventLoop`).
* Improve test suite by testing against PHP 7.2
(#140 by @carusogabriel)
## 0.8.6 (2017-11-18)
* Feature: Add Unix domain socket (UDS) support to `Server` with `unix://` URI scheme
and add advanced `UnixServer` class.
(#120 by @andig)
```php
// new: Server now supports "unix://" scheme
$server = new Server('unix:///tmp/server.sock', $loop);
// new: advanced usage
$server = new UnixServer('/tmp/server.sock', $loop);
```
* Restructure examples to ease getting started
(#136 by @clue)
* Improve test suite by adding forward compatibility with PHPUnit 6 and
ignore Mac OS X test failures for now until Travis tests work again
(#133 by @gabriel-caruso and #134 by @clue)
## 0.8.5 (2017-10-23)
* Fix: Work around PHP bug with Unix domain socket (UDS) paths for Mac OS X
(#123 by @andig)
* Fix: Fix `SecureServer` to return `null` URI if server socket is already closed
(#129 by @clue)
* Improve test suite by adding forward compatibility with PHPUnit v5 and
forward compatibility with upcoming EventLoop releases in tests and
test Mac OS X on Travis
(#122 by @andig and #125, #127 and #130 by @clue)
* Readme improvements
(#118 by @jsor)
## 0.8.4 (2017-09-16)
* Feature: Add `FixedUriConnector` decorator to use fixed, preconfigured URI instead
(#117 by @clue)
This can be useful for consumers that do not support certain URIs, such as
when you want to explicitly connect to a Unix domain socket (UDS) path
instead of connecting to a default address assumed by an higher-level API:
```php
$connector = new FixedUriConnector(
'unix:///var/run/docker.sock',
new UnixConnector($loop)
);
// destination will be ignored, actually connects to Unix domain socket
$promise = $connector->connect('localhost:80');
```
## 0.8.3 (2017-09-08)
* Feature: Reduce memory consumption for failed connections
(#113 by @valga)
* Fix: Work around write chunk size for TLS streams for PHP < 7.1.14
(#114 by @clue)
## 0.8.2 (2017-08-25)
* Feature: Update DNS dependency to support hosts file on all platforms
(#112 by @clue)
This means that connecting to hosts such as `localhost` will now work as
expected across all platforms with no changes required:
```php
$connector = new Connector($loop);
$connector->connect('localhost:8080')->then(function ($connection) {
// …
});
```
## 0.8.1 (2017-08-15)
* Feature: Forward compatibility with upcoming EventLoop v1.0 and v0.5 and
target evenement 3.0 a long side 2.0 and 1.0
(#104 by @clue and #111 by @WyriHaximus)
* Improve test suite by locking Travis distro so new defaults will not break the build and
fix HHVM build for now again and ignore future HHVM build errors
(#109 and #110 by @clue)
* Minor documentation fixes
(#103 by @christiaan and #108 by @hansott)
## 0.8.0 (2017-05-09)
* Feature: New `Server` class now acts as a facade for existing server classes
and renamed old `Server` to `TcpServer` for advanced usage.
(#96 and #97 by @clue)
The `Server` class is now the main class in this package that implements the
`ServerInterface` and allows you to accept incoming streaming connections,
such as plaintext TCP/IP or secure TLS connection streams.
> This is not a BC break and consumer code does not have to be updated.
* Feature / BC break: All addresses are now URIs that include the URI scheme
(#98 by @clue)
```diff
- $parts = parse_url('tcp://' . $conn->getRemoteAddress());
+ $parts = parse_url($conn->getRemoteAddress());
```
* Fix: Fix `unix://` addresses for Unix domain socket (UDS) paths
(#100 by @clue)
* Feature: Forward compatibility with Stream v1.0 and v0.7
(#99 by @clue)
## 0.7.2 (2017-04-24)
* Fix: Work around latest PHP 7.0.18 and 7.1.4 no longer accepting full URIs
(#94 by @clue)
## 0.7.1 (2017-04-10)
* Fix: Ignore HHVM errors when closing connection that is already closing
(#91 by @clue)
## 0.7.0 (2017-04-10)
* Feature: Merge SocketClient component into this component
(#87 by @clue)
This means that this package now provides async, streaming plaintext TCP/IP
and secure TLS socket server and client connections for ReactPHP.
```
$connector = new React\Socket\Connector($loop);
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
$connection->write('…');
});
```
Accordingly, the `ConnectionInterface` is now used to represent both incoming
server side connections as well as outgoing client side connections.
If you've previously used the SocketClient component to establish outgoing
client connections, upgrading should take no longer than a few minutes.
All classes have been merged as-is from the latest `v0.7.0` release with no
other changes, so you can simply update your code to use the updated namespace
like this:
```php
// old from SocketClient component and namespace
$connector = new React\SocketClient\Connector($loop);
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
$connection->write('…');
});
// new
$connector = new React\Socket\Connector($loop);
$connector->connect('google.com:80')->then(function (ConnectionInterface $conn) {
$connection->write('…');
});
```
## 0.6.0 (2017-04-04)
* Feature: Add `LimitingServer` to limit and keep track of open connections
(#86 by @clue)
```php
$server = new Server(0, $loop);
$server = new LimitingServer($server, 100);
$server->on('connection', function (ConnectionInterface $connection) {
$connection->write('hello there!' . PHP_EOL);
});
```
* Feature / BC break: Add `pause()` and `resume()` methods to limit active
connections
(#84 by @clue)
```php
$server = new Server(0, $loop);
$server->pause();
$loop->addTimer(1.0, function() use ($server) {
$server->resume();
});
```
## 0.5.1 (2017-03-09)
* Feature: Forward compatibility with Stream v0.5 and upcoming v0.6
(#79 by @clue)
## 0.5.0 (2017-02-14)
* Feature / BC break: Replace `listen()` call with URIs passed to constructor
and reject listening on hostnames with `InvalidArgumentException`
and replace `ConnectionException` with `RuntimeException` for consistency
(#61, #66 and #72 by @clue)
```php
// old
$server = new Server($loop);
$server->listen(8080);
// new
$server = new Server(8080, $loop);
```
Similarly, you can now pass a full listening URI to the constructor to change
the listening host:
```php
// old
$server = new Server($loop);
$server->listen(8080, '127.0.0.1');
// new
$server = new Server('127.0.0.1:8080', $loop);
```
Trying to start listening on (DNS) host names will now throw an
`InvalidArgumentException`, use IP addresses instead:
```php
// old
$server = new Server($loop);
$server->listen(8080, 'localhost');
// new
$server = new Server('127.0.0.1:8080', $loop);
```
If trying to listen fails (such as if port is already in use or port below
1024 may require root access etc.), it will now throw a `RuntimeException`,
the `ConnectionException` class has been removed:
```php
// old: throws React\Socket\ConnectionException
$server = new Server($loop);
$server->listen(80);
// new: throws RuntimeException
$server = new Server(80, $loop);
```
* Feature / BC break: Rename `shutdown()` to `close()` for consistency throughout React
(#62 by @clue)
```php
// old
$server->shutdown();
// new
$server->close();
```
* Feature / BC break: Replace `getPort()` with `getAddress()`
(#67 by @clue)
```php
// old
echo $server->getPort(); // 8080
// new
echo $server->getAddress(); // 127.0.0.1:8080
```
* Feature / BC break: `getRemoteAddress()` returns full address instead of only IP
(#65 by @clue)
```php
// old
echo $connection->getRemoteAddress(); // 192.168.0.1
// new
echo $connection->getRemoteAddress(); // 192.168.0.1:51743
```
* Feature / BC break: Add `getLocalAddress()` method
(#68 by @clue)
```php
echo $connection->getLocalAddress(); // 127.0.0.1:8080
```
* BC break: The `Server` and `SecureServer` class are now marked `final`
and you can no longer `extend` them
(which was never documented or recommended anyway).
Public properties and event handlers are now internal only.
Please use composition instead of extension.
(#71, #70 and #69 by @clue)
## 0.4.6 (2017-01-26)
* Feature: Support socket context options passed to `Server`
(#64 by @clue)
* Fix: Properly return `null` for unknown addresses
(#63 by @clue)
* Improve documentation for `ServerInterface` and lock test suite requirements
(#60 by @clue, #57 by @shaunbramley)
## 0.4.5 (2017-01-08)
* Feature: Add `SecureServer` for secure TLS connections
(#55 by @clue)
* Add functional integration tests
(#54 by @clue)
## 0.4.4 (2016-12-19)
* Feature / Fix: `ConnectionInterface` should extend `DuplexStreamInterface` + documentation
(#50 by @clue)
* Feature / Fix: Improve test suite and switch to normal stream handler
(#51 by @clue)
* Feature: Add examples
(#49 by @clue)
## 0.4.3 (2016-03-01)
* Bug fix: Suppress errors on stream_socket_accept to prevent PHP from crashing
* Support for PHP7 and HHVM
* Support PHP 5.3 again
## 0.4.2 (2014-05-25)
* Verify stream is a valid resource in Connection
## 0.4.1 (2014-04-13)
* Bug fix: Check read buffer for data before shutdown signal and end emit (@ArtyDev)
* Bug fix: v0.3.4 changes merged for v0.4.1
## 0.3.4 (2014-03-30)
* Bug fix: Reset socket to non-blocking after shutting down (PHP bug)
## 0.4.0 (2014-02-02)
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
* BC break: Update to React/Promise 2.0
* BC break: Update to Evenement 2.0
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
* Bump React dependencies to v0.4
## 0.3.3 (2013-07-08)
* Version bump
## 0.3.2 (2013-05-10)
* Version bump
## 0.3.1 (2013-04-21)
* Feature: Support binding to IPv6 addresses (@clue)
## 0.3.0 (2013-04-14)
* Bump React dependencies to v0.3
## 0.2.6 (2012-12-26)
* Version bump
## 0.2.3 (2012-11-14)
* Version bump
## 0.2.0 (2012-09-10)
* Bump React dependencies to v0.2
## 0.1.1 (2012-07-12)
* Version bump
## 0.1.0 (2012-07-11)
* First tagged release

21
vendor/react/socket/LICENSE vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

1564
vendor/react/socket/README.md vendored Executable file

File diff suppressed because it is too large Load Diff

52
vendor/react/socket/composer.json vendored Executable file
View File

@@ -0,0 +1,52 @@
{
"name": "react/socket",
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
"keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"homepage": "https://clue.engineering/",
"email": "christian@clue.engineering"
},
{
"name": "Cees-Jan Kiewiet",
"homepage": "https://wyrihaximus.net/",
"email": "reactphp@ceesjankiewiet.nl"
},
{
"name": "Jan Sorgalla",
"homepage": "https://sorgalla.com/",
"email": "jsorgalla@gmail.com"
},
{
"name": "Chris Boden",
"homepage": "https://cboden.dev/",
"email": "cboden@gmail.com"
}
],
"require": {
"php": ">=5.3.0",
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"react/dns": "^1.13",
"react/event-loop": "^1.2",
"react/promise": "^3.2 || ^2.6 || ^1.2.1",
"react/stream": "^1.4"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"react/async": "^4.3 || ^3.3 || ^2",
"react/promise-stream": "^1.4",
"react/promise-timer": "^1.11"
},
"autoload": {
"psr-4": {
"React\\Socket\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"React\\Tests\\Socket\\": "tests/"
}
}
}

183
vendor/react/socket/src/Connection.php vendored Executable file
View File

@@ -0,0 +1,183 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use React\EventLoop\LoopInterface;
use React\Stream\DuplexResourceStream;
use React\Stream\Util;
use React\Stream\WritableResourceStream;
use React\Stream\WritableStreamInterface;
/**
* The actual connection implementation for ConnectionInterface
*
* This class should only be used internally, see ConnectionInterface instead.
*
* @see ConnectionInterface
* @internal
*/
class Connection extends EventEmitter implements ConnectionInterface
{
/**
* Internal flag whether this is a Unix domain socket (UDS) connection
*
* @internal
*/
public $unix = false;
/**
* Internal flag whether encryption has been enabled on this connection
*
* Mostly used by internal StreamEncryption so that connection returns
* `tls://` scheme for encrypted connections instead of `tcp://`.
*
* @internal
*/
public $encryptionEnabled = false;
/** @internal */
public $stream;
private $input;
public function __construct($resource, LoopInterface $loop)
{
// PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might
// block with 100% CPU usage on fragmented TLS records.
// We try to work around this by always consuming the complete receive
// buffer at once to avoid stale data in TLS buffers. This is known to
// work around high CPU usage for well-behaving peers, but this may
// cause very large data chunks for high throughput scenarios. The buggy
// behavior can still be triggered due to network I/O buffers or
// malicious peers on affected versions, upgrading is highly recommended.
// @link https://bugs.php.net/bug.php?id=77390
$clearCompleteBuffer = \PHP_VERSION_ID < 70215 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70303);
// PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
// chunks of data over TLS streams at once.
// We try to work around this by limiting the write chunk size to 8192
// bytes for older PHP versions only.
// This is only a work-around and has a noticable performance penalty on
// affected versions. Please update your PHP version.
// This applies to all streams because TLS may be enabled later on.
// See https://github.com/reactphp/socket/issues/105
$limitWriteChunks = (\PHP_VERSION_ID < 70018 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70104));
$this->input = new DuplexResourceStream(
$resource,
$loop,
$clearCompleteBuffer ? -1 : null,
new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
);
$this->stream = $resource;
Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
$this->input->on('close', array($this, 'close'));
}
public function isReadable()
{
return $this->input->isReadable();
}
public function isWritable()
{
return $this->input->isWritable();
}
public function pause()
{
$this->input->pause();
}
public function resume()
{
$this->input->resume();
}
public function pipe(WritableStreamInterface $dest, array $options = array())
{
return $this->input->pipe($dest, $options);
}
public function write($data)
{
return $this->input->write($data);
}
public function end($data = null)
{
$this->input->end($data);
}
public function close()
{
$this->input->close();
$this->handleClose();
$this->removeAllListeners();
}
public function handleClose()
{
if (!\is_resource($this->stream)) {
return;
}
// Try to cleanly shut down socket and ignore any errors in case other
// side already closed. Underlying Stream implementation will take care
// of closing stream resource, so we otherwise keep this open here.
@\stream_socket_shutdown($this->stream, \STREAM_SHUT_RDWR);
}
public function getRemoteAddress()
{
if (!\is_resource($this->stream)) {
return null;
}
return $this->parseAddress(\stream_socket_get_name($this->stream, true));
}
public function getLocalAddress()
{
if (!\is_resource($this->stream)) {
return null;
}
return $this->parseAddress(\stream_socket_get_name($this->stream, false));
}
private function parseAddress($address)
{
if ($address === false) {
return null;
}
if ($this->unix) {
// remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
// note that technically ":" is a valid address, so keep this in place otherwise
if (\substr($address, -1) === ':' && \defined('HHVM_VERSION_ID') && \HHVM_VERSION_ID < 31900) {
$address = (string)\substr($address, 0, -1); // @codeCoverageIgnore
}
// work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
// PHP uses "\0" string and HHVM uses empty string (colon removed above)
if ($address === '' || $address[0] === "\x00" ) {
return null; // @codeCoverageIgnore
}
return 'unix://' . $address;
}
// check if this is an IPv6 address which includes multiple colons but no square brackets
$pos = \strrpos($address, ':');
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
}
return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace React\Socket;
use React\Stream\DuplexStreamInterface;
/**
* Any incoming and outgoing connection is represented by this interface,
* such as a normal TCP/IP connection.
*
* An incoming or outgoing connection is a duplex stream (both readable and
* writable) that implements React's
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
* It contains additional properties for the local and remote address (client IP)
* where this connection has been established to/from.
*
* Most commonly, instances implementing this `ConnectionInterface` are emitted
* by all classes implementing the [`ServerInterface`](#serverinterface) and
* used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
*
* Because the `ConnectionInterface` implements the underlying
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
* you can use any of its events and methods as usual:
*
* ```php
* $connection->on('data', function ($chunk) {
* echo $chunk;
* });
*
* $connection->on('end', function () {
* echo 'ended';
* });
*
* $connection->on('error', function (Exception $e) {
* echo 'error: ' . $e->getMessage();
* });
*
* $connection->on('close', function () {
* echo 'closed';
* });
*
* $connection->write($data);
* $connection->end($data = null);
* $connection->close();
* // …
* ```
*
* For more details, see the
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
*
* @see DuplexStreamInterface
* @see ServerInterface
* @see ConnectorInterface
*/
interface ConnectionInterface extends DuplexStreamInterface
{
/**
* Returns the full remote address (URI) where this connection has been established with
*
* ```php
* $address = $connection->getRemoteAddress();
* echo 'Connection with ' . $address . PHP_EOL;
* ```
*
* If the remote address can not be determined or is unknown at this time (such as
* after the connection has been closed), it MAY return a `NULL` value instead.
*
* Otherwise, it will return the full address (URI) as a string value, such
* as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
* `unix://example.sock` or `unix:///path/to/example.sock`.
* Note that individual URI components are application specific and depend
* on the underlying transport protocol.
*
* If this is a TCP/IP based connection and you only want the remote IP, you may
* use something like this:
*
* ```php
* $address = $connection->getRemoteAddress();
* $ip = trim(parse_url($address, PHP_URL_HOST), '[]');
* echo 'Connection with ' . $ip . PHP_EOL;
* ```
*
* @return ?string remote address (URI) or null if unknown
*/
public function getRemoteAddress();
/**
* Returns the full local address (full URI with scheme, IP and port) where this connection has been established with
*
* ```php
* $address = $connection->getLocalAddress();
* echo 'Connection with ' . $address . PHP_EOL;
* ```
*
* If the local address can not be determined or is unknown at this time (such as
* after the connection has been closed), it MAY return a `NULL` value instead.
*
* Otherwise, it will return the full address (URI) as a string value, such
* as `tcp://127.0.0.1:8080`, `tcp://[::1]:80`, `tls://127.0.0.1:443`,
* `unix://example.sock` or `unix:///path/to/example.sock`.
* Note that individual URI components are application specific and depend
* on the underlying transport protocol.
*
* This method complements the [`getRemoteAddress()`](#getremoteaddress) method,
* so they should not be confused.
*
* If your `TcpServer` instance is listening on multiple interfaces (e.g. using
* the address `0.0.0.0`), you can use this method to find out which interface
* actually accepted this connection (such as a public or local interface).
*
* If your system has multiple interfaces (e.g. a WAN and a LAN interface),
* you can use this method to find out which interface was actually
* used for this connection.
*
* @return ?string local address (URI) or null if unknown
* @see self::getRemoteAddress()
*/
public function getLocalAddress();
}

236
vendor/react/socket/src/Connector.php vendored Executable file
View File

@@ -0,0 +1,236 @@
<?php
namespace React\Socket;
use React\Dns\Config\Config as DnsConfig;
use React\Dns\Resolver\Factory as DnsFactory;
use React\Dns\Resolver\ResolverInterface;
use React\EventLoop\LoopInterface;
/**
* The `Connector` class is the main class in this package that implements the
* `ConnectorInterface` and allows you to create streaming connections.
*
* You can use this connector to create any kind of streaming connections, such
* as plaintext TCP/IP, secure TLS or local Unix connection streams.
*
* Under the hood, the `Connector` is implemented as a *higher-level facade*
* for the lower-level connectors implemented in this package. This means it
* also shares all of their features and implementation details.
* If you want to typehint in your higher-level protocol implementation, you SHOULD
* use the generic [`ConnectorInterface`](#connectorinterface) instead.
*
* @see ConnectorInterface for the base interface
*/
final class Connector implements ConnectorInterface
{
private $connectors = array();
/**
* Instantiate new `Connector`
*
* ```php
* $connector = new React\Socket\Connector();
* ```
*
* This class takes two optional arguments for more advanced usage:
*
* ```php
* // constructor signature as of v1.9.0
* $connector = new React\Socket\Connector(array $context = [], ?LoopInterface $loop = null);
*
* // legacy constructor signature before v1.9.0
* $connector = new React\Socket\Connector(?LoopInterface $loop = null, array $context = []);
* ```
*
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
* pass the event loop instance to use for this object. You can use a `null` value
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
* given event loop instance.
*
* @param array|LoopInterface|null $context
* @param null|LoopInterface|array $loop
* @throws \InvalidArgumentException for invalid arguments
*/
public function __construct($context = array(), $loop = null)
{
// swap arguments for legacy constructor signature
if (($context instanceof LoopInterface || $context === null) && (\func_num_args() <= 1 || \is_array($loop))) {
$swap = $loop === null ? array(): $loop;
$loop = $context;
$context = $swap;
}
if (!\is_array($context) || ($loop !== null && !$loop instanceof LoopInterface)) {
throw new \InvalidArgumentException('Expected "array $context" and "?LoopInterface $loop" arguments');
}
// apply default options if not explicitly given
$context += array(
'tcp' => true,
'tls' => true,
'unix' => true,
'dns' => true,
'timeout' => true,
'happy_eyeballs' => true,
);
if ($context['timeout'] === true) {
$context['timeout'] = (float)\ini_get("default_socket_timeout");
}
if ($context['tcp'] instanceof ConnectorInterface) {
$tcp = $context['tcp'];
} else {
$tcp = new TcpConnector(
$loop,
\is_array($context['tcp']) ? $context['tcp'] : array()
);
}
if ($context['dns'] !== false) {
if ($context['dns'] instanceof ResolverInterface) {
$resolver = $context['dns'];
} else {
if ($context['dns'] !== true) {
$config = $context['dns'];
} else {
// try to load nameservers from system config or default to Google's public DNS
$config = DnsConfig::loadSystemConfigBlocking();
if (!$config->nameservers) {
$config->nameservers[] = '8.8.8.8'; // @codeCoverageIgnore
}
}
$factory = new DnsFactory();
$resolver = $factory->createCached(
$config,
$loop
);
}
if ($context['happy_eyeballs'] === true) {
$tcp = new HappyEyeBallsConnector($loop, $tcp, $resolver);
} else {
$tcp = new DnsConnector($tcp, $resolver);
}
}
if ($context['tcp'] !== false) {
$context['tcp'] = $tcp;
if ($context['timeout'] !== false) {
$context['tcp'] = new TimeoutConnector(
$context['tcp'],
$context['timeout'],
$loop
);
}
$this->connectors['tcp'] = $context['tcp'];
}
if ($context['tls'] !== false) {
if (!$context['tls'] instanceof ConnectorInterface) {
$context['tls'] = new SecureConnector(
$tcp,
$loop,
\is_array($context['tls']) ? $context['tls'] : array()
);
}
if ($context['timeout'] !== false) {
$context['tls'] = new TimeoutConnector(
$context['tls'],
$context['timeout'],
$loop
);
}
$this->connectors['tls'] = $context['tls'];
}
if ($context['unix'] !== false) {
if (!$context['unix'] instanceof ConnectorInterface) {
$context['unix'] = new UnixConnector($loop);
}
$this->connectors['unix'] = $context['unix'];
}
}
public function connect($uri)
{
$scheme = 'tcp';
if (\strpos($uri, '://') !== false) {
$scheme = (string)\substr($uri, 0, \strpos($uri, '://'));
}
if (!isset($this->connectors[$scheme])) {
return \React\Promise\reject(new \RuntimeException(
'No connector available for URI scheme "' . $scheme . '" (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}
return $this->connectors[$scheme]->connect($uri);
}
/**
* [internal] Builds on URI from the given URI parts and ip address with original hostname as query
*
* @param array $parts
* @param string $host
* @param string $ip
* @return string
* @internal
*/
public static function uri(array $parts, $host, $ip)
{
$uri = '';
// prepend original scheme if known
if (isset($parts['scheme'])) {
$uri .= $parts['scheme'] . '://';
}
if (\strpos($ip, ':') !== false) {
// enclose IPv6 addresses in square brackets before appending port
$uri .= '[' . $ip . ']';
} else {
$uri .= $ip;
}
// append original port if known
if (isset($parts['port'])) {
$uri .= ':' . $parts['port'];
}
// append orignal path if known
if (isset($parts['path'])) {
$uri .= $parts['path'];
}
// append original query if known
if (isset($parts['query'])) {
$uri .= '?' . $parts['query'];
}
// append original hostname as query if resolved via DNS and if
// destination URI does not contain "hostname" query param already
$args = array();
\parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
if ($host !== $ip && !isset($args['hostname'])) {
$uri .= (isset($parts['query']) ? '&' : '?') . 'hostname=' . \rawurlencode($host);
}
// append original fragment if known
if (isset($parts['fragment'])) {
$uri .= '#' . $parts['fragment'];
}
return $uri;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace React\Socket;
/**
* The `ConnectorInterface` is responsible for providing an interface for
* establishing streaming connections, such as a normal TCP/IP connection.
*
* This is the main interface defined in this package and it is used throughout
* React's vast ecosystem.
*
* Most higher-level components (such as HTTP, database or other networking
* service clients) accept an instance implementing this interface to create their
* TCP/IP connection to the underlying networking service.
* This is usually done via dependency injection, so it's fairly simple to actually
* swap this implementation against any other implementation of this interface.
*
* The interface only offers a single `connect()` method.
*
* @see ConnectionInterface
*/
interface ConnectorInterface
{
/**
* Creates a streaming connection to the given remote address
*
* If returns a Promise which either fulfills with a stream implementing
* `ConnectionInterface` on success or rejects with an `Exception` if the
* connection is not successful.
*
* ```php
* $connector->connect('google.com:443')->then(
* function (React\Socket\ConnectionInterface $connection) {
* // connection successfully established
* },
* function (Exception $error) {
* // failed to connect due to $error
* }
* );
* ```
*
* The returned Promise MUST be implemented in such a way that it can be
* cancelled when it is still pending. Cancelling a pending promise MUST
* reject its value with an Exception. It SHOULD clean up any underlying
* resources and references as applicable.
*
* ```php
* $promise = $connector->connect($uri);
*
* $promise->cancel();
* ```
*
* @param string $uri
* @return \React\Promise\PromiseInterface<ConnectionInterface>
* Resolves with a `ConnectionInterface` on success or rejects with an `Exception` on error.
* @see ConnectionInterface
*/
public function connect($uri);
}

117
vendor/react/socket/src/DnsConnector.php vendored Executable file
View File

@@ -0,0 +1,117 @@
<?php
namespace React\Socket;
use React\Dns\Resolver\ResolverInterface;
use React\Promise;
use React\Promise\PromiseInterface;
final class DnsConnector implements ConnectorInterface
{
private $connector;
private $resolver;
public function __construct(ConnectorInterface $connector, ResolverInterface $resolver)
{
$this->connector = $connector;
$this->resolver = $resolver;
}
public function connect($uri)
{
$original = $uri;
if (\strpos($uri, '://') === false) {
$uri = 'tcp://' . $uri;
$parts = \parse_url($uri);
if (isset($parts['scheme'])) {
unset($parts['scheme']);
}
} else {
$parts = \parse_url($uri);
}
if (!$parts || !isset($parts['host'])) {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $original . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}
$host = \trim($parts['host'], '[]');
$connector = $this->connector;
// skip DNS lookup / URI manipulation if this URI already contains an IP
if (@\inet_pton($host) !== false) {
return $connector->connect($original);
}
$promise = $this->resolver->resolve($host);
$resolved = null;
return new Promise\Promise(
function ($resolve, $reject) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
// resolve/reject with result of DNS lookup
$promise->then(function ($ip) use (&$promise, &$resolved, $uri, $connector, $host, $parts) {
$resolved = $ip;
return $promise = $connector->connect(
Connector::uri($parts, $host, $ip)
)->then(null, function (\Exception $e) use ($uri) {
if ($e instanceof \RuntimeException) {
$message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
$e = new \RuntimeException(
'Connection to ' . $uri . ' failed: ' . $message,
$e->getCode(),
$e
);
// avoid garbage references by replacing all closures in call stack.
// what a lovely piece of code!
$r = new \ReflectionProperty('Exception', 'trace');
$r->setAccessible(true);
$trace = $r->getValue($e);
// Exception trace arguments are not available on some PHP 7.4 installs
// @codeCoverageIgnoreStart
foreach ($trace as $ti => $one) {
if (isset($one['args'])) {
foreach ($one['args'] as $ai => $arg) {
if ($arg instanceof \Closure) {
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
}
}
}
}
// @codeCoverageIgnoreEnd
$r->setValue($e, $trace);
}
throw $e;
});
}, function ($e) use ($uri, $reject) {
$reject(new \RuntimeException('Connection to ' . $uri .' failed during DNS lookup: ' . $e->getMessage(), 0, $e));
})->then($resolve, $reject);
},
function ($_, $reject) use (&$promise, &$resolved, $uri) {
// cancellation should reject connection attempt
// reject DNS resolution with custom reason, otherwise rely on connection cancellation below
if ($resolved === null) {
$reject(new \RuntimeException(
'Connection to ' . $uri . ' cancelled during DNS lookup (ECONNABORTED)',
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
));
}
// (try to) cancel pending DNS lookup / connection attempt
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
// overwrite callback arguments for PHP7+ only, so they do not show
// up in the Exception trace and do not cause a possible cyclic reference.
$_ = $reject = null;
$promise->cancel();
$promise = null;
}
}
);
}
}

222
vendor/react/socket/src/FdServer.php vendored Executable file
View File

@@ -0,0 +1,222 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
/**
* [Internal] The `FdServer` class implements the `ServerInterface` and
* is responsible for accepting connections from an existing file descriptor.
*
* ```php
* $socket = new React\Socket\FdServer(3);
* ```
*
* Whenever a client connects, it will emit a `connection` event with a connection
* instance implementing `ConnectionInterface`:
*
* ```php
* $socket->on('connection', function (ConnectionInterface $connection) {
* echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
* $connection->write('hello there!' . PHP_EOL);
*
* });
* ```
*
* See also the `ServerInterface` for more details.
*
* @see ServerInterface
* @see ConnectionInterface
* @internal
*/
final class FdServer extends EventEmitter implements ServerInterface
{
private $master;
private $loop;
private $unix = false;
private $listening = false;
/**
* Creates a socket server and starts listening on the given file descriptor
*
* This starts accepting new incoming connections on the given file descriptor.
* See also the `connection event` documented in the `ServerInterface`
* for more details.
*
* ```php
* $socket = new React\Socket\FdServer(3);
* ```
*
* If the given FD is invalid or out of range, it will throw an `InvalidArgumentException`:
*
* ```php
* // throws InvalidArgumentException
* $socket = new React\Socket\FdServer(-1);
* ```
*
* If the given FD appears to be valid, but listening on it fails (such as
* if the FD does not exist or does not refer to a socket server), it will
* throw a `RuntimeException`:
*
* ```php
* // throws RuntimeException because FD does not reference a socket server
* $socket = new React\Socket\FdServer(0, $loop);
* ```
*
* Note that these error conditions may vary depending on your system and/or
* configuration.
* See the exception message and code for more details about the actual error
* condition.
*
* @param int|string $fd FD number such as `3` or as URL in the form of `php://fd/3`
* @param ?LoopInterface $loop
* @throws \InvalidArgumentException if the listening address is invalid
* @throws \RuntimeException if listening on this address fails (already in use etc.)
*/
public function __construct($fd, $loop = null)
{
if (\preg_match('#^php://fd/(\d+)$#', $fd, $m)) {
$fd = (int) $m[1];
}
if (!\is_int($fd) || $fd < 0 || $fd >= \PHP_INT_MAX) {
throw new \InvalidArgumentException(
'Invalid FD number given (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->loop = $loop ?: Loop::get();
$errno = 0;
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
// Match errstr from PHP's warning message.
// fopen(php://fd/3): Failed to open stream: Error duping file descriptor 3; possibly it doesn't exist: [9]: Bad file descriptor
\preg_match('/\[(\d+)\]: (.*)/', $error, $m);
$errno = isset($m[1]) ? (int) $m[1] : 0;
$errstr = isset($m[2]) ? $m[2] : $error;
});
$this->master = \fopen('php://fd/' . $fd, 'r+');
\restore_error_handler();
if (false === $this->master) {
throw new \RuntimeException(
'Failed to listen on FD ' . $fd . ': ' . $errstr . SocketServer::errconst($errno),
$errno
);
}
$meta = \stream_get_meta_data($this->master);
if (!isset($meta['stream_type']) || $meta['stream_type'] !== 'tcp_socket') {
\fclose($this->master);
$errno = \defined('SOCKET_ENOTSOCK') ? \SOCKET_ENOTSOCK : 88;
$errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Not a socket';
throw new \RuntimeException(
'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (ENOTSOCK)',
$errno
);
}
// Socket should not have a peer address if this is a listening socket.
// Looks like this work-around is the closest we can get because PHP doesn't expose SO_ACCEPTCONN even with ext-sockets.
if (\stream_socket_get_name($this->master, true) !== false) {
\fclose($this->master);
$errno = \defined('SOCKET_EISCONN') ? \SOCKET_EISCONN : 106;
$errstr = \function_exists('socket_strerror') ? \socket_strerror($errno) : 'Socket is connected';
throw new \RuntimeException(
'Failed to listen on FD ' . $fd . ': ' . $errstr . ' (EISCONN)',
$errno
);
}
// Assume this is a Unix domain socket (UDS) when its listening address doesn't parse as a valid URL with a port.
// Looks like this work-around is the closest we can get because PHP doesn't expose SO_DOMAIN even with ext-sockets.
$this->unix = \parse_url($this->getAddress(), \PHP_URL_PORT) === false;
\stream_set_blocking($this->master, false);
$this->resume();
}
public function getAddress()
{
if (!\is_resource($this->master)) {
return null;
}
$address = \stream_socket_get_name($this->master, false);
if ($this->unix === true) {
return 'unix://' . $address;
}
// check if this is an IPv6 address which includes multiple colons but no square brackets
$pos = \strrpos($address, ':');
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
}
return 'tcp://' . $address;
}
public function pause()
{
if (!$this->listening) {
return;
}
$this->loop->removeReadStream($this->master);
$this->listening = false;
}
public function resume()
{
if ($this->listening || !\is_resource($this->master)) {
return;
}
$that = $this;
$this->loop->addReadStream($this->master, function ($master) use ($that) {
try {
$newSocket = SocketServer::accept($master);
} catch (\RuntimeException $e) {
$that->emit('error', array($e));
return;
}
$that->handleConnection($newSocket);
});
$this->listening = true;
}
public function close()
{
if (!\is_resource($this->master)) {
return;
}
$this->pause();
\fclose($this->master);
$this->removeAllListeners();
}
/** @internal */
public function handleConnection($socket)
{
$connection = new Connection($socket, $this->loop);
$connection->unix = $this->unix;
$this->emit('connection', array($connection));
}
}

41
vendor/react/socket/src/FixedUriConnector.php vendored Executable file
View File

@@ -0,0 +1,41 @@
<?php
namespace React\Socket;
/**
* Decorates an existing Connector to always use a fixed, preconfigured URI
*
* This can be useful for consumers that do not support certain URIs, such as
* when you want to explicitly connect to a Unix domain socket (UDS) path
* instead of connecting to a default address assumed by an higher-level API:
*
* ```php
* $connector = new React\Socket\FixedUriConnector(
* 'unix:///var/run/docker.sock',
* new React\Socket\UnixConnector()
* );
*
* // destination will be ignored, actually connects to Unix domain socket
* $promise = $connector->connect('localhost:80');
* ```
*/
class FixedUriConnector implements ConnectorInterface
{
private $uri;
private $connector;
/**
* @param string $uri
* @param ConnectorInterface $connector
*/
public function __construct($uri, ConnectorInterface $connector)
{
$this->uri = $uri;
$this->connector = $connector;
}
public function connect($_)
{
return $this->connector->connect($this->uri);
}
}

View File

@@ -0,0 +1,334 @@
<?php
namespace React\Socket;
use React\Dns\Model\Message;
use React\Dns\Resolver\ResolverInterface;
use React\EventLoop\LoopInterface;
use React\EventLoop\TimerInterface;
use React\Promise;
use React\Promise\PromiseInterface;
/**
* @internal
*/
final class HappyEyeBallsConnectionBuilder
{
/**
* As long as we haven't connected yet keep popping an IP address of the connect queue until one of them
* succeeds or they all fail. We will wait 100ms between connection attempts as per RFC.
*
* @link https://tools.ietf.org/html/rfc8305#section-5
*/
const CONNECTION_ATTEMPT_DELAY = 0.1;
/**
* Delay `A` lookup by 50ms sending out connection to IPv4 addresses when IPv6 records haven't
* resolved yet as per RFC.
*
* @link https://tools.ietf.org/html/rfc8305#section-3
*/
const RESOLUTION_DELAY = 0.05;
public $loop;
public $connector;
public $resolver;
public $uri;
public $host;
public $resolved = array(
Message::TYPE_A => false,
Message::TYPE_AAAA => false,
);
public $resolverPromises = array();
public $connectionPromises = array();
public $connectQueue = array();
public $nextAttemptTimer;
public $parts;
public $ipsCount = 0;
public $failureCount = 0;
public $resolve;
public $reject;
public $lastErrorFamily;
public $lastError6;
public $lastError4;
public function __construct(LoopInterface $loop, ConnectorInterface $connector, ResolverInterface $resolver, $uri, $host, $parts)
{
$this->loop = $loop;
$this->connector = $connector;
$this->resolver = $resolver;
$this->uri = $uri;
$this->host = $host;
$this->parts = $parts;
}
public function connect()
{
$that = $this;
return new Promise\Promise(function ($resolve, $reject) use ($that) {
$lookupResolve = function ($type) use ($that, $resolve, $reject) {
return function (array $ips) use ($that, $type, $resolve, $reject) {
unset($that->resolverPromises[$type]);
$that->resolved[$type] = true;
$that->mixIpsIntoConnectQueue($ips);
// start next connection attempt if not already awaiting next
if ($that->nextAttemptTimer === null && $that->connectQueue) {
$that->check($resolve, $reject);
}
};
};
$that->resolverPromises[Message::TYPE_AAAA] = $that->resolve(Message::TYPE_AAAA, $reject)->then($lookupResolve(Message::TYPE_AAAA));
$that->resolverPromises[Message::TYPE_A] = $that->resolve(Message::TYPE_A, $reject)->then(function (array $ips) use ($that) {
// happy path: IPv6 has resolved already (or could not resolve), continue with IPv4 addresses
if ($that->resolved[Message::TYPE_AAAA] === true || !$ips) {
return $ips;
}
// Otherwise delay processing IPv4 lookup until short timer passes or IPv6 resolves in the meantime
$deferred = new Promise\Deferred(function () use (&$ips) {
// discard all IPv4 addresses if cancelled
$ips = array();
});
$timer = $that->loop->addTimer($that::RESOLUTION_DELAY, function () use ($deferred, $ips) {
$deferred->resolve($ips);
});
$that->resolverPromises[Message::TYPE_AAAA]->then(function () use ($that, $timer, $deferred, &$ips) {
$that->loop->cancelTimer($timer);
$deferred->resolve($ips);
});
return $deferred->promise();
})->then($lookupResolve(Message::TYPE_A));
}, function ($_, $reject) use ($that) {
$reject(new \RuntimeException(
'Connection to ' . $that->uri . ' cancelled' . (!$that->connectionPromises ? ' during DNS lookup' : '') . ' (ECONNABORTED)',
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
));
$_ = $reject = null;
$that->cleanUp();
});
}
/**
* @internal
* @param int $type DNS query type
* @param callable $reject
* @return \React\Promise\PromiseInterface<string[]> Returns a promise that
* always resolves with a list of IP addresses on success or an empty
* list on error.
*/
public function resolve($type, $reject)
{
$that = $this;
return $that->resolver->resolveAll($that->host, $type)->then(null, function (\Exception $e) use ($type, $reject, $that) {
unset($that->resolverPromises[$type]);
$that->resolved[$type] = true;
if ($type === Message::TYPE_A) {
$that->lastError4 = $e->getMessage();
$that->lastErrorFamily = 4;
} else {
$that->lastError6 = $e->getMessage();
$that->lastErrorFamily = 6;
}
// cancel next attempt timer when there are no more IPs to connect to anymore
if ($that->nextAttemptTimer !== null && !$that->connectQueue) {
$that->loop->cancelTimer($that->nextAttemptTimer);
$that->nextAttemptTimer = null;
}
if ($that->hasBeenResolved() && $that->ipsCount === 0) {
$reject(new \RuntimeException(
$that->error(),
0,
$e
));
}
// Exception already handled above, so don't throw an unhandled rejection here
return array();
});
}
/**
* @internal
*/
public function check($resolve, $reject)
{
$ip = \array_shift($this->connectQueue);
// start connection attempt and remember array position to later unset again
$this->connectionPromises[] = $this->attemptConnection($ip);
\end($this->connectionPromises);
$index = \key($this->connectionPromises);
$that = $this;
$that->connectionPromises[$index]->then(function ($connection) use ($that, $index, $resolve) {
unset($that->connectionPromises[$index]);
$that->cleanUp();
$resolve($connection);
}, function (\Exception $e) use ($that, $index, $ip, $resolve, $reject) {
unset($that->connectionPromises[$index]);
$that->failureCount++;
$message = \preg_replace('/^(Connection to [^ ]+)[&?]hostname=[^ &]+/', '$1', $e->getMessage());
if (\strpos($ip, ':') === false) {
$that->lastError4 = $message;
$that->lastErrorFamily = 4;
} else {
$that->lastError6 = $message;
$that->lastErrorFamily = 6;
}
// start next connection attempt immediately on error
if ($that->connectQueue) {
if ($that->nextAttemptTimer !== null) {
$that->loop->cancelTimer($that->nextAttemptTimer);
$that->nextAttemptTimer = null;
}
$that->check($resolve, $reject);
}
if ($that->hasBeenResolved() === false) {
return;
}
if ($that->ipsCount === $that->failureCount) {
$that->cleanUp();
$reject(new \RuntimeException(
$that->error(),
$e->getCode(),
$e
));
}
});
// Allow next connection attempt in 100ms: https://tools.ietf.org/html/rfc8305#section-5
// Only start timer when more IPs are queued or when DNS query is still pending (might add more IPs)
if ($this->nextAttemptTimer === null && (\count($this->connectQueue) > 0 || $this->resolved[Message::TYPE_A] === false || $this->resolved[Message::TYPE_AAAA] === false)) {
$this->nextAttemptTimer = $this->loop->addTimer(self::CONNECTION_ATTEMPT_DELAY, function () use ($that, $resolve, $reject) {
$that->nextAttemptTimer = null;
if ($that->connectQueue) {
$that->check($resolve, $reject);
}
});
}
}
/**
* @internal
*/
public function attemptConnection($ip)
{
$uri = Connector::uri($this->parts, $this->host, $ip);
return $this->connector->connect($uri);
}
/**
* @internal
*/
public function cleanUp()
{
// clear list of outstanding IPs to avoid creating new connections
$this->connectQueue = array();
// cancel pending connection attempts
foreach ($this->connectionPromises as $connectionPromise) {
if ($connectionPromise instanceof PromiseInterface && \method_exists($connectionPromise, 'cancel')) {
$connectionPromise->cancel();
}
}
// cancel pending DNS resolution (cancel IPv4 first in case it is awaiting IPv6 resolution delay)
foreach (\array_reverse($this->resolverPromises) as $resolverPromise) {
if ($resolverPromise instanceof PromiseInterface && \method_exists($resolverPromise, 'cancel')) {
$resolverPromise->cancel();
}
}
if ($this->nextAttemptTimer instanceof TimerInterface) {
$this->loop->cancelTimer($this->nextAttemptTimer);
$this->nextAttemptTimer = null;
}
}
/**
* @internal
*/
public function hasBeenResolved()
{
foreach ($this->resolved as $typeHasBeenResolved) {
if ($typeHasBeenResolved === false) {
return false;
}
}
return true;
}
/**
* Mixes an array of IP addresses into the connect queue in such a way they alternate when attempting to connect.
* The goal behind it is first attempt to connect to IPv6, then to IPv4, then to IPv6 again until one of those
* attempts succeeds.
*
* @link https://tools.ietf.org/html/rfc8305#section-4
*
* @internal
*/
public function mixIpsIntoConnectQueue(array $ips)
{
\shuffle($ips);
$this->ipsCount += \count($ips);
$connectQueueStash = $this->connectQueue;
$this->connectQueue = array();
while (\count($connectQueueStash) > 0 || \count($ips) > 0) {
if (\count($ips) > 0) {
$this->connectQueue[] = \array_shift($ips);
}
if (\count($connectQueueStash) > 0) {
$this->connectQueue[] = \array_shift($connectQueueStash);
}
}
}
/**
* @internal
* @return string
*/
public function error()
{
if ($this->lastError4 === $this->lastError6) {
$message = $this->lastError6;
} elseif ($this->lastErrorFamily === 6) {
$message = 'Last error for IPv6: ' . $this->lastError6 . '. Previous error for IPv4: ' . $this->lastError4;
} else {
$message = 'Last error for IPv4: ' . $this->lastError4 . '. Previous error for IPv6: ' . $this->lastError6;
}
if ($this->hasBeenResolved() && $this->ipsCount === 0) {
if ($this->lastError6 === $this->lastError4) {
$message = ' during DNS lookup: ' . $this->lastError6;
} else {
$message = ' during DNS lookup. ' . $message;
}
} else {
$message = ': ' . $message;
}
return 'Connection to ' . $this->uri . ' failed' . $message;
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace React\Socket;
use React\Dns\Resolver\ResolverInterface;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise;
final class HappyEyeBallsConnector implements ConnectorInterface
{
private $loop;
private $connector;
private $resolver;
/**
* @param ?LoopInterface $loop
* @param ConnectorInterface $connector
* @param ResolverInterface $resolver
*/
public function __construct($loop = null, $connector = null, $resolver = null)
{
// $connector and $resolver arguments are actually required, marked
// optional for technical reasons only. Nullable $loop without default
// requires PHP 7.1, null default is also supported in legacy PHP
// versions, but required parameters are not allowed after arguments
// with null default. Mark all parameters optional and check accordingly.
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
}
if (!$connector instanceof ConnectorInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($connector) expected React\Socket\ConnectorInterface');
}
if (!$resolver instanceof ResolverInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #3 ($resolver) expected React\Dns\Resolver\ResolverInterface');
}
$this->loop = $loop ?: Loop::get();
$this->connector = $connector;
$this->resolver = $resolver;
}
public function connect($uri)
{
$original = $uri;
if (\strpos($uri, '://') === false) {
$uri = 'tcp://' . $uri;
$parts = \parse_url($uri);
if (isset($parts['scheme'])) {
unset($parts['scheme']);
}
} else {
$parts = \parse_url($uri);
}
if (!$parts || !isset($parts['host'])) {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $original . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}
$host = \trim($parts['host'], '[]');
// skip DNS lookup / URI manipulation if this URI already contains an IP
if (@\inet_pton($host) !== false) {
return $this->connector->connect($original);
}
$builder = new HappyEyeBallsConnectionBuilder(
$this->loop,
$this->connector,
$this->resolver,
$uri,
$host,
$parts
);
return $builder->connect();
}
}

203
vendor/react/socket/src/LimitingServer.php vendored Executable file
View File

@@ -0,0 +1,203 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use Exception;
use OverflowException;
/**
* The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
* for limiting and keeping track of open connections to this server instance.
*
* Whenever the underlying server emits a `connection` event, it will check its
* limits and then either
* - keep track of this connection by adding it to the list of
* open connections and then forward the `connection` event
* - or reject (close) the connection when its limits are exceeded and will
* forward an `error` event instead.
*
* Whenever a connection closes, it will remove this connection from the list of
* open connections.
*
* ```php
* $server = new React\Socket\LimitingServer($server, 100);
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
* $connection->write('hello there!' . PHP_EOL);
*
* });
* ```
*
* See also the `ServerInterface` for more details.
*
* @see ServerInterface
* @see ConnectionInterface
*/
class LimitingServer extends EventEmitter implements ServerInterface
{
private $connections = array();
private $server;
private $limit;
private $pauseOnLimit = false;
private $autoPaused = false;
private $manuPaused = false;
/**
* Instantiates a new LimitingServer.
*
* You have to pass a maximum number of open connections to ensure
* the server will automatically reject (close) connections once this limit
* is exceeded. In this case, it will emit an `error` event to inform about
* this and no `connection` event will be emitted.
*
* ```php
* $server = new React\Socket\LimitingServer($server, 100);
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
* $connection->write('hello there!' . PHP_EOL);
*
* });
* ```
*
* You MAY pass a `null` limit in order to put no limit on the number of
* open connections and keep accepting new connection until you run out of
* operating system resources (such as open file handles). This may be
* useful if you do not want to take care of applying a limit but still want
* to use the `getConnections()` method.
*
* You can optionally configure the server to pause accepting new
* connections once the connection limit is reached. In this case, it will
* pause the underlying server and no longer process any new connections at
* all, thus also no longer closing any excessive connections.
* The underlying operating system is responsible for keeping a backlog of
* pending connections until its limit is reached, at which point it will
* start rejecting further connections.
* Once the server is below the connection limit, it will continue consuming
* connections from the backlog and will process any outstanding data on
* each connection.
* This mode may be useful for some protocols that are designed to wait for
* a response message (such as HTTP), but may be less useful for other
* protocols that demand immediate responses (such as a "welcome" message in
* an interactive chat).
*
* ```php
* $server = new React\Socket\LimitingServer($server, 100, true);
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
* $connection->write('hello there!' . PHP_EOL);
*
* });
* ```
*
* @param ServerInterface $server
* @param int|null $connectionLimit
* @param bool $pauseOnLimit
*/
public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
{
$this->server = $server;
$this->limit = $connectionLimit;
if ($connectionLimit !== null) {
$this->pauseOnLimit = $pauseOnLimit;
}
$this->server->on('connection', array($this, 'handleConnection'));
$this->server->on('error', array($this, 'handleError'));
}
/**
* Returns an array with all currently active connections
*
* ```php
* foreach ($server->getConnection() as $connection) {
* $connection->write('Hi!');
* }
* ```
*
* @return ConnectionInterface[]
*/
public function getConnections()
{
return $this->connections;
}
public function getAddress()
{
return $this->server->getAddress();
}
public function pause()
{
if (!$this->manuPaused) {
$this->manuPaused = true;
if (!$this->autoPaused) {
$this->server->pause();
}
}
}
public function resume()
{
if ($this->manuPaused) {
$this->manuPaused = false;
if (!$this->autoPaused) {
$this->server->resume();
}
}
}
public function close()
{
$this->server->close();
}
/** @internal */
public function handleConnection(ConnectionInterface $connection)
{
// close connection if limit exceeded
if ($this->limit !== null && \count($this->connections) >= $this->limit) {
$this->handleError(new \OverflowException('Connection closed because server reached connection limit'));
$connection->close();
return;
}
$this->connections[] = $connection;
$that = $this;
$connection->on('close', function () use ($that, $connection) {
$that->handleDisconnection($connection);
});
// pause accepting new connections if limit exceeded
if ($this->pauseOnLimit && !$this->autoPaused && \count($this->connections) >= $this->limit) {
$this->autoPaused = true;
if (!$this->manuPaused) {
$this->server->pause();
}
}
$this->emit('connection', array($connection));
}
/** @internal */
public function handleDisconnection(ConnectionInterface $connection)
{
unset($this->connections[\array_search($connection, $this->connections)]);
// continue accepting new connection if below limit
if ($this->autoPaused && \count($this->connections) < $this->limit) {
$this->autoPaused = false;
if (!$this->manuPaused) {
$this->server->resume();
}
}
}
/** @internal */
public function handleError(\Exception $error)
{
$this->emit('error', array($error));
}
}

132
vendor/react/socket/src/SecureConnector.php vendored Executable file
View File

@@ -0,0 +1,132 @@
<?php
namespace React\Socket;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise;
use BadMethodCallException;
use InvalidArgumentException;
use UnexpectedValueException;
final class SecureConnector implements ConnectorInterface
{
private $connector;
private $streamEncryption;
private $context;
/**
* @param ConnectorInterface $connector
* @param ?LoopInterface $loop
* @param array $context
*/
public function __construct(ConnectorInterface $connector, $loop = null, array $context = array())
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->connector = $connector;
$this->streamEncryption = new StreamEncryption($loop ?: Loop::get(), false);
$this->context = $context;
}
public function connect($uri)
{
if (!\function_exists('stream_socket_enable_crypto')) {
return Promise\reject(new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)')); // @codeCoverageIgnore
}
if (\strpos($uri, '://') === false) {
$uri = 'tls://' . $uri;
}
$parts = \parse_url($uri);
if (!$parts || !isset($parts['scheme']) || $parts['scheme'] !== 'tls') {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $uri . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}
$context = $this->context;
$encryption = $this->streamEncryption;
$connected = false;
/** @var \React\Promise\PromiseInterface<ConnectionInterface> $promise */
$promise = $this->connector->connect(
\str_replace('tls://', '', $uri)
)->then(function (ConnectionInterface $connection) use ($context, $encryption, $uri, &$promise, &$connected) {
// (unencrypted) TCP/IP connection succeeded
$connected = true;
if (!$connection instanceof Connection) {
$connection->close();
throw new \UnexpectedValueException('Base connector does not use internal Connection class exposing stream resource');
}
// set required SSL/TLS context options
foreach ($context as $name => $value) {
\stream_context_set_option($connection->stream, 'ssl', $name, $value);
}
// try to enable encryption
return $promise = $encryption->enable($connection)->then(null, function ($error) use ($connection, $uri) {
// establishing encryption failed => close invalid connection and return error
$connection->close();
throw new \RuntimeException(
'Connection to ' . $uri . ' failed during TLS handshake: ' . $error->getMessage(),
$error->getCode()
);
});
}, function (\Exception $e) use ($uri) {
if ($e instanceof \RuntimeException) {
$message = \preg_replace('/^Connection to [^ ]+/', '', $e->getMessage());
$e = new \RuntimeException(
'Connection to ' . $uri . $message,
$e->getCode(),
$e
);
// avoid garbage references by replacing all closures in call stack.
// what a lovely piece of code!
$r = new \ReflectionProperty('Exception', 'trace');
$r->setAccessible(true);
$trace = $r->getValue($e);
// Exception trace arguments are not available on some PHP 7.4 installs
// @codeCoverageIgnoreStart
foreach ($trace as $ti => $one) {
if (isset($one['args'])) {
foreach ($one['args'] as $ai => $arg) {
if ($arg instanceof \Closure) {
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
}
}
}
}
// @codeCoverageIgnoreEnd
$r->setValue($e, $trace);
}
throw $e;
});
return new \React\Promise\Promise(
function ($resolve, $reject) use ($promise) {
$promise->then($resolve, $reject);
},
function ($_, $reject) use (&$promise, $uri, &$connected) {
if ($connected) {
$reject(new \RuntimeException(
'Connection to ' . $uri . ' cancelled during TLS handshake (ECONNABORTED)',
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
));
}
$promise->cancel();
$promise = null;
}
);
}
}

210
vendor/react/socket/src/SecureServer.php vendored Executable file
View File

@@ -0,0 +1,210 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use BadMethodCallException;
use UnexpectedValueException;
/**
* The `SecureServer` class implements the `ServerInterface` and is responsible
* for providing a secure TLS (formerly known as SSL) server.
*
* It does so by wrapping a `TcpServer` instance which waits for plaintext
* TCP/IP connections and then performs a TLS handshake for each connection.
*
* ```php
* $server = new React\Socket\TcpServer(8000);
* $server = new React\Socket\SecureServer($server, null, array(
* // tls context options here…
* ));
* ```
*
* Whenever a client completes the TLS handshake, it will emit a `connection` event
* with a connection instance implementing [`ConnectionInterface`](#connectioninterface):
*
* ```php
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
* echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;
*
* $connection->write('hello there!' . PHP_EOL);
*
* });
* ```
*
* Whenever a client fails to perform a successful TLS handshake, it will emit an
* `error` event and then close the underlying TCP/IP connection:
*
* ```php
* $server->on('error', function (Exception $e) {
* echo 'Error' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* See also the `ServerInterface` for more details.
*
* Note that the `SecureServer` class is a concrete implementation for TLS sockets.
* If you want to typehint in your higher-level protocol implementation, you SHOULD
* use the generic `ServerInterface` instead.
*
* @see ServerInterface
* @see ConnectionInterface
*/
final class SecureServer extends EventEmitter implements ServerInterface
{
private $tcp;
private $encryption;
private $context;
/**
* Creates a secure TLS server and starts waiting for incoming connections
*
* It does so by wrapping a `TcpServer` instance which waits for plaintext
* TCP/IP connections and then performs a TLS handshake for each connection.
* It thus requires valid [TLS context options],
* which in its most basic form may look something like this if you're using a
* PEM encoded certificate file:
*
* ```php
* $server = new React\Socket\TcpServer(8000);
* $server = new React\Socket\SecureServer($server, null, array(
* 'local_cert' => 'server.pem'
* ));
* ```
*
* Note that the certificate file will not be loaded on instantiation but when an
* incoming connection initializes its TLS context.
* This implies that any invalid certificate file paths or contents will only cause
* an `error` event at a later time.
*
* If your private key is encrypted with a passphrase, you have to specify it
* like this:
*
* ```php
* $server = new React\Socket\TcpServer(8000);
* $server = new React\Socket\SecureServer($server, null, array(
* 'local_cert' => 'server.pem',
* 'passphrase' => 'secret'
* ));
* ```
*
* Note that available [TLS context options],
* their defaults and effects of changing these may vary depending on your system
* and/or PHP version.
* Passing unknown context options has no effect.
*
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
* pass the event loop instance to use for this object. You can use a `null` value
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
* given event loop instance.
*
* Advanced usage: Despite allowing any `ServerInterface` as first parameter,
* you SHOULD pass a `TcpServer` instance as first parameter, unless you
* know what you're doing.
* Internally, the `SecureServer` has to set the required TLS context options on
* the underlying stream resources.
* These resources are not exposed through any of the interfaces defined in this
* package, but only through the internal `Connection` class.
* The `TcpServer` class is guaranteed to emit connections that implement
* the `ConnectionInterface` and uses the internal `Connection` class in order to
* expose these underlying resources.
* If you use a custom `ServerInterface` and its `connection` event does not
* meet this requirement, the `SecureServer` will emit an `error` event and
* then close the underlying connection.
*
* @param ServerInterface|TcpServer $tcp
* @param ?LoopInterface $loop
* @param array $context
* @throws BadMethodCallException for legacy HHVM < 3.8 due to lack of support
* @see TcpServer
* @link https://www.php.net/manual/en/context.ssl.php for TLS context options
*/
public function __construct(ServerInterface $tcp, $loop = null, array $context = array())
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
if (!\function_exists('stream_socket_enable_crypto')) {
throw new \BadMethodCallException('Encryption not supported on your platform (HHVM < 3.8?)'); // @codeCoverageIgnore
}
// default to empty passphrase to suppress blocking passphrase prompt
$context += array(
'passphrase' => ''
);
$this->tcp = $tcp;
$this->encryption = new StreamEncryption($loop ?: Loop::get());
$this->context = $context;
$that = $this;
$this->tcp->on('connection', function ($connection) use ($that) {
$that->handleConnection($connection);
});
$this->tcp->on('error', function ($error) use ($that) {
$that->emit('error', array($error));
});
}
public function getAddress()
{
$address = $this->tcp->getAddress();
if ($address === null) {
return null;
}
return \str_replace('tcp://' , 'tls://', $address);
}
public function pause()
{
$this->tcp->pause();
}
public function resume()
{
$this->tcp->resume();
}
public function close()
{
return $this->tcp->close();
}
/** @internal */
public function handleConnection(ConnectionInterface $connection)
{
if (!$connection instanceof Connection) {
$this->emit('error', array(new \UnexpectedValueException('Base server does not use internal Connection class exposing stream resource')));
$connection->close();
return;
}
foreach ($this->context as $name => $value) {
\stream_context_set_option($connection->stream, 'ssl', $name, $value);
}
// get remote address before starting TLS handshake in case connection closes during handshake
$remote = $connection->getRemoteAddress();
$that = $this;
$this->encryption->enable($connection)->then(
function ($conn) use ($that) {
$that->emit('connection', array($conn));
},
function ($error) use ($that, $connection, $remote) {
$error = new \RuntimeException(
'Connection from ' . $remote . ' failed during TLS handshake: ' . $error->getMessage(),
$error->getCode()
);
$that->emit('error', array($error));
$connection->close();
}
);
}
}

118
vendor/react/socket/src/Server.php vendored Executable file
View File

@@ -0,0 +1,118 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use Exception;
/**
* @deprecated 1.9.0 See `SocketServer` instead
* @see SocketServer
*/
final class Server extends EventEmitter implements ServerInterface
{
private $server;
/**
* [Deprecated] `Server`
*
* This class exists for BC reasons only and should not be used anymore.
*
* ```php
* // deprecated
* $socket = new React\Socket\Server(0);
* $socket = new React\Socket\Server('127.0.0.1:8000');
* $socket = new React\Socket\Server('127.0.0.1:8000', null, $context);
* $socket = new React\Socket\Server('127.0.0.1:8000', $loop, $context);
*
* // new
* $socket = new React\Socket\SocketServer('127.0.0.1:0');
* $socket = new React\Socket\SocketServer('127.0.0.1:8000');
* $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
* $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context, $loop);
* ```
*
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
* pass the event loop instance to use for this object. You can use a `null` value
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
* given event loop instance.
*
* For BC reasons, you can also pass the TCP socket context options as a simple
* array without wrapping this in another array under the `tcp` key.
*
* @param string|int $uri
* @param ?LoopInterface $loop
* @param array $context
* @deprecated 1.9.0 See `SocketServer` instead
* @see SocketServer
*/
public function __construct($uri, $loop = null, array $context = array())
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
$loop = $loop ?: Loop::get();
// sanitize TCP context options if not properly wrapped
if ($context && (!isset($context['tcp']) && !isset($context['tls']) && !isset($context['unix']))) {
$context = array('tcp' => $context);
}
// apply default options if not explicitly given
$context += array(
'tcp' => array(),
'tls' => array(),
'unix' => array()
);
$scheme = 'tcp';
$pos = \strpos($uri, '://');
if ($pos !== false) {
$scheme = \substr($uri, 0, $pos);
}
if ($scheme === 'unix') {
$server = new UnixServer($uri, $loop, $context['unix']);
} else {
$server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
if ($scheme === 'tls') {
$server = new SecureServer($server, $loop, $context['tls']);
}
}
$this->server = $server;
$that = $this;
$server->on('connection', function (ConnectionInterface $conn) use ($that) {
$that->emit('connection', array($conn));
});
$server->on('error', function (Exception $error) use ($that) {
$that->emit('error', array($error));
});
}
public function getAddress()
{
return $this->server->getAddress();
}
public function pause()
{
$this->server->pause();
}
public function resume()
{
$this->server->resume();
}
public function close()
{
$this->server->close();
}
}

151
vendor/react/socket/src/ServerInterface.php vendored Executable file
View File

@@ -0,0 +1,151 @@
<?php
namespace React\Socket;
use Evenement\EventEmitterInterface;
/**
* The `ServerInterface` is responsible for providing an interface for accepting
* incoming streaming connections, such as a normal TCP/IP connection.
*
* Most higher-level components (such as a HTTP server) accept an instance
* implementing this interface to accept incoming streaming connections.
* This is usually done via dependency injection, so it's fairly simple to actually
* swap this implementation against any other implementation of this interface.
* This means that you SHOULD typehint against this interface instead of a concrete
* implementation of this interface.
*
* Besides defining a few methods, this interface also implements the
* `EventEmitterInterface` which allows you to react to certain events:
*
* connection event:
* The `connection` event will be emitted whenever a new connection has been
* established, i.e. a new client connects to this server socket:
*
* ```php
* $socket->on('connection', function (React\Socket\ConnectionInterface $connection) {
* echo 'new connection' . PHP_EOL;
* });
* ```
*
* See also the `ConnectionInterface` for more details about handling the
* incoming connection.
*
* error event:
* The `error` event will be emitted whenever there's an error accepting a new
* connection from a client.
*
* ```php
* $socket->on('error', function (Exception $e) {
* echo 'error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* Note that this is not a fatal error event, i.e. the server keeps listening for
* new connections even after this event.
*
* @see ConnectionInterface
*/
interface ServerInterface extends EventEmitterInterface
{
/**
* Returns the full address (URI) this server is currently listening on
*
* ```php
* $address = $socket->getAddress();
* echo 'Server listening on ' . $address . PHP_EOL;
* ```
*
* If the address can not be determined or is unknown at this time (such as
* after the socket has been closed), it MAY return a `NULL` value instead.
*
* Otherwise, it will return the full address (URI) as a string value, such
* as `tcp://127.0.0.1:8080`, `tcp://[::1]:80` or `tls://127.0.0.1:443`.
* Note that individual URI components are application specific and depend
* on the underlying transport protocol.
*
* If this is a TCP/IP based server and you only want the local port, you may
* use something like this:
*
* ```php
* $address = $socket->getAddress();
* $port = parse_url($address, PHP_URL_PORT);
* echo 'Server listening on port ' . $port . PHP_EOL;
* ```
*
* @return ?string the full listening address (URI) or NULL if it is unknown (not applicable to this server socket or already closed)
*/
public function getAddress();
/**
* Pauses accepting new incoming connections.
*
* Removes the socket resource from the EventLoop and thus stop accepting
* new connections. Note that the listening socket stays active and is not
* closed.
*
* This means that new incoming connections will stay pending in the
* operating system backlog until its configurable backlog is filled.
* Once the backlog is filled, the operating system may reject further
* incoming connections until the backlog is drained again by resuming
* to accept new connections.
*
* Once the server is paused, no futher `connection` events SHOULD
* be emitted.
*
* ```php
* $socket->pause();
*
* $socket->on('connection', assertShouldNeverCalled());
* ```
*
* This method is advisory-only, though generally not recommended, the
* server MAY continue emitting `connection` events.
*
* Unless otherwise noted, a successfully opened server SHOULD NOT start
* in paused state.
*
* You can continue processing events by calling `resume()` again.
*
* Note that both methods can be called any number of times, in particular
* calling `pause()` more than once SHOULD NOT have any effect.
* Similarly, calling this after `close()` is a NO-OP.
*
* @see self::resume()
* @return void
*/
public function pause();
/**
* Resumes accepting new incoming connections.
*
* Re-attach the socket resource to the EventLoop after a previous `pause()`.
*
* ```php
* $socket->pause();
*
* Loop::addTimer(1.0, function () use ($socket) {
* $socket->resume();
* });
* ```
*
* Note that both methods can be called any number of times, in particular
* calling `resume()` without a prior `pause()` SHOULD NOT have any effect.
* Similarly, calling this after `close()` is a NO-OP.
*
* @see self::pause()
* @return void
*/
public function resume();
/**
* Shuts down this listening socket
*
* This will stop listening for new incoming connections on this socket.
*
* Calling this method more than once on the same instance is a NO-OP.
*
* @return void
*/
public function close();
}

215
vendor/react/socket/src/SocketServer.php vendored Executable file
View File

@@ -0,0 +1,215 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use React\EventLoop\LoopInterface;
final class SocketServer extends EventEmitter implements ServerInterface
{
private $server;
/**
* The `SocketServer` class is the main class in this package that implements the `ServerInterface` and
* allows you to accept incoming streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
*
* ```php
* $socket = new React\Socket\SocketServer('127.0.0.1:0');
* $socket = new React\Socket\SocketServer('127.0.0.1:8000');
* $socket = new React\Socket\SocketServer('127.0.0.1:8000', $context);
* ```
*
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
* pass the event loop instance to use for this object. You can use a `null` value
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
* given event loop instance.
*
* @param string $uri
* @param array $context
* @param ?LoopInterface $loop
* @throws \InvalidArgumentException if the listening address is invalid
* @throws \RuntimeException if listening on this address fails (already in use etc.)
*/
public function __construct($uri, array $context = array(), $loop = null)
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
}
// apply default options if not explicitly given
$context += array(
'tcp' => array(),
'tls' => array(),
'unix' => array()
);
$scheme = 'tcp';
$pos = \strpos($uri, '://');
if ($pos !== false) {
$scheme = \substr($uri, 0, $pos);
}
if ($scheme === 'unix') {
$server = new UnixServer($uri, $loop, $context['unix']);
} elseif ($scheme === 'php') {
$server = new FdServer($uri, $loop);
} else {
if (preg_match('#^(?:\w+://)?\d+$#', $uri)) {
throw new \InvalidArgumentException(
'Invalid URI given (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}
$server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
if ($scheme === 'tls') {
$server = new SecureServer($server, $loop, $context['tls']);
}
}
$this->server = $server;
$that = $this;
$server->on('connection', function (ConnectionInterface $conn) use ($that) {
$that->emit('connection', array($conn));
});
$server->on('error', function (\Exception $error) use ($that) {
$that->emit('error', array($error));
});
}
public function getAddress()
{
return $this->server->getAddress();
}
public function pause()
{
$this->server->pause();
}
public function resume()
{
$this->server->resume();
}
public function close()
{
$this->server->close();
}
/**
* [internal] Internal helper method to accept new connection from given server socket
*
* @param resource $socket server socket to accept connection from
* @return resource new client socket if any
* @throws \RuntimeException if accepting fails
* @internal
*/
public static function accept($socket)
{
$errno = 0;
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
// Match errstr from PHP's warning message.
// stream_socket_accept(): accept failed: Connection timed out
$errstr = \preg_replace('#.*: #', '', $error);
$errno = SocketServer::errno($errstr);
});
$newSocket = \stream_socket_accept($socket, 0);
\restore_error_handler();
if (false === $newSocket) {
throw new \RuntimeException(
'Unable to accept new connection: ' . $errstr . self::errconst($errno),
$errno
);
}
return $newSocket;
}
/**
* [Internal] Returns errno value for given errstr
*
* The errno and errstr values describes the type of error that has been
* encountered. This method tries to look up the given errstr and find a
* matching errno value which can be useful to provide more context to error
* messages. It goes through the list of known errno constants when either
* `ext-sockets`, `ext-posix` or `ext-pcntl` is available to find an errno
* matching the given errstr.
*
* @param string $errstr
* @return int errno value (e.g. value of `SOCKET_ECONNREFUSED`) or 0 if not found
* @internal
* @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission
* @codeCoverageIgnore
*/
public static function errno($errstr)
{
// PHP defines the required `strerror()` function through either `ext-sockets`, `ext-posix` or `ext-pcntl`
$strerror = \function_exists('socket_strerror') ? 'socket_strerror' : (\function_exists('posix_strerror') ? 'posix_strerror' : (\function_exists('pcntl_strerror') ? 'pcntl_strerror' : null));
if ($strerror !== null) {
assert(\is_string($strerror) && \is_callable($strerror));
// PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED`
// PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE`
// go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errstr`
foreach (\get_defined_constants(false) as $name => $value) {
if (\is_int($value) && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0) && $strerror($value) === $errstr) {
return $value;
}
}
// if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available)
// go through list of all possible errno values from 1 to `MAX_ERRNO` and see if they match the given `$errstr`
for ($errno = 1, $max = \defined('MAX_ERRNO') ? \MAX_ERRNO : 4095; $errno <= $max; ++$errno) {
if ($strerror($errno) === $errstr) {
return $errno;
}
}
}
// if we reach this, no matching errno value could be found (unlikely when either `ext-sockets`, `ext-posix` or `ext-pcntl` is available)
return 0;
}
/**
* [Internal] Returns errno constant name for given errno value
*
* The errno value describes the type of error that has been encountered.
* This method tries to look up the given errno value and find a matching
* errno constant name which can be useful to provide more context and more
* descriptive error messages. It goes through the list of known errno
* constants when either `ext-sockets` or `ext-pcntl` is available to find
* the matching errno constant name.
*
* Because this method is used to append more context to error messages, the
* constant name will be prefixed with a space and put between parenthesis
* when found.
*
* @param int $errno
* @return string e.g. ` (ECONNREFUSED)` or empty string if no matching const for the given errno could be found
* @internal
* @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/errno with permission
* @codeCoverageIgnore
*/
public static function errconst($errno)
{
// PHP defines most useful errno constants like `ECONNREFUSED` through constants in `ext-sockets` like `SOCKET_ECONNREFUSED`
// PHP also defines a hand full of errno constants like `EMFILE` through constants in `ext-pcntl` like `PCNTL_EMFILE`
// go through list of all defined constants like `SOCKET_E*` and `PCNTL_E*` and see if they match the given `$errno`
foreach (\get_defined_constants(false) as $name => $value) {
if ($value === $errno && (\strpos($name, 'SOCKET_E') === 0 || \strpos($name, 'PCNTL_E') === 0)) {
return ' (' . \substr($name, \strpos($name, '_') + 1) . ')';
}
}
// if we reach this, no matching errno constant could be found (unlikely when `ext-sockets` is available)
return '';
}
}

158
vendor/react/socket/src/StreamEncryption.php vendored Executable file
View File

@@ -0,0 +1,158 @@
<?php
namespace React\Socket;
use React\EventLoop\LoopInterface;
use React\Promise\Deferred;
use RuntimeException;
use UnexpectedValueException;
/**
* This class is considered internal and its API should not be relied upon
* outside of Socket.
*
* @internal
*/
class StreamEncryption
{
private $loop;
private $method;
private $server;
public function __construct(LoopInterface $loop, $server = true)
{
$this->loop = $loop;
$this->server = $server;
// support TLSv1.0+ by default and exclude legacy SSLv2/SSLv3.
// As of PHP 7.2+ the main crypto method constant includes all TLS versions.
// As of PHP 5.6+ the crypto method is a bitmask, so we explicitly include all TLS versions.
// For legacy PHP < 5.6 the crypto method is a single value only and this constant includes all TLS versions.
// @link https://3v4l.org/9PSST
if ($server) {
$this->method = \STREAM_CRYPTO_METHOD_TLS_SERVER;
if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | \STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; // @codeCoverageIgnore
}
} else {
$this->method = \STREAM_CRYPTO_METHOD_TLS_CLIENT;
if (\PHP_VERSION_ID < 70200 && \PHP_VERSION_ID >= 50600) {
$this->method |= \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; // @codeCoverageIgnore
}
}
}
/**
* @param Connection $stream
* @return \React\Promise\PromiseInterface<Connection>
*/
public function enable(Connection $stream)
{
return $this->toggle($stream, true);
}
/**
* @param Connection $stream
* @param bool $toggle
* @return \React\Promise\PromiseInterface<Connection>
*/
public function toggle(Connection $stream, $toggle)
{
// pause actual stream instance to continue operation on raw stream socket
$stream->pause();
// TODO: add write() event to make sure we're not sending any excessive data
// cancelling this leaves this stream in an inconsistent state…
$deferred = new Deferred(function () {
throw new \RuntimeException();
});
// get actual stream socket from stream instance
$socket = $stream->stream;
// get crypto method from context options or use global setting from constructor
$method = $this->method;
$context = \stream_context_get_options($socket);
if (isset($context['ssl']['crypto_method'])) {
$method = $context['ssl']['crypto_method'];
}
$that = $this;
$toggleCrypto = function () use ($socket, $deferred, $toggle, $method, $that) {
$that->toggleCrypto($socket, $deferred, $toggle, $method);
};
$this->loop->addReadStream($socket, $toggleCrypto);
if (!$this->server) {
$toggleCrypto();
}
$loop = $this->loop;
return $deferred->promise()->then(function () use ($stream, $socket, $loop, $toggle) {
$loop->removeReadStream($socket);
$stream->encryptionEnabled = $toggle;
$stream->resume();
return $stream;
}, function($error) use ($stream, $socket, $loop) {
$loop->removeReadStream($socket);
$stream->resume();
throw $error;
});
}
/**
* @internal
* @param resource $socket
* @param Deferred<null> $deferred
* @param bool $toggle
* @param int $method
* @return void
*/
public function toggleCrypto($socket, Deferred $deferred, $toggle, $method)
{
$error = null;
\set_error_handler(function ($_, $errstr) use (&$error) {
$error = \str_replace(array("\r", "\n"), ' ', $errstr);
// remove useless function name from error message
if (($pos = \strpos($error, "): ")) !== false) {
$error = \substr($error, $pos + 3);
}
});
$result = \stream_socket_enable_crypto($socket, $toggle, $method);
\restore_error_handler();
if (true === $result) {
$deferred->resolve(null);
} else if (false === $result) {
// overwrite callback arguments for PHP7+ only, so they do not show
// up in the Exception trace and do not cause a possible cyclic reference.
$d = $deferred;
$deferred = null;
if (\feof($socket) || $error === null) {
// EOF or failed without error => connection closed during handshake
$d->reject(new \UnexpectedValueException(
'Connection lost during TLS handshake (ECONNRESET)',
\defined('SOCKET_ECONNRESET') ? \SOCKET_ECONNRESET : 104
));
} else {
// handshake failed with error message
$d->reject(new \UnexpectedValueException(
$error
));
}
} else {
// need more data, will retry
}
}
}

173
vendor/react/socket/src/TcpConnector.php vendored Executable file
View File

@@ -0,0 +1,173 @@
<?php
namespace React\Socket;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise;
use InvalidArgumentException;
use RuntimeException;
final class TcpConnector implements ConnectorInterface
{
private $loop;
private $context;
/**
* @param ?LoopInterface $loop
* @param array $context
*/
public function __construct($loop = null, array $context = array())
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->loop = $loop ?: Loop::get();
$this->context = $context;
}
public function connect($uri)
{
if (\strpos($uri, '://') === false) {
$uri = 'tcp://' . $uri;
}
$parts = \parse_url($uri);
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $uri . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}
$ip = \trim($parts['host'], '[]');
if (@\inet_pton($ip) === false) {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}
// use context given in constructor
$context = array(
'socket' => $this->context
);
// parse arguments from query component of URI
$args = array();
if (isset($parts['query'])) {
\parse_str($parts['query'], $args);
}
// If an original hostname has been given, use this for TLS setup.
// This can happen due to layers of nested connectors, such as a
// DnsConnector reporting its original hostname.
// These context options are here in case TLS is enabled later on this stream.
// If TLS is not enabled later, this doesn't hurt either.
if (isset($args['hostname'])) {
$context['ssl'] = array(
'SNI_enabled' => true,
'peer_name' => $args['hostname']
);
// Legacy PHP < 5.6 ignores peer_name and requires legacy context options instead.
// The SNI_server_name context option has to be set here during construction,
// as legacy PHP ignores any values set later.
// @codeCoverageIgnoreStart
if (\PHP_VERSION_ID < 50600) {
$context['ssl'] += array(
'SNI_server_name' => $args['hostname'],
'CN_match' => $args['hostname']
);
}
// @codeCoverageIgnoreEnd
}
// latest versions of PHP no longer accept any other URI components and
// HHVM fails to parse URIs with a query but no path, so let's simplify our URI here
$remote = 'tcp://' . $parts['host'] . ':' . $parts['port'];
$stream = @\stream_socket_client(
$remote,
$errno,
$errstr,
0,
\STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT,
\stream_context_create($context)
);
if (false === $stream) {
return Promise\reject(new \RuntimeException(
'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno),
$errno
));
}
// wait for connection
$loop = $this->loop;
return new Promise\Promise(function ($resolve, $reject) use ($loop, $stream, $uri) {
$loop->addWriteStream($stream, function ($stream) use ($loop, $resolve, $reject, $uri) {
$loop->removeWriteStream($stream);
// The following hack looks like the only way to
// detect connection refused errors with PHP's stream sockets.
if (false === \stream_socket_get_name($stream, true)) {
// If we reach this point, we know the connection is dead, but we don't know the underlying error condition.
// @codeCoverageIgnoreStart
if (\function_exists('socket_import_stream')) {
// actual socket errno and errstr can be retrieved with ext-sockets on PHP 5.4+
$socket = \socket_import_stream($stream);
$errno = \socket_get_option($socket, \SOL_SOCKET, \SO_ERROR);
$errstr = \socket_strerror($errno);
} elseif (\PHP_OS === 'Linux') {
// Linux reports socket errno and errstr again when trying to write to the dead socket.
// Suppress error reporting to get error message below and close dead socket before rejecting.
// This is only known to work on Linux, Mac and Windows are known to not support this.
$errno = 0;
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
// Match errstr from PHP's warning message.
// fwrite(): send of 1 bytes failed with errno=111 Connection refused
\preg_match('/errno=(\d+) (.+)/', $error, $m);
$errno = isset($m[1]) ? (int) $m[1] : 0;
$errstr = isset($m[2]) ? $m[2] : $error;
});
\fwrite($stream, \PHP_EOL);
\restore_error_handler();
} else {
// Not on Linux and ext-sockets not available? Too bad.
$errno = \defined('SOCKET_ECONNREFUSED') ? \SOCKET_ECONNREFUSED : 111;
$errstr = 'Connection refused?';
}
// @codeCoverageIgnoreEnd
\fclose($stream);
$reject(new \RuntimeException(
'Connection to ' . $uri . ' failed: ' . $errstr . SocketServer::errconst($errno),
$errno
));
} else {
$resolve(new Connection($stream, $loop));
}
});
}, function () use ($loop, $stream, $uri) {
$loop->removeWriteStream($stream);
\fclose($stream);
// @codeCoverageIgnoreStart
// legacy PHP 5.3 sometimes requires a second close call (see tests)
if (\PHP_VERSION_ID < 50400 && \is_resource($stream)) {
\fclose($stream);
}
// @codeCoverageIgnoreEnd
throw new \RuntimeException(
'Connection to ' . $uri . ' cancelled during TCP/IP handshake (ECONNABORTED)',
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
);
});
}
}

262
vendor/react/socket/src/TcpServer.php vendored Executable file
View File

@@ -0,0 +1,262 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use InvalidArgumentException;
use RuntimeException;
/**
* The `TcpServer` class implements the `ServerInterface` and
* is responsible for accepting plaintext TCP/IP connections.
*
* ```php
* $server = new React\Socket\TcpServer(8080);
* ```
*
* Whenever a client connects, it will emit a `connection` event with a connection
* instance implementing `ConnectionInterface`:
*
* ```php
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
* echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
* $connection->write('hello there!' . PHP_EOL);
*
* });
* ```
*
* See also the `ServerInterface` for more details.
*
* @see ServerInterface
* @see ConnectionInterface
*/
final class TcpServer extends EventEmitter implements ServerInterface
{
private $master;
private $loop;
private $listening = false;
/**
* Creates a plaintext TCP/IP socket server and starts listening on the given address
*
* This starts accepting new incoming connections on the given address.
* See also the `connection event` documented in the `ServerInterface`
* for more details.
*
* ```php
* $server = new React\Socket\TcpServer(8080);
* ```
*
* As above, the `$uri` parameter can consist of only a port, in which case the
* server will default to listening on the localhost address `127.0.0.1`,
* which means it will not be reachable from outside of this system.
*
* In order to use a random port assignment, you can use the port `0`:
*
* ```php
* $server = new React\Socket\TcpServer(0);
* $address = $server->getAddress();
* ```
*
* In order to change the host the socket is listening on, you can provide an IP
* address through the first parameter provided to the constructor, optionally
* preceded by the `tcp://` scheme:
*
* ```php
* $server = new React\Socket\TcpServer('192.168.0.1:8080');
* ```
*
* If you want to listen on an IPv6 address, you MUST enclose the host in square
* brackets:
*
* ```php
* $server = new React\Socket\TcpServer('[::1]:8080');
* ```
*
* If the given URI is invalid, does not contain a port, any other scheme or if it
* contains a hostname, it will throw an `InvalidArgumentException`:
*
* ```php
* // throws InvalidArgumentException due to missing port
* $server = new React\Socket\TcpServer('127.0.0.1');
* ```
*
* If the given URI appears to be valid, but listening on it fails (such as if port
* is already in use or port below 1024 may require root access etc.), it will
* throw a `RuntimeException`:
*
* ```php
* $first = new React\Socket\TcpServer(8080);
*
* // throws RuntimeException because port is already in use
* $second = new React\Socket\TcpServer(8080);
* ```
*
* Note that these error conditions may vary depending on your system and/or
* configuration.
* See the exception message and code for more details about the actual error
* condition.
*
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
* pass the event loop instance to use for this object. You can use a `null` value
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
* given event loop instance.
*
* Optionally, you can specify [socket context options](https://www.php.net/manual/en/context.socket.php)
* for the underlying stream socket resource like this:
*
* ```php
* $server = new React\Socket\TcpServer('[::1]:8080', null, array(
* 'backlog' => 200,
* 'so_reuseport' => true,
* 'ipv6_v6only' => true
* ));
* ```
*
* Note that available [socket context options](https://www.php.net/manual/en/context.socket.php),
* their defaults and effects of changing these may vary depending on your system
* and/or PHP version.
* Passing unknown context options has no effect.
* The `backlog` context option defaults to `511` unless given explicitly.
*
* @param string|int $uri
* @param ?LoopInterface $loop
* @param array $context
* @throws InvalidArgumentException if the listening address is invalid
* @throws RuntimeException if listening on this address fails (already in use etc.)
*/
public function __construct($uri, $loop = null, array $context = array())
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->loop = $loop ?: Loop::get();
// a single port has been given => assume localhost
if ((string)(int)$uri === (string)$uri) {
$uri = '127.0.0.1:' . $uri;
}
// assume default scheme if none has been given
if (\strpos($uri, '://') === false) {
$uri = 'tcp://' . $uri;
}
// parse_url() does not accept null ports (random port assignment) => manually remove
if (\substr($uri, -2) === ':0') {
$parts = \parse_url(\substr($uri, 0, -2));
if ($parts) {
$parts['port'] = 0;
}
} else {
$parts = \parse_url($uri);
}
// ensure URI contains TCP scheme, host and port
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
throw new \InvalidArgumentException(
'Invalid URI "' . $uri . '" given (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}
if (@\inet_pton(\trim($parts['host'], '[]')) === false) {
throw new \InvalidArgumentException(
'Given URI "' . $uri . '" does not contain a valid host IP (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}
$this->master = @\stream_socket_server(
$uri,
$errno,
$errstr,
\STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
\stream_context_create(array('socket' => $context + array('backlog' => 511)))
);
if (false === $this->master) {
if ($errno === 0) {
// PHP does not seem to report errno, so match errno from errstr
// @link https://3v4l.org/3qOBl
$errno = SocketServer::errno($errstr);
}
throw new \RuntimeException(
'Failed to listen on "' . $uri . '": ' . $errstr . SocketServer::errconst($errno),
$errno
);
}
\stream_set_blocking($this->master, false);
$this->resume();
}
public function getAddress()
{
if (!\is_resource($this->master)) {
return null;
}
$address = \stream_socket_get_name($this->master, false);
// check if this is an IPv6 address which includes multiple colons but no square brackets
$pos = \strrpos($address, ':');
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
}
return 'tcp://' . $address;
}
public function pause()
{
if (!$this->listening) {
return;
}
$this->loop->removeReadStream($this->master);
$this->listening = false;
}
public function resume()
{
if ($this->listening || !\is_resource($this->master)) {
return;
}
$that = $this;
$this->loop->addReadStream($this->master, function ($master) use ($that) {
try {
$newSocket = SocketServer::accept($master);
} catch (\RuntimeException $e) {
$that->emit('error', array($e));
return;
}
$that->handleConnection($newSocket);
});
$this->listening = true;
}
public function close()
{
if (!\is_resource($this->master)) {
return;
}
$this->pause();
\fclose($this->master);
$this->removeAllListeners();
}
/** @internal */
public function handleConnection($socket)
{
$this->emit('connection', array(
new Connection($socket, $this->loop)
));
}
}

79
vendor/react/socket/src/TimeoutConnector.php vendored Executable file
View File

@@ -0,0 +1,79 @@
<?php
namespace React\Socket;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise\Promise;
final class TimeoutConnector implements ConnectorInterface
{
private $connector;
private $timeout;
private $loop;
/**
* @param ConnectorInterface $connector
* @param float $timeout
* @param ?LoopInterface $loop
*/
public function __construct(ConnectorInterface $connector, $timeout, $loop = null)
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->connector = $connector;
$this->timeout = $timeout;
$this->loop = $loop ?: Loop::get();
}
public function connect($uri)
{
$promise = $this->connector->connect($uri);
$loop = $this->loop;
$time = $this->timeout;
return new Promise(function ($resolve, $reject) use ($loop, $time, $promise, $uri) {
$timer = null;
$promise = $promise->then(function ($v) use (&$timer, $loop, $resolve) {
if ($timer) {
$loop->cancelTimer($timer);
}
$timer = false;
$resolve($v);
}, function ($v) use (&$timer, $loop, $reject) {
if ($timer) {
$loop->cancelTimer($timer);
}
$timer = false;
$reject($v);
});
// promise already resolved => no need to start timer
if ($timer === false) {
return;
}
// start timeout timer which will cancel the pending promise
$timer = $loop->addTimer($time, function () use ($time, &$promise, $reject, $uri) {
$reject(new \RuntimeException(
'Connection to ' . $uri . ' timed out after ' . $time . ' seconds (ETIMEDOUT)',
\defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 110
));
// Cancel pending connection to clean up any underlying resources and references.
// Avoid garbage references in call stack by passing pending promise by reference.
assert(\method_exists($promise, 'cancel'));
$promise->cancel();
$promise = null;
});
}, function () use (&$promise) {
// Cancelling this promise will cancel the pending connection, thus triggering the rejection logic above.
// Avoid garbage references in call stack by passing pending promise by reference.
assert(\method_exists($promise, 'cancel'));
$promise->cancel();
$promise = null;
});
}
}

58
vendor/react/socket/src/UnixConnector.php vendored Executable file
View File

@@ -0,0 +1,58 @@
<?php
namespace React\Socket;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Promise;
use InvalidArgumentException;
use RuntimeException;
/**
* Unix domain socket connector
*
* Unix domain sockets use atomic operations, so we can as well emulate
* async behavior.
*/
final class UnixConnector implements ConnectorInterface
{
private $loop;
/**
* @param ?LoopInterface $loop
*/
public function __construct($loop = null)
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->loop = $loop ?: Loop::get();
}
public function connect($path)
{
if (\strpos($path, '://') === false) {
$path = 'unix://' . $path;
} elseif (\substr($path, 0, 7) !== 'unix://') {
return Promise\reject(new \InvalidArgumentException(
'Given URI "' . $path . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
));
}
$resource = @\stream_socket_client($path, $errno, $errstr, 1.0);
if (!$resource) {
return Promise\reject(new \RuntimeException(
'Unable to connect to unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
$errno
));
}
$connection = new Connection($resource, $this->loop);
$connection->unix = true;
return Promise\resolve($connection);
}
}

162
vendor/react/socket/src/UnixServer.php vendored Executable file
View File

@@ -0,0 +1,162 @@
<?php
namespace React\Socket;
use Evenement\EventEmitter;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use InvalidArgumentException;
use RuntimeException;
/**
* The `UnixServer` class implements the `ServerInterface` and
* is responsible for accepting plaintext connections on unix domain sockets.
*
* ```php
* $server = new React\Socket\UnixServer('unix:///tmp/app.sock');
* ```
*
* See also the `ServerInterface` for more details.
*
* @see ServerInterface
* @see ConnectionInterface
*/
final class UnixServer extends EventEmitter implements ServerInterface
{
private $master;
private $loop;
private $listening = false;
/**
* Creates a plaintext socket server and starts listening on the given unix socket
*
* This starts accepting new incoming connections on the given address.
* See also the `connection event` documented in the `ServerInterface`
* for more details.
*
* ```php
* $server = new React\Socket\UnixServer('unix:///tmp/app.sock');
* ```
*
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
* pass the event loop instance to use for this object. You can use a `null` value
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
* given event loop instance.
*
* @param string $path
* @param ?LoopInterface $loop
* @param array $context
* @throws InvalidArgumentException if the listening address is invalid
* @throws RuntimeException if listening on this address fails (already in use etc.)
*/
public function __construct($path, $loop = null, array $context = array())
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->loop = $loop ?: Loop::get();
if (\strpos($path, '://') === false) {
$path = 'unix://' . $path;
} elseif (\substr($path, 0, 7) !== 'unix://') {
throw new \InvalidArgumentException(
'Given URI "' . $path . '" is invalid (EINVAL)',
\defined('SOCKET_EINVAL') ? \SOCKET_EINVAL : (\defined('PCNTL_EINVAL') ? \PCNTL_EINVAL : 22)
);
}
$errno = 0;
$errstr = '';
\set_error_handler(function ($_, $error) use (&$errno, &$errstr) {
// PHP does not seem to report errno/errstr for Unix domain sockets (UDS) right now.
// This only applies to UDS server sockets, see also https://3v4l.org/NAhpr.
// Parse PHP warning message containing unknown error, HHVM reports proper info at least.
if (\preg_match('/\(([^\)]+)\)|\[(\d+)\]: (.*)/', $error, $match)) {
$errstr = isset($match[3]) ? $match['3'] : $match[1];
$errno = isset($match[2]) ? (int)$match[2] : 0;
}
});
$this->master = \stream_socket_server(
$path,
$errno,
$errstr,
\STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
\stream_context_create(array('socket' => $context))
);
\restore_error_handler();
if (false === $this->master) {
throw new \RuntimeException(
'Failed to listen on Unix domain socket "' . $path . '": ' . $errstr . SocketServer::errconst($errno),
$errno
);
}
\stream_set_blocking($this->master, 0);
$this->resume();
}
public function getAddress()
{
if (!\is_resource($this->master)) {
return null;
}
return 'unix://' . \stream_socket_get_name($this->master, false);
}
public function pause()
{
if (!$this->listening) {
return;
}
$this->loop->removeReadStream($this->master);
$this->listening = false;
}
public function resume()
{
if ($this->listening || !is_resource($this->master)) {
return;
}
$that = $this;
$this->loop->addReadStream($this->master, function ($master) use ($that) {
try {
$newSocket = SocketServer::accept($master);
} catch (\RuntimeException $e) {
$that->emit('error', array($e));
return;
}
$that->handleConnection($newSocket);
});
$this->listening = true;
}
public function close()
{
if (!\is_resource($this->master)) {
return;
}
$this->pause();
\fclose($this->master);
$this->removeAllListeners();
}
/** @internal */
public function handleConnection($socket)
{
$connection = new Connection($socket, $this->loop);
$connection->unix = true;
$this->emit('connection', array(
$connection
));
}
}

460
vendor/react/stream/CHANGELOG.md vendored Executable file
View File

@@ -0,0 +1,460 @@
# Changelog
## 1.4.0 (2024-06-11)
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
(#179 by @clue)
* Feature: Full PHP 8.3 compatibility.
(#172 by @clue)
* Fix: Fix `drain` event of `ThroughStream` to handle potential race condition.
(#171 by @clue)
## 1.3.0 (2023-06-16)
* Feature: Full PHP 8.1 and PHP 8.2 compatibility.
(#160 by @SimonFrings, #165 by @clue and #169 by @WyriHaximus)
* Feature: Avoid unneeded syscall when creating non-blocking `DuplexResourceStream`.
(#164 by @clue)
* Minor documentation improvements.
(#161 by @mrsimonbennett, #162 by @SimonFrings and #166 by @nhedger)
* Improve test suite and project setup and report failed assertions.
(#168 and #170 by @clue and #163 by @SimonFrings)
## 1.2.0 (2021-07-11)
A major new feature release, see [**release announcement**](https://clue.engineering/2021/announcing-reactphp-default-loop).
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop).
(#159 by @clue)
```php
// old (still supported)
$stream = new ReadableResourceStream($resource, $loop);
$stream = new WritabeResourceStream($resource, $loop);
$stream = new DuplexResourceStream($resource, $loop);
// new (using default loop)
$stream = new ReadableResourceStream($resource);
$stream = new WritabeResourceStream($resource);
$stream = new DuplexResourceStream($resource);
```
* Improve test suite, use GitHub actions for continuous integration (CI),
update PHPUnit config, run tests on PHP 8 and add full core team to the license.
(#153, #156 and #157 by @SimonFrings and #154 by @WyriHaximus)
## 1.1.1 (2020-05-04)
* Fix: Fix faulty write buffer behavior when sending large data chunks over TLS (Mac OS X only).
(#150 by @clue)
* Minor code style improvements to fix phpstan analysis warnings and
add `.gitattributes` to exclude dev files from exports.
(#140 by @flow-control and #144 by @reedy)
* Improve test suite to run tests on PHP 7.4 and simplify test matrix.
(#147 by @clue)
## 1.1.0 (2019-01-01)
* Improvement: Increase performance by optimizing global function and constant look ups.
(#137 by @WyriHaximus)
* Travis: Test against PHP 7.3.
(#138 by @WyriHaximus)
* Fix: Ignore empty reads.
(#139 by @WyriHaximus)
## 1.0.0 (2018-07-11)
* First stable LTS release, now following [SemVer](https://semver.org/).
We'd like to emphasize that this component is production ready and battle-tested.
We plan to support all long-term support (LTS) releases for at least 24 months,
so you have a rock-solid foundation to build on top of.
> Contains no other changes, so it's actually fully compatible with the v0.7.7 release.
## 0.7.7 (2018-01-19)
* Improve test suite by fixing forward compatibility with upcoming EventLoop
releases, avoid risky tests and add test group to skip integration tests
relying on internet connection and apply appropriate test timeouts.
(#128, #131 and #132 by @clue)
## 0.7.6 (2017-12-21)
* Fix: Work around reading from unbuffered pipe stream in legacy PHP < 5.4.28 and PHP < 5.5.12
(#126 by @clue)
* Improve test suite by simplifying test bootstrapping logic via Composer and
test against PHP 7.2
(#127 by @clue and #124 by @carusogabriel)
## 0.7.5 (2017-11-20)
* Fix: Igore excessive `fopen()` mode flags for `WritableResourceStream`
(#119 by @clue)
* Fix: Fix forward compatibility with upcoming EventLoop releases
(#121 by @clue)
* Restructure examples to ease getting started
(#123 by @clue)
* Improve test suite by adding forward compatibility with PHPUnit 6 and
ignore Mac OS X test failures for now until Travis tests work again
(#122 by @gabriel-caruso and #120 by @clue)
## 0.7.4 (2017-10-11)
* Fix: Remove event listeners from `CompositeStream` once closed and
remove undocumented left-over `close` event argument
(#116 by @clue)
* Minor documentation improvements: Fix wrong class name in example,
fix typos in README and
fix forward compatibility with upcoming EventLoop releases in example
(#113 by @docteurklein and #114 and #115 by @clue)
* Improve test suite by running against Mac OS X on Travis
(#112 by @clue)
## 0.7.3 (2017-08-05)
* Improvement: Support Événement 3.0 a long side 2.0 and 1.0
(#108 by @WyriHaximus)
* Readme: Corrected loop initialization in usage example
(#109 by @pulyavin)
* Travis: Lock linux distribution preventing future builds from breaking
(#110 by @clue)
## 0.7.2 (2017-06-15)
* Bug fix: WritableResourceStream: Close the underlying stream when closing the stream.
(#107 by @WyriHaximus)
## 0.7.1 (2017-05-20)
* Feature: Add optional `$writeChunkSize` parameter to limit maximum number of
bytes to write at once.
(#105 by @clue)
```php
$stream = new WritableResourceStream(STDOUT, $loop, null, 8192);
```
* Ignore HHVM test failures for now until Travis tests work again
(#106 by @clue)
## 0.7.0 (2017-05-04)
* Removed / BC break: Remove deprecated and unneeded functionality
(#45, #87, #90, #91 and #93 by @clue)
* Remove deprecated `Stream` class, use `DuplexResourceStream` instead
(#87 by @clue)
* Remove public `$buffer` property, use new constructor parameters instead
(#91 by @clue)
* Remove public `$stream` property from all resource streams
(#90 by @clue)
* Remove undocumented and now unused `ReadableStream` and `WritableStream`
(#93 by @clue)
* Remove `BufferedSink`
(#45 by @clue)
* Feature / BC break: Simplify `ThroughStream` by using data callback instead of
inheritance. It is now a direct implementation of `DuplexStreamInterface`.
(#88 and #89 by @clue)
```php
$through = new ThroughStream(function ($data) {
return json_encode($data) . PHP_EOL;
});
$through->on('data', $this->expectCallableOnceWith("[2, true]\n"));
$through->write(array(2, true));
```
* Feature / BC break: The `CompositeStream` starts closed if either side is
already closed and forwards pause to pipe source on first write attempt.
(#96 and #103 by @clue)
If either side of the composite stream closes, it will also close the other
side. We now also ensure that if either side is already closed during
instantiation, it will also close the other side.
* BC break: Mark all classes as `final` and
mark internal API as `private` to discourage inheritance
(#95 and #99 by @clue)
* Feature / BC break: Only emit `error` event for fatal errors
(#92 by @clue)
> The `error` event was previously also allowed to be emitted for non-fatal
errors, but our implementations actually only ever emitted this as a fatal
error and then closed the stream.
* Feature: Explicitly allow custom events and exclude any semantics
(#97 by @clue)
* Strict definition for event callback functions
(#101 by @clue)
* Support legacy PHP 5.3 through PHP 7.1 and HHVM and improve usage documentation
(#100 and #102 by @clue)
* Actually require all dependencies so this is self-contained and improve
forward compatibility with EventLoop v1.0 and v0.5
(#94 and #98 by @clue)
## 0.6.0 (2017-03-26)
* Feature / Fix / BC break: Add `DuplexResourceStream` and deprecate `Stream`
(#85 by @clue)
```php
// old (does still work for BC reasons)
$stream = new Stream($connection, $loop);
// new
$stream = new DuplexResourceStream($connection, $loop);
```
Note that the `DuplexResourceStream` now rejects read-only or write-only
streams, so this may affect BC. If you want a read-only or write-only
resource, use `ReadableResourceStream` or `WritableResourceStream` instead of
`DuplexResourceStream`.
> BC note: This class was previously called `Stream`. The `Stream` class still
exists for BC reasons and will be removed in future versions of this package.
* Feature / BC break: Add `WritableResourceStream` (previously called `Buffer`)
(#84 by @clue)
```php
// old
$stream = new Buffer(STDOUT, $loop);
// new
$stream = new WritableResourceStream(STDOUT, $loop);
```
* Feature: Add `ReadableResourceStream`
(#83 by @clue)
```php
$stream = new ReadableResourceStream(STDIN, $loop);
```
* Fix / BC Break: Enforce using non-blocking I/O
(#46 by @clue)
> BC note: This is known to affect process pipes on Windows which do not
support non-blocking I/O and could thus block the whole EventLoop previously.
* Feature / Fix / BC break: Consistent semantics for
`DuplexStreamInterface::end()` to ensure it SHOULD also end readable side
(#86 by @clue)
* Fix: Do not use unbuffered reads on pipe streams for legacy PHP < 5.4
(#80 by @clue)
## 0.5.0 (2017-03-08)
* Feature / BC break: Consistent `end` event semantics (EOF)
(#70 by @clue)
The `end` event will now only be emitted for a *successful* end, not if the
stream closes due to an unrecoverable `error` event or if you call `close()`
explicitly.
If you want to detect when the stream closes (terminates), use the `close`
event instead.
* BC break: Remove custom (undocumented) `full-drain` event from `Buffer`
(#63 and #68 by @clue)
> The `full-drain` event was undocumented and mostly used internally.
Relying on this event has attracted some low-quality code in the past, so
we've removed this from the public API in order to work out a better
solution instead.
If you want to detect when the buffer finishes flushing data to the stream,
you may want to look into its `end()` method or the `close` event instead.
* Feature / BC break: Consistent event semantics and documentation,
explicitly state *when* events will be emitted and *which* arguments they
receive.
(#73 and #69 by @clue)
The documentation now explicitly defines each event and its arguments.
Custom events and event arguments are still supported.
Most notably, all defined events only receive inherently required event
arguments and no longer transmit the instance they are emitted on for
consistency and performance reasons.
```php
// old (inconsistent and not supported by all implementations)
$stream->on('data', function ($data, $stream) {
// process $data
});
// new (consistent throughout the whole ecosystem)
$stream->on('data', function ($data) use ($stream) {
// process $data
});
```
> This mostly adds documentation (and thus some stricter, consistent
definitions) for the existing behavior, it does NOT define any major
changes otherwise.
Most existing code should be compatible with these changes, unless
it relied on some undocumented/unintended semantics.
* Feature / BC break: Consistent method semantics and documentation
(#72 by @clue)
> This mostly adds documentation (and thus some stricter, consistent
definitions) for the existing behavior, it does NOT define any major
changes otherwise.
Most existing code should be compatible with these changes, unless
it relied on some undocumented/unintended semantics.
* Feature: Consistent `pipe()` semantics for closed and closing streams
(#71 from @clue)
The source stream will now always be paused via `pause()` when the
destination stream closes. Also, properly stop piping if the source
stream closes and remove all event forwarding.
* Improve test suite by adding PHPUnit to `require-dev` and improving coverage.
(#74 and #75 by @clue, #66 by @nawarian)
## 0.4.6 (2017-01-25)
* Feature: The `Buffer` can now be injected into the `Stream` (or be used standalone)
(#62 by @clue)
* Fix: Forward `close` event only once for `CompositeStream` and `ThroughStream`
(#60 by @clue)
* Fix: Consistent `close` event behavior for `Buffer`
(#61 by @clue)
## 0.4.5 (2016-11-13)
* Feature: Support setting read buffer size to `null` (infinite)
(#42 by @clue)
* Fix: Do not emit `full-drain` event if `Buffer` is closed during `drain` event
(#55 by @clue)
* Vastly improved performance by factor of 10x to 20x.
Raise default buffer sizes to 64 KiB and simplify and improve error handling
and unneeded function calls.
(#53, #55, #56 by @clue)
## 0.4.4 (2016-08-22)
* Bug fix: Emit `error` event and close `Stream` when accessing the underlying
stream resource fails with a permanent error.
(#52 and #40 by @clue, #25 by @lysenkobv)
* Bug fix: Do not emit empty `data` event if nothing has been read (stream reached EOF)
(#39 by @clue)
* Bug fix: Ignore empty writes to `Buffer`
(#51 by @clue)
* Add benchmarking script to measure throughput in CI
(#41 by @clue)
## 0.4.3 (2015-10-07)
* Bug fix: Read buffer to 0 fixes error with libevent and large quantity of I/O (@mbonneau)
* Bug fix: No double-write during drain call (@arnaud-lb)
* Bug fix: Support HHVM (@clue)
* Adjust compatibility to 5.3 (@clue)
## 0.4.2 (2014-09-09)
* Added DuplexStreamInterface
* Stream sets stream resources to non-blocking
* Fixed potential race condition in pipe
## 0.4.1 (2014-04-13)
* Bug fix: v0.3.4 changes merged for v0.4.1
## 0.3.4 (2014-03-30)
* Bug fix: [Stream] Fixed 100% CPU spike from non-empty write buffer on closed stream
## 0.4.0 (2014-02-02)
* BC break: Bump minimum PHP version to PHP 5.4, remove 5.3 specific hacks
* BC break: Update to Evenement 2.0
* Dependency: Autoloading and filesystem structure now PSR-4 instead of PSR-0
## 0.3.3 (2013-07-08)
* Bug fix: [Stream] Correctly detect closed connections
## 0.3.2 (2013-05-10)
* Bug fix: [Stream] Make sure CompositeStream is closed properly
## 0.3.1 (2013-04-21)
* Bug fix: [Stream] Allow any `ReadableStreamInterface` on `BufferedSink::createPromise()`
## 0.3.0 (2013-04-14)
* Feature: [Stream] Factory method for BufferedSink
## 0.2.6 (2012-12-26)
* Version bump
## 0.2.5 (2012-11-26)
* Feature: Make BufferedSink trigger progress events on the promise (@jsor)
## 0.2.4 (2012-11-18)
* Feature: Added ThroughStream, CompositeStream, ReadableStream and WritableStream
* Feature: Added BufferedSink
## 0.2.3 (2012-11-14)
* Version bump
## 0.2.2 (2012-10-28)
* Version bump
## 0.2.1 (2012-10-14)
* Bug fix: Check for EOF in `Buffer::write()`
## 0.2.0 (2012-09-10)
* Version bump
## 0.1.1 (2012-07-12)
* Bug fix: Testing and functional against PHP >= 5.3.3 and <= 5.3.8
## 0.1.0 (2012-07-11)
* First tagged release

21
vendor/react/stream/LICENSE vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

1249
vendor/react/stream/README.md vendored Executable file

File diff suppressed because it is too large Load Diff

47
vendor/react/stream/composer.json vendored Executable file
View File

@@ -0,0 +1,47 @@
{
"name": "react/stream",
"description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
"keywords": ["event-driven", "readable", "writable", "stream", "non-blocking", "io", "pipe", "ReactPHP"],
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"homepage": "https://clue.engineering/",
"email": "christian@clue.engineering"
},
{
"name": "Cees-Jan Kiewiet",
"homepage": "https://wyrihaximus.net/",
"email": "reactphp@ceesjankiewiet.nl"
},
{
"name": "Jan Sorgalla",
"homepage": "https://sorgalla.com/",
"email": "jsorgalla@gmail.com"
},
{
"name": "Chris Boden",
"homepage": "https://cboden.dev/",
"email": "cboden@gmail.com"
}
],
"require": {
"php": ">=5.3.8",
"react/event-loop": "^1.2",
"evenement/evenement": "^3.0 || ^2.0 || ^1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"clue/stream-filter": "~1.2"
},
"autoload": {
"psr-4": {
"React\\Stream\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"React\\Tests\\Stream\\": "tests/"
}
}
}

Some files were not shown because too many files have changed in this diff Show More