mirror of
https://github.com/Icinga/icinga-php-thirdparty.git
synced 2025-08-15 06:38:15 +02:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ff25a12ed0 | ||
|
3ffc4e5833 | ||
|
2d8baeafe4 | ||
|
50f68d3fc3 | ||
|
3bc4158c81 | ||
|
63e153614d | ||
|
a3a9a0f9a8 | ||
|
6c862b2ab8 | ||
|
426e244015 | ||
|
a843109028 | ||
|
3a4ac0eae4 | ||
|
12de4298b3 | ||
|
0105d75397 | ||
|
17eacc0fa0 | ||
|
54f11ae3ed | ||
|
21f1fd0331 | ||
|
48424f5a88 | ||
|
60dc3cb74b | ||
|
d7dec5a35c | ||
|
515540e633 | ||
|
818b63f028 | ||
|
3efc86e9bc | ||
|
a33308e27f | ||
|
c614ddb31f | ||
|
d2a9d65913 | ||
|
83837dd07c | ||
|
76f8bce8d2 | ||
|
3a9e74ef58 | ||
|
2cb7b445bc | ||
|
79c1847b87 | ||
|
048c339cc9 | ||
|
8b455aa764 | ||
|
16df93679e | ||
|
6ba9c56b2c | ||
|
e509effd2f | ||
|
a46ad9ca72 | ||
|
b79a796867 | ||
|
f5ec92fb63 | ||
|
a737bf6c14 | ||
|
d6ba1c49b5 | ||
|
bd24853dbb | ||
|
445fef27e5 | ||
|
4230555a1e | ||
|
eae38fcabf |
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 20 # Roughly half the number of dependencies
|
||||
groups:
|
||||
composer:
|
||||
update-types:
|
||||
- "patch"
|
||||
schedule:
|
||||
interval: "daily"
|
4
.github/workflows/create-snapshot.yml
vendored
4
.github/workflows/create-snapshot.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code base
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '5.6'
|
||||
php-version: '8.2'
|
||||
|
||||
- name: Create snapshot/nightly
|
||||
if: success()
|
||||
|
@ -6,7 +6,7 @@ which can be integrated as library into Icinga Web 2.
|
||||
## Requirements
|
||||
|
||||
* [Icinga Web 2](https://github.com/Icinga/icingaweb2) (>= 2.9)
|
||||
* PHP (>= 7.2.9)
|
||||
* PHP (>= 8.2)
|
||||
|
||||
## Installation
|
||||
|
||||
@ -16,7 +16,7 @@ path for Icinga Web 2 installations is: `/usr/share/icinga-php`
|
||||
Download or clone this repository there (e.g. `/usr/share/icinga-php/vendor`) and you're done.
|
||||
|
||||
> **Note**: Do NOT install the GIT master, it will not work! Checking out a
|
||||
> branch like `stable/0.11.0` or a tag like `v0.11.0` is fine.
|
||||
> branch like `stable/0.13.0` or a tag like `v0.13.0` is fine.
|
||||
|
||||
### Examples
|
||||
|
||||
@ -24,7 +24,7 @@ Download or clone this repository there (e.g. `/usr/share/icinga-php/vendor`) an
|
||||
|
||||
```sh
|
||||
INSTALL_PATH="/usr/share/icinga-php/vendor"
|
||||
INSTALL_VERSION="v0.11.0"
|
||||
INSTALL_VERSION="v0.13.0"
|
||||
mkdir "$INSTALL_PATH" \
|
||||
&& wget -q "https://github.com/Icinga/icinga-php-thirdparty/archive/$INSTALL_VERSION.tar.gz" -O - \
|
||||
| tar xfz - -C "$INSTALL_PATH" --strip-components 1
|
||||
@ -34,6 +34,6 @@ mkdir "$INSTALL_PATH" \
|
||||
|
||||
```
|
||||
INSTALL_PATH="/usr/share/icinga-php/vendor"
|
||||
INSTALL_VERSION="stable/0.11.0"
|
||||
INSTALL_VERSION="stable/0.13.0"
|
||||
git clone https://github.com/Icinga/icinga-php-thirdparty.git "$INSTALL_PATH" --branch "$INSTALL_VERSION"
|
||||
```
|
||||
|
@ -8,4 +8,4 @@ e.g.
|
||||
|
||||
## Docker Example
|
||||
|
||||
docker run -it -v $(pwd):/tmp/pwd -w /tmp/pwd -v $(realpath ~/.gitconfig):/tmp/user/.gitconfig -e "HOME=/tmp/user" -u $(id -u):$(id -g) dev-docker_web72 bin/make-release.sh 1.0.0 --no-tag
|
||||
docker run -it -v $(pwd):/tmp/pwd -w /tmp/pwd -v $(realpath ~/.gitconfig):/tmp/user/.gitconfig -e "HOME=/tmp/user" -u $(id -u):$(id -g) dev-docker_web82 bin/make-release.sh 1.0.0 --no-tag
|
||||
|
10881
asset/js/jquery/jquery.js
vendored
10881
asset/js/jquery/jquery.js
vendored
File diff suppressed because it is too large
Load Diff
2
asset/js/jquery/jquery.min.js
vendored
2
asset/js/jquery/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
13
asset/js/mbostock/LICENSE
Normal file
13
asset/js/mbostock/LICENSE
Normal file
@ -0,0 +1,13 @@
|
||||
Copyright 2010-2023 Mike Bostock
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
20625
asset/js/mbostock/d3.js
vendored
Normal file
20625
asset/js/mbostock/d3.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
asset/js/mbostock/d3.min.js
vendored
Normal file
2
asset/js/mbostock/d3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -32,13 +32,11 @@ fi
|
||||
|
||||
git rm -rf vendor
|
||||
rm -rf vendor
|
||||
rm -f composer.lock
|
||||
composer install || fail "composer install failed"
|
||||
git add vendor
|
||||
find asset/ -type f | xargs -L1 git add -f
|
||||
echo "v$VERSION" > VERSION
|
||||
git add VERSION
|
||||
git add composer.lock -f
|
||||
git commit -m "Version v$VERSION"
|
||||
|
||||
composer validate --no-check-all --strict || fail "Composer validate failed"
|
||||
|
@ -18,7 +18,11 @@ if [[ -n $(git branch | grep $BRANCH) ]]; then
|
||||
fi
|
||||
|
||||
git checkout -b $BRANCH
|
||||
git merge --no-ff -m "Merge latest tag, to make it reachable for git-describe" $LATEST_TAG
|
||||
|
||||
git commit -a -m "Require dev-master everywhere"
|
||||
git mv composer.lock composer.lock.bak
|
||||
git commit -am "Backup composer.lock"
|
||||
git merge --no-ff -m "Merge latest tag, package pipelines require it" $LATEST_TAG || (git checkout --ours composer.lock.bak && git add composer.lock.bak && git commit --no-edit)
|
||||
git mv -f composer.lock.bak composer.lock
|
||||
git commit -am "Restore composer.lock" || true # in case composer.lock was not modified
|
||||
|
||||
bin/make-release.sh "$NEXT_VERSION-dev" --no-checkout
|
||||
|
@ -6,9 +6,6 @@
|
||||
"license": "MIT",
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"platform": {
|
||||
"php": "7.2.9"
|
||||
},
|
||||
"allow-plugins": {
|
||||
"cweagans/composer-patches": true
|
||||
}
|
||||
@ -17,7 +14,7 @@
|
||||
"issues": "https://github.com/Icinga/icinga-php-thirdparty/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.9 <=8.2",
|
||||
"php": ">=8.2",
|
||||
"ext-curl": "*",
|
||||
"clue/block-react": "^1.5",
|
||||
"clue/connection-manager-extra": "^1.3",
|
||||
@ -28,16 +25,16 @@
|
||||
"clue/socket-raw": "^1.6",
|
||||
"clue/socks-react": "^1.4",
|
||||
"clue/stdio-react": "^2.6",
|
||||
"components/jquery": "3.6.*",
|
||||
"components/jquery": "3.7.*",
|
||||
"cweagans/composer-patches": "~1.0",
|
||||
"dompdf/dompdf": "^2.0.3",
|
||||
"dompdf/dompdf": "^3.1.0",
|
||||
"erusev/parsedown": "^1.7.4",
|
||||
"evenement/evenement": "^3.0.1",
|
||||
"ezyang/htmlpurifier": "^4.16",
|
||||
"guzzlehttp/guzzle": "^7.7",
|
||||
"guzzlehttp/psr7": "^2.5",
|
||||
"jfcherng/php-diff": "^6.10.14",
|
||||
"predis/predis": "^2.2",
|
||||
"predis/predis": "^3.0",
|
||||
"psr/http-message": "^1.1",
|
||||
"ramsey/uuid": "^4.2.3",
|
||||
"react/child-process": "^0.6.5",
|
||||
@ -63,6 +60,9 @@
|
||||
"psr-0": { "AssetLoader": "" }
|
||||
},
|
||||
"scripts": {
|
||||
"post-install-cmd": [
|
||||
"AssetLoader::update"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"AssetLoader::update"
|
||||
]
|
||||
@ -70,9 +70,6 @@
|
||||
"extra": {
|
||||
"composer-exit-on-patch-failure": true,
|
||||
"patches": {
|
||||
"ramsey/collection": {
|
||||
"Collection: Add PHP 8.1 support": "patches/ramsey-collection.patch"
|
||||
},
|
||||
"shardj/zf1-future": {
|
||||
"ZF1-Future: ZF backward compatibility": "patches/shardj-zf1-future.patch"
|
||||
}
|
||||
|
1403
composer.lock
generated
1403
composer.lock
generated
File diff suppressed because it is too large
Load Diff
25
vendor/autoload.php
vendored
25
vendor/autoload.php
vendored
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
if (!headers_sent()) {
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
}
|
||||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
if (!ini_get('display_errors')) {
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
fwrite(STDERR, $err);
|
||||
} elseif (!headers_sent()) {
|
||||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit412d4c2afc15769a0fac36e82d8e412c::getLoader();
|
119
vendor/bin/lessc
vendored
119
vendor/bin/lessc
vendored
@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Proxy PHP file generated by Composer
|
||||
*
|
||||
* This file includes the referenced bin path (../wikimedia/less.php/bin/lessc)
|
||||
* using a stream wrapper to prevent the shebang from being output on PHP<8
|
||||
*
|
||||
* @generated
|
||||
*/
|
||||
|
||||
namespace Composer;
|
||||
|
||||
$GLOBALS['_composer_bin_dir'] = __DIR__;
|
||||
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
if (!class_exists('Composer\BinProxyWrapper')) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class BinProxyWrapper
|
||||
{
|
||||
private $handle;
|
||||
private $position;
|
||||
private $realpath;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path)
|
||||
{
|
||||
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
|
||||
$opened_path = substr($path, 17);
|
||||
$this->realpath = realpath($opened_path) ?: $opened_path;
|
||||
$opened_path = $this->realpath;
|
||||
$this->handle = fopen($this->realpath, $mode);
|
||||
$this->position = 0;
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
public function stream_read($count)
|
||||
{
|
||||
$data = fread($this->handle, $count);
|
||||
|
||||
if ($this->position === 0) {
|
||||
$data = preg_replace('{^#!.*\r?\n}', '', $data);
|
||||
}
|
||||
|
||||
$this->position += strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function stream_cast($castAs)
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
public function stream_close()
|
||||
{
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($operation)
|
||||
{
|
||||
return $operation ? flock($this->handle, $operation) : true;
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence)
|
||||
{
|
||||
if (0 === fseek($this->handle, $offset, $whence)) {
|
||||
$this->position = ftell($this->handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_tell()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof()
|
||||
{
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
public function stream_stat()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function url_stat($path, $flags)
|
||||
{
|
||||
$path = substr($path, 17);
|
||||
if (file_exists($path)) {
|
||||
return stat($path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|
||||
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
|
||||
) {
|
||||
return include("phpvfscomposer://" . __DIR__ . '/..'.'/wikimedia/less.php/bin/lessc');
|
||||
}
|
||||
}
|
||||
|
||||
return include __DIR__ . '/..'.'/wikimedia/less.php/bin/lessc';
|
415
vendor/brick/math/CHANGELOG.md
vendored
415
vendor/brick/math/CHANGELOG.md
vendored
@ -1,415 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.9.3](https://github.com/brick/math/releases/tag/0.9.3) - 2021-08-15
|
||||
|
||||
🚀 **Compatibility with PHP 8.1**
|
||||
|
||||
- Support for custom object serialization; this removes a warning on PHP 8.1 due to the `Serializable` interface being deprecated (thanks @TRowbotham)
|
||||
|
||||
## [0.9.2](https://github.com/brick/math/releases/tag/0.9.2) - 2021-01-20
|
||||
|
||||
🐛 **Bug fix**
|
||||
|
||||
- Incorrect results could be returned when using the BCMath calculator, with a default scale set with `bcscale()`, on PHP >= 7.2 (#55).
|
||||
|
||||
## [0.9.1](https://github.com/brick/math/releases/tag/0.9.1) - 2020-08-19
|
||||
|
||||
✨ New features
|
||||
|
||||
- `BigInteger::not()` returns the bitwise `NOT` value
|
||||
|
||||
🐛 **Bug fixes**
|
||||
|
||||
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
|
||||
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
|
||||
|
||||
## [0.9.0](https://github.com/brick/math/releases/tag/0.9.0) - 2020-08-18
|
||||
|
||||
👌 **Improvements**
|
||||
|
||||
- `BigNumber::of()` now accepts `.123` and `123.` formats, both of which return a `BigDecimal`
|
||||
|
||||
💥 **Breaking changes**
|
||||
|
||||
- Deprecated method `BigInteger::powerMod()` has been removed - use `modPow()` instead
|
||||
- Deprecated method `BigInteger::parse()` has been removed - use `fromBase()` instead
|
||||
|
||||
## [0.8.17](https://github.com/brick/math/releases/tag/0.8.17) - 2020-08-19
|
||||
|
||||
🐛 **Bug fix**
|
||||
|
||||
- `BigInteger::toBytes()` could return an incorrect binary representation for some numbers
|
||||
- The bitwise operations `and()`, `or()`, `xor()` on `BigInteger` could return an incorrect result when the GMP extension is not available
|
||||
|
||||
## [0.8.16](https://github.com/brick/math/releases/tag/0.8.16) - 2020-08-18
|
||||
|
||||
🚑 **Critical fix**
|
||||
|
||||
- This version reintroduces the deprecated `BigInteger::parse()` method, that has been removed by mistake in version `0.8.9` and should have lasted for the whole `0.8` release cycle.
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::modInverse()` calculates a modular multiplicative inverse
|
||||
- `BigInteger::fromBytes()` creates a `BigInteger` from a byte string
|
||||
- `BigInteger::toBytes()` converts a `BigInteger` to a byte string
|
||||
- `BigInteger::randomBits()` creates a pseudo-random `BigInteger` of a given bit length
|
||||
- `BigInteger::randomRange()` creates a pseudo-random `BigInteger` between two bounds
|
||||
|
||||
💩 **Deprecations**
|
||||
|
||||
- `BigInteger::powerMod()` is now deprecated in favour of `modPow()`
|
||||
|
||||
## [0.8.15](https://github.com/brick/math/releases/tag/0.8.15) - 2020-04-15
|
||||
|
||||
🐛 **Fixes**
|
||||
|
||||
- added missing `ext-json` requirement, due to `BigNumber` implementing `JsonSerializable`
|
||||
|
||||
⚡️ **Optimizations**
|
||||
|
||||
- additional optimization in `BigInteger::remainder()`
|
||||
|
||||
## [0.8.14](https://github.com/brick/math/releases/tag/0.8.14) - 2020-02-18
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::getLowestSetBit()` returns the index of the rightmost one bit
|
||||
|
||||
## [0.8.13](https://github.com/brick/math/releases/tag/0.8.13) - 2020-02-16
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::isEven()` tests whether the number is even
|
||||
- `BigInteger::isOdd()` tests whether the number is odd
|
||||
- `BigInteger::testBit()` tests if a bit is set
|
||||
- `BigInteger::getBitLength()` returns the number of bits in the minimal representation of the number
|
||||
|
||||
## [0.8.12](https://github.com/brick/math/releases/tag/0.8.12) - 2020-02-03
|
||||
|
||||
🛠️ **Maintenance release**
|
||||
|
||||
Classes are now annotated for better static analysis with [psalm](https://psalm.dev/).
|
||||
|
||||
This is a maintenance release: no bug fixes, no new features, no breaking changes.
|
||||
|
||||
## [0.8.11](https://github.com/brick/math/releases/tag/0.8.11) - 2020-01-23
|
||||
|
||||
✨ **New feature**
|
||||
|
||||
`BigInteger::powerMod()` performs a power-with-modulo operation. Useful for crypto.
|
||||
|
||||
## [0.8.10](https://github.com/brick/math/releases/tag/0.8.10) - 2020-01-21
|
||||
|
||||
✨ **New feature**
|
||||
|
||||
`BigInteger::mod()` returns the **modulo** of two numbers. The *modulo* differs from the *remainder* when the signs of the operands are different.
|
||||
|
||||
## [0.8.9](https://github.com/brick/math/releases/tag/0.8.9) - 2020-01-08
|
||||
|
||||
⚡️ **Performance improvements**
|
||||
|
||||
A few additional optimizations in `BigInteger` and `BigDecimal` when one of the operands can be returned as is. Thanks to @tomtomsen in #24.
|
||||
|
||||
## [0.8.8](https://github.com/brick/math/releases/tag/0.8.8) - 2019-04-25
|
||||
|
||||
🐛 **Bug fixes**
|
||||
|
||||
- `BigInteger::toBase()` could return an empty string for zero values (BCMath & Native calculators only, GMP calculator unaffected)
|
||||
|
||||
✨ **New features**
|
||||
|
||||
- `BigInteger::toArbitraryBase()` converts a number to an arbitrary base, using a custom alphabet
|
||||
- `BigInteger::fromArbitraryBase()` converts a string in an arbitrary base, using a custom alphabet, back to a number
|
||||
|
||||
These methods can be used as the foundation to convert strings between different bases/alphabets, using BigInteger as an intermediate representation.
|
||||
|
||||
💩 **Deprecations**
|
||||
|
||||
- `BigInteger::parse()` is now deprecated in favour of `fromBase()`
|
||||
|
||||
`BigInteger::fromBase()` works the same way as `parse()`, with 2 minor differences:
|
||||
|
||||
- the `$base` parameter is required, it does not default to `10`
|
||||
- it throws a `NumberFormatException` instead of an `InvalidArgumentException` when the number is malformed
|
||||
|
||||
## [0.8.7](https://github.com/brick/math/releases/tag/0.8.7) - 2019-04-20
|
||||
|
||||
**Improvements**
|
||||
|
||||
- Safer conversion from `float` when using custom locales
|
||||
- **Much faster** `NativeCalculator` implementation 🚀
|
||||
|
||||
You can expect **at least a 3x performance improvement** for common arithmetic operations when using the library on systems without GMP or BCMath; it gets exponentially faster on multiplications with a high number of digits. This is due to calculations now being performed on whole blocks of digits (the block size depending on the platform, 32-bit or 64-bit) instead of digit-by-digit as before.
|
||||
|
||||
## [0.8.6](https://github.com/brick/math/releases/tag/0.8.6) - 2019-04-11
|
||||
|
||||
**New method**
|
||||
|
||||
`BigNumber::sum()` returns the sum of one or more numbers.
|
||||
|
||||
## [0.8.5](https://github.com/brick/math/releases/tag/0.8.5) - 2019-02-12
|
||||
|
||||
**Bug fix**: `of()` factory methods could fail when passing a `float` in environments using a `LC_NUMERIC` locale with a decimal separator other than `'.'` (#20).
|
||||
|
||||
Thanks @manowark 👍
|
||||
|
||||
## [0.8.4](https://github.com/brick/math/releases/tag/0.8.4) - 2018-12-07
|
||||
|
||||
**New method**
|
||||
|
||||
`BigDecimal::sqrt()` calculates the square root of a decimal number, to a given scale.
|
||||
|
||||
## [0.8.3](https://github.com/brick/math/releases/tag/0.8.3) - 2018-12-06
|
||||
|
||||
**New method**
|
||||
|
||||
`BigInteger::sqrt()` calculates the square root of a number (thanks @peter279k).
|
||||
|
||||
**New exception**
|
||||
|
||||
`NegativeNumberException` is thrown when calling `sqrt()` on a negative number.
|
||||
|
||||
## [0.8.2](https://github.com/brick/math/releases/tag/0.8.2) - 2018-11-08
|
||||
|
||||
**Performance update**
|
||||
|
||||
- Further improvement of `toInt()` performance
|
||||
- `NativeCalculator` can now perform some multiplications more efficiently
|
||||
|
||||
## [0.8.1](https://github.com/brick/math/releases/tag/0.8.1) - 2018-11-07
|
||||
|
||||
Performance optimization of `toInt()` methods.
|
||||
|
||||
## [0.8.0](https://github.com/brick/math/releases/tag/0.8.0) - 2018-10-13
|
||||
|
||||
**Breaking changes**
|
||||
|
||||
The following deprecated methods have been removed. Use the new method name instead:
|
||||
|
||||
| Method removed | Replacement method |
|
||||
| --- | --- |
|
||||
| `BigDecimal::getIntegral()` | `BigDecimal::getIntegralPart()` |
|
||||
| `BigDecimal::getFraction()` | `BigDecimal::getFractionalPart()` |
|
||||
|
||||
---
|
||||
|
||||
**New features**
|
||||
|
||||
`BigInteger` has been augmented with 5 new methods for bitwise operations:
|
||||
|
||||
| New method | Description |
|
||||
| --- | --- |
|
||||
| `and()` | performs a bitwise `AND` operation on two numbers |
|
||||
| `or()` | performs a bitwise `OR` operation on two numbers |
|
||||
| `xor()` | performs a bitwise `XOR` operation on two numbers |
|
||||
| `shiftedLeft()` | returns the number shifted left by a number of bits |
|
||||
| `shiftedRight()` | returns the number shifted right by a number of bits |
|
||||
|
||||
Thanks to @DASPRiD 👍
|
||||
|
||||
## [0.7.3](https://github.com/brick/math/releases/tag/0.7.3) - 2018-08-20
|
||||
|
||||
**New method:** `BigDecimal::hasNonZeroFractionalPart()`
|
||||
|
||||
**Renamed/deprecated methods:**
|
||||
|
||||
- `BigDecimal::getIntegral()` has been renamed to `getIntegralPart()` and is now deprecated
|
||||
- `BigDecimal::getFraction()` has been renamed to `getFractionalPart()` and is now deprecated
|
||||
|
||||
## [0.7.2](https://github.com/brick/math/releases/tag/0.7.2) - 2018-07-21
|
||||
|
||||
**Performance update**
|
||||
|
||||
`BigInteger::parse()` and `toBase()` now use GMP's built-in base conversion features when available.
|
||||
|
||||
## [0.7.1](https://github.com/brick/math/releases/tag/0.7.1) - 2018-03-01
|
||||
|
||||
This is a maintenance release, no code has been changed.
|
||||
|
||||
- When installed with `--no-dev`, the autoloader does not autoload tests anymore
|
||||
- Tests and other files unnecessary for production are excluded from the dist package
|
||||
|
||||
This will help make installations more compact.
|
||||
|
||||
## [0.7.0](https://github.com/brick/math/releases/tag/0.7.0) - 2017-10-02
|
||||
|
||||
Methods renamed:
|
||||
|
||||
- `BigNumber:sign()` has been renamed to `getSign()`
|
||||
- `BigDecimal::unscaledValue()` has been renamed to `getUnscaledValue()`
|
||||
- `BigDecimal::scale()` has been renamed to `getScale()`
|
||||
- `BigDecimal::integral()` has been renamed to `getIntegral()`
|
||||
- `BigDecimal::fraction()` has been renamed to `getFraction()`
|
||||
- `BigRational::numerator()` has been renamed to `getNumerator()`
|
||||
- `BigRational::denominator()` has been renamed to `getDenominator()`
|
||||
|
||||
Classes renamed:
|
||||
|
||||
- `ArithmeticException` has been renamed to `MathException`
|
||||
|
||||
## [0.6.2](https://github.com/brick/math/releases/tag/0.6.2) - 2017-10-02
|
||||
|
||||
The base class for all exceptions is now `MathException`.
|
||||
`ArithmeticException` has been deprecated, and will be removed in 0.7.0.
|
||||
|
||||
## [0.6.1](https://github.com/brick/math/releases/tag/0.6.1) - 2017-10-02
|
||||
|
||||
A number of methods have been renamed:
|
||||
|
||||
- `BigNumber:sign()` is deprecated; use `getSign()` instead
|
||||
- `BigDecimal::unscaledValue()` is deprecated; use `getUnscaledValue()` instead
|
||||
- `BigDecimal::scale()` is deprecated; use `getScale()` instead
|
||||
- `BigDecimal::integral()` is deprecated; use `getIntegral()` instead
|
||||
- `BigDecimal::fraction()` is deprecated; use `getFraction()` instead
|
||||
- `BigRational::numerator()` is deprecated; use `getNumerator()` instead
|
||||
- `BigRational::denominator()` is deprecated; use `getDenominator()` instead
|
||||
|
||||
The old methods will be removed in version 0.7.0.
|
||||
|
||||
## [0.6.0](https://github.com/brick/math/releases/tag/0.6.0) - 2017-08-25
|
||||
|
||||
- Minimum PHP version is now [7.1](https://gophp71.org/); for PHP 5.6 and PHP 7.0 support, use version `0.5`
|
||||
- Deprecated method `BigDecimal::withScale()` has been removed; use `toScale()` instead
|
||||
- Method `BigNumber::toInteger()` has been renamed to `toInt()`
|
||||
|
||||
## [0.5.4](https://github.com/brick/math/releases/tag/0.5.4) - 2016-10-17
|
||||
|
||||
`BigNumber` classes now implement [JsonSerializable](http://php.net/manual/en/class.jsonserializable.php).
|
||||
The JSON output is always a string.
|
||||
|
||||
## [0.5.3](https://github.com/brick/math/releases/tag/0.5.3) - 2016-03-31
|
||||
|
||||
This is a bugfix release. Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||
|
||||
## [0.5.2](https://github.com/brick/math/releases/tag/0.5.2) - 2015-08-06
|
||||
|
||||
The `$scale` parameter of `BigDecimal::dividedBy()` is now optional again.
|
||||
|
||||
## [0.5.1](https://github.com/brick/math/releases/tag/0.5.1) - 2015-07-05
|
||||
|
||||
**New method: `BigNumber::toScale()`**
|
||||
|
||||
This allows to convert any `BigNumber` to a `BigDecimal` with a given scale, using rounding if necessary.
|
||||
|
||||
## [0.5.0](https://github.com/brick/math/releases/tag/0.5.0) - 2015-07-04
|
||||
|
||||
**New features**
|
||||
- Common `BigNumber` interface for all classes, with the following methods:
|
||||
- `sign()` and derived methods (`isZero()`, `isPositive()`, ...)
|
||||
- `compareTo()` and derived methods (`isEqualTo()`, `isGreaterThan()`, ...) that work across different `BigNumber` types
|
||||
- `toBigInteger()`, `toBigDecimal()`, `toBigRational`() conversion methods
|
||||
- `toInteger()` and `toFloat()` conversion methods to native types
|
||||
- Unified `of()` behaviour: every class now accepts any type of number, provided that it can be safely converted to the current type
|
||||
- New method: `BigDecimal::exactlyDividedBy()`; this method automatically computes the scale of the result, provided that the division yields a finite number of digits
|
||||
- New methods: `BigRational::quotient()` and `remainder()`
|
||||
- Fine-grained exceptions: `DivisionByZeroException`, `RoundingNecessaryException`, `NumberFormatException`
|
||||
- Factory methods `zero()`, `one()` and `ten()` available in all classes
|
||||
- Rounding mode reintroduced in `BigInteger::dividedBy()`
|
||||
|
||||
This release also comes with many performance improvements.
|
||||
|
||||
---
|
||||
|
||||
**Breaking changes**
|
||||
- `BigInteger`:
|
||||
- `getSign()` is renamed to `sign()`
|
||||
- `toString()` is renamed to `toBase()`
|
||||
- `BigInteger::dividedBy()` now throws an exception by default if the remainder is not zero; use `quotient()` to get the previous behaviour
|
||||
- `BigDecimal`:
|
||||
- `getSign()` is renamed to `sign()`
|
||||
- `getUnscaledValue()` is renamed to `unscaledValue()`
|
||||
- `getScale()` is renamed to `scale()`
|
||||
- `getIntegral()` is renamed to `integral()`
|
||||
- `getFraction()` is renamed to `fraction()`
|
||||
- `divideAndRemainder()` is renamed to `quotientAndRemainder()`
|
||||
- `dividedBy()` now takes a **mandatory** `$scale` parameter **before** the rounding mode
|
||||
- `toBigInteger()` does not accept a `$roundingMode` parameter any more
|
||||
- `toBigRational()` does not simplify the fraction any more; explicitly add `->simplified()` to get the previous behaviour
|
||||
- `BigRational`:
|
||||
- `getSign()` is renamed to `sign()`
|
||||
- `getNumerator()` is renamed to `numerator()`
|
||||
- `getDenominator()` is renamed to `denominator()`
|
||||
- `of()` is renamed to `nd()`, while `parse()` is renamed to `of()`
|
||||
- Miscellaneous:
|
||||
- `ArithmeticException` is moved to an `Exception\` sub-namespace
|
||||
- `of()` factory methods now throw `NumberFormatException` instead of `InvalidArgumentException`
|
||||
|
||||
## [0.4.3](https://github.com/brick/math/releases/tag/0.4.3) - 2016-03-31
|
||||
|
||||
Backport of two bug fixes from the 0.5 branch:
|
||||
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
|
||||
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||
|
||||
## [0.4.2](https://github.com/brick/math/releases/tag/0.4.2) - 2015-06-16
|
||||
|
||||
New method: `BigDecimal::stripTrailingZeros()`
|
||||
|
||||
## [0.4.1](https://github.com/brick/math/releases/tag/0.4.1) - 2015-06-12
|
||||
|
||||
Introducing a `BigRational` class, to perform calculations on fractions of any size.
|
||||
|
||||
## [0.4.0](https://github.com/brick/math/releases/tag/0.4.0) - 2015-06-12
|
||||
|
||||
Rounding modes have been removed from `BigInteger`, and are now a concept specific to `BigDecimal`.
|
||||
|
||||
`BigInteger::dividedBy()` now always returns the quotient of the division.
|
||||
|
||||
## [0.3.5](https://github.com/brick/math/releases/tag/0.3.5) - 2016-03-31
|
||||
|
||||
Backport of two bug fixes from the 0.5 branch:
|
||||
|
||||
- `BigInteger::parse()` did not always throw `InvalidArgumentException` as expected
|
||||
- Dividing by a negative power of 1 with the same scale as the dividend could trigger an incorrect optimization which resulted in a wrong result. See #6.
|
||||
|
||||
## [0.3.4](https://github.com/brick/math/releases/tag/0.3.4) - 2015-06-11
|
||||
|
||||
New methods:
|
||||
- `BigInteger::remainder()` returns the remainder of a division only
|
||||
- `BigInteger::gcd()` returns the greatest common divisor of two numbers
|
||||
|
||||
## [0.3.3](https://github.com/brick/math/releases/tag/0.3.3) - 2015-06-07
|
||||
|
||||
Fix `toString()` not handling negative numbers.
|
||||
|
||||
## [0.3.2](https://github.com/brick/math/releases/tag/0.3.2) - 2015-06-07
|
||||
|
||||
`BigInteger` and `BigDecimal` now have a `getSign()` method that returns:
|
||||
- `-1` if the number is negative
|
||||
- `0` if the number is zero
|
||||
- `1` if the number is positive
|
||||
|
||||
## [0.3.1](https://github.com/brick/math/releases/tag/0.3.1) - 2015-06-05
|
||||
|
||||
Minor performance improvements
|
||||
|
||||
## [0.3.0](https://github.com/brick/math/releases/tag/0.3.0) - 2015-06-04
|
||||
|
||||
The `$roundingMode` and `$scale` parameters have been swapped in `BigDecimal::dividedBy()`.
|
||||
|
||||
## [0.2.2](https://github.com/brick/math/releases/tag/0.2.2) - 2015-06-04
|
||||
|
||||
Stronger immutability guarantee for `BigInteger` and `BigDecimal`.
|
||||
|
||||
So far, it would have been possible to break immutability of these classes by calling the `unserialize()` internal function. This release fixes that.
|
||||
|
||||
## [0.2.1](https://github.com/brick/math/releases/tag/0.2.1) - 2015-06-02
|
||||
|
||||
Added `BigDecimal::divideAndRemainder()`
|
||||
|
||||
## [0.2.0](https://github.com/brick/math/releases/tag/0.2.0) - 2015-05-22
|
||||
|
||||
- `min()` and `max()` do not accept an `array` any more, but a variable number of parameters
|
||||
- **minimum PHP version is now 5.6**
|
||||
- continuous integration with PHP 7
|
||||
|
||||
## [0.1.1](https://github.com/brick/math/releases/tag/0.1.1) - 2014-09-01
|
||||
|
||||
- Added `BigInteger::power()`
|
||||
- Added HHVM support
|
||||
|
||||
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
|
||||
|
||||
First beta release.
|
||||
|
20
vendor/brick/math/LICENSE
vendored
20
vendor/brick/math/LICENSE
vendored
@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-present Benjamin Morel
|
||||
|
||||
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.
|
17
vendor/brick/math/SECURITY.md
vendored
17
vendor/brick/math/SECURITY.md
vendored
@ -1,17 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the last two release streams are supported.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.9.x | :white_check_mark: |
|
||||
| 0.8.x | :white_check_mark: |
|
||||
| < 0.8 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security vulnerability, please use the
|
||||
[Tidelift security contact](https://tidelift.com/security).
|
||||
Tidelift will coordinate the fix and disclosure.
|
35
vendor/brick/math/composer.json
vendored
35
vendor/brick/math/composer.json
vendored
@ -1,35 +0,0 @@
|
||||
{
|
||||
"name": "brick/math",
|
||||
"description": "Arbitrary-precision arithmetic library",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"Brick",
|
||||
"Math",
|
||||
"Arbitrary-precision",
|
||||
"Arithmetic",
|
||||
"BigInteger",
|
||||
"BigDecimal",
|
||||
"BigRational",
|
||||
"Bignum"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
|
||||
"php-coveralls/php-coveralls": "^2.2",
|
||||
"vimeo/psalm": "4.9.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Brick\\Math\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Brick\\Math\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
895
vendor/brick/math/src/BigDecimal.php
vendored
895
vendor/brick/math/src/BigDecimal.php
vendored
@ -1,895 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math;
|
||||
|
||||
use Brick\Math\Exception\DivisionByZeroException;
|
||||
use Brick\Math\Exception\MathException;
|
||||
use Brick\Math\Exception\NegativeNumberException;
|
||||
use Brick\Math\Internal\Calculator;
|
||||
|
||||
/**
|
||||
* Immutable, arbitrary-precision signed decimal numbers.
|
||||
*
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class BigDecimal extends BigNumber
|
||||
{
|
||||
/**
|
||||
* The unscaled value of this decimal number.
|
||||
*
|
||||
* This is a string of digits with an optional leading minus sign.
|
||||
* No leading zero must be present.
|
||||
* No leading minus sign must be present if the value is 0.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* The scale (number of digits after the decimal point) of this decimal number.
|
||||
*
|
||||
* This must be zero or more.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $scale;
|
||||
|
||||
/**
|
||||
* Protected constructor. Use a factory method to obtain an instance.
|
||||
*
|
||||
* @param string $value The unscaled value, validated.
|
||||
* @param int $scale The scale, validated.
|
||||
*/
|
||||
protected function __construct(string $value, int $scale = 0)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->scale = $scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BigDecimal of the given value.
|
||||
*
|
||||
* @param BigNumber|int|float|string $value
|
||||
*
|
||||
* @return BigDecimal
|
||||
*
|
||||
* @throws MathException If the value cannot be converted to a BigDecimal.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function of($value) : BigNumber
|
||||
{
|
||||
return parent::of($value)->toBigDecimal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BigDecimal from an unscaled value and a scale.
|
||||
*
|
||||
* Example: `(12345, 3)` will result in the BigDecimal `12.345`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
|
||||
* @param int $scale The scale of the number, positive or zero.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*
|
||||
* @throws \InvalidArgumentException If the scale is negative.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function ofUnscaledValue($value, int $scale = 0) : BigDecimal
|
||||
{
|
||||
if ($scale < 0) {
|
||||
throw new \InvalidArgumentException('The scale cannot be negative.');
|
||||
}
|
||||
|
||||
return new BigDecimal((string) BigInteger::of($value), $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigDecimal representing zero, with a scale of zero.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function zero() : BigDecimal
|
||||
{
|
||||
/**
|
||||
* @psalm-suppress ImpureStaticVariable
|
||||
* @var BigDecimal|null $zero
|
||||
*/
|
||||
static $zero;
|
||||
|
||||
if ($zero === null) {
|
||||
$zero = new BigDecimal('0');
|
||||
}
|
||||
|
||||
return $zero;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigDecimal representing one, with a scale of zero.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function one() : BigDecimal
|
||||
{
|
||||
/**
|
||||
* @psalm-suppress ImpureStaticVariable
|
||||
* @var BigDecimal|null $one
|
||||
*/
|
||||
static $one;
|
||||
|
||||
if ($one === null) {
|
||||
$one = new BigDecimal('1');
|
||||
}
|
||||
|
||||
return $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigDecimal representing ten, with a scale of zero.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function ten() : BigDecimal
|
||||
{
|
||||
/**
|
||||
* @psalm-suppress ImpureStaticVariable
|
||||
* @var BigDecimal|null $ten
|
||||
*/
|
||||
static $ten;
|
||||
|
||||
if ($ten === null) {
|
||||
$ten = new BigDecimal('10');
|
||||
}
|
||||
|
||||
return $ten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of this number and the given one.
|
||||
*
|
||||
* The result has a scale of `max($this->scale, $that->scale)`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @return BigDecimal The result.
|
||||
*
|
||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||
*/
|
||||
public function plus($that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->value === '0' && $that->scale <= $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->value === '0' && $this->scale <= $that->scale) {
|
||||
return $that;
|
||||
}
|
||||
|
||||
[$a, $b] = $this->scaleValues($this, $that);
|
||||
|
||||
$value = Calculator::get()->add($a, $b);
|
||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference of this number and the given one.
|
||||
*
|
||||
* The result has a scale of `max($this->scale, $that->scale)`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @return BigDecimal The result.
|
||||
*
|
||||
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
|
||||
*/
|
||||
public function minus($that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->value === '0' && $that->scale <= $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
[$a, $b] = $this->scaleValues($this, $that);
|
||||
|
||||
$value = Calculator::get()->sub($a, $b);
|
||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the product of this number and the given one.
|
||||
*
|
||||
* The result has a scale of `$this->scale + $that->scale`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @return BigDecimal The result.
|
||||
*
|
||||
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
|
||||
*/
|
||||
public function multipliedBy($that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->value === '1' && $that->scale === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($this->value === '1' && $this->scale === 0) {
|
||||
return $that;
|
||||
}
|
||||
|
||||
$value = Calculator::get()->mul($this->value, $that->value);
|
||||
$scale = $this->scale + $that->scale;
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the division of this number by the given one, at the given scale.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor.
|
||||
* @param int|null $scale The desired scale, or null to use the scale of this number.
|
||||
* @param int $roundingMode An optional rounding mode.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*
|
||||
* @throws \InvalidArgumentException If the scale or rounding mode is invalid.
|
||||
* @throws MathException If the number is invalid, is zero, or rounding was necessary.
|
||||
*/
|
||||
public function dividedBy($that, ?int $scale = null, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->isZero()) {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
if ($scale === null) {
|
||||
$scale = $this->scale;
|
||||
} elseif ($scale < 0) {
|
||||
throw new \InvalidArgumentException('Scale cannot be negative.');
|
||||
}
|
||||
|
||||
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$p = $this->valueWithMinScale($that->scale + $scale);
|
||||
$q = $that->valueWithMinScale($this->scale - $scale);
|
||||
|
||||
$result = Calculator::get()->divRound($p, $q, $roundingMode);
|
||||
|
||||
return new BigDecimal($result, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the exact result of the division of this number by the given one.
|
||||
*
|
||||
* The scale of the result is automatically calculated to fit all the fraction digits.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @return BigDecimal The result.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
|
||||
* or the result yields an infinite number of digits.
|
||||
*/
|
||||
public function exactlyDividedBy($that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->value === '0') {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
[, $b] = $this->scaleValues($this, $that);
|
||||
|
||||
$d = \rtrim($b, '0');
|
||||
$scale = \strlen($b) - \strlen($d);
|
||||
|
||||
$calculator = Calculator::get();
|
||||
|
||||
foreach ([5, 2] as $prime) {
|
||||
for (;;) {
|
||||
$lastDigit = (int) $d[-1];
|
||||
|
||||
if ($lastDigit % $prime !== 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$d = $calculator->divQ($d, (string) $prime);
|
||||
$scale++;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->dividedBy($that, $scale)->stripTrailingZeros();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this number exponentiated to the given value.
|
||||
*
|
||||
* The result has a scale of `$this->scale * $exponent`.
|
||||
*
|
||||
* @param int $exponent The exponent.
|
||||
*
|
||||
* @return BigDecimal The result.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
||||
*/
|
||||
public function power(int $exponent) : BigDecimal
|
||||
{
|
||||
if ($exponent === 0) {
|
||||
return BigDecimal::one();
|
||||
}
|
||||
|
||||
if ($exponent === 1) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($exponent < 0 || $exponent > Calculator::MAX_POWER) {
|
||||
throw new \InvalidArgumentException(\sprintf(
|
||||
'The exponent %d is not in the range 0 to %d.',
|
||||
$exponent,
|
||||
Calculator::MAX_POWER
|
||||
));
|
||||
}
|
||||
|
||||
return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient of the division of this number by this given one.
|
||||
*
|
||||
* The quotient has a scale of `0`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @return BigDecimal The quotient.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
||||
*/
|
||||
public function quotient($that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->isZero()) {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
$p = $this->valueWithMinScale($that->scale);
|
||||
$q = $that->valueWithMinScale($this->scale);
|
||||
|
||||
$quotient = Calculator::get()->divQ($p, $q);
|
||||
|
||||
return new BigDecimal($quotient, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remainder of the division of this number by this given one.
|
||||
*
|
||||
* The remainder has a scale of `max($this->scale, $that->scale)`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @return BigDecimal The remainder.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
||||
*/
|
||||
public function remainder($that) : BigDecimal
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->isZero()) {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
$p = $this->valueWithMinScale($that->scale);
|
||||
$q = $that->valueWithMinScale($this->scale);
|
||||
|
||||
$remainder = Calculator::get()->divR($p, $q);
|
||||
|
||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
||||
|
||||
return new BigDecimal($remainder, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient and remainder of the division of this number by the given one.
|
||||
*
|
||||
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
|
||||
*
|
||||
* @return BigDecimal[] An array containing the quotient and the remainder.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid decimal number, or is zero.
|
||||
*/
|
||||
public function quotientAndRemainder($that) : array
|
||||
{
|
||||
$that = BigDecimal::of($that);
|
||||
|
||||
if ($that->isZero()) {
|
||||
throw DivisionByZeroException::divisionByZero();
|
||||
}
|
||||
|
||||
$p = $this->valueWithMinScale($that->scale);
|
||||
$q = $that->valueWithMinScale($this->scale);
|
||||
|
||||
[$quotient, $remainder] = Calculator::get()->divQR($p, $q);
|
||||
|
||||
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
|
||||
|
||||
$quotient = new BigDecimal($quotient, 0);
|
||||
$remainder = new BigDecimal($remainder, $scale);
|
||||
|
||||
return [$quotient, $remainder];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the square root of this number, rounded down to the given number of decimals.
|
||||
*
|
||||
* @param int $scale
|
||||
*
|
||||
* @return BigDecimal
|
||||
*
|
||||
* @throws \InvalidArgumentException If the scale is negative.
|
||||
* @throws NegativeNumberException If this number is negative.
|
||||
*/
|
||||
public function sqrt(int $scale) : BigDecimal
|
||||
{
|
||||
if ($scale < 0) {
|
||||
throw new \InvalidArgumentException('Scale cannot be negative.');
|
||||
}
|
||||
|
||||
if ($this->value === '0') {
|
||||
return new BigDecimal('0', $scale);
|
||||
}
|
||||
|
||||
if ($this->value[0] === '-') {
|
||||
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
|
||||
}
|
||||
|
||||
$value = $this->value;
|
||||
$addDigits = 2 * $scale - $this->scale;
|
||||
|
||||
if ($addDigits > 0) {
|
||||
// add zeros
|
||||
$value .= \str_repeat('0', $addDigits);
|
||||
} elseif ($addDigits < 0) {
|
||||
// trim digits
|
||||
if (-$addDigits >= \strlen($this->value)) {
|
||||
// requesting a scale too low, will always yield a zero result
|
||||
return new BigDecimal('0', $scale);
|
||||
}
|
||||
|
||||
$value = \substr($value, 0, $addDigits);
|
||||
}
|
||||
|
||||
$value = Calculator::get()->sqrt($value);
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
|
||||
*
|
||||
* @param int $n
|
||||
*
|
||||
* @return BigDecimal
|
||||
*/
|
||||
public function withPointMovedLeft(int $n) : BigDecimal
|
||||
{
|
||||
if ($n === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($n < 0) {
|
||||
return $this->withPointMovedRight(-$n);
|
||||
}
|
||||
|
||||
return new BigDecimal($this->value, $this->scale + $n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
|
||||
*
|
||||
* @param int $n
|
||||
*
|
||||
* @return BigDecimal
|
||||
*/
|
||||
public function withPointMovedRight(int $n) : BigDecimal
|
||||
{
|
||||
if ($n === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($n < 0) {
|
||||
return $this->withPointMovedLeft(-$n);
|
||||
}
|
||||
|
||||
$value = $this->value;
|
||||
$scale = $this->scale - $n;
|
||||
|
||||
if ($scale < 0) {
|
||||
if ($value !== '0') {
|
||||
$value .= \str_repeat('0', -$scale);
|
||||
}
|
||||
$scale = 0;
|
||||
}
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*/
|
||||
public function stripTrailingZeros() : BigDecimal
|
||||
{
|
||||
if ($this->scale === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$trimmedValue = \rtrim($this->value, '0');
|
||||
|
||||
if ($trimmedValue === '') {
|
||||
return BigDecimal::zero();
|
||||
}
|
||||
|
||||
$trimmableZeros = \strlen($this->value) - \strlen($trimmedValue);
|
||||
|
||||
if ($trimmableZeros === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($trimmableZeros > $this->scale) {
|
||||
$trimmableZeros = $this->scale;
|
||||
}
|
||||
|
||||
$value = \substr($this->value, 0, -$trimmableZeros);
|
||||
$scale = $this->scale - $trimmableZeros;
|
||||
|
||||
return new BigDecimal($value, $scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute value of this number.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*/
|
||||
public function abs() : BigDecimal
|
||||
{
|
||||
return $this->isNegative() ? $this->negated() : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated value of this number.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*/
|
||||
public function negated() : BigDecimal
|
||||
{
|
||||
return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compareTo($that) : int
|
||||
{
|
||||
$that = BigNumber::of($that);
|
||||
|
||||
if ($that instanceof BigInteger) {
|
||||
$that = $that->toBigDecimal();
|
||||
}
|
||||
|
||||
if ($that instanceof BigDecimal) {
|
||||
[$a, $b] = $this->scaleValues($this, $that);
|
||||
|
||||
return Calculator::get()->cmp($a, $b);
|
||||
}
|
||||
|
||||
return - $that->compareTo($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSign() : int
|
||||
{
|
||||
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BigInteger
|
||||
*/
|
||||
public function getUnscaledValue() : BigInteger
|
||||
{
|
||||
return BigInteger::create($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getScale() : int
|
||||
{
|
||||
return $this->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representing the integral part of this decimal number.
|
||||
*
|
||||
* Example: `-123.456` => `-123`.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIntegralPart() : string
|
||||
{
|
||||
if ($this->scale === 0) {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||
|
||||
return \substr($value, 0, -$this->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representing the fractional part of this decimal number.
|
||||
*
|
||||
* If the scale is zero, an empty string is returned.
|
||||
*
|
||||
* Examples: `-123.456` => '456', `123` => ''.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFractionalPart() : string
|
||||
{
|
||||
if ($this->scale === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||
|
||||
return \substr($value, -$this->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this decimal number has a non-zero fractional part.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNonZeroFractionalPart() : bool
|
||||
{
|
||||
return $this->getFractionalPart() !== \str_repeat('0', $this->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toBigInteger() : BigInteger
|
||||
{
|
||||
$zeroScaleDecimal = $this->scale === 0 ? $this : $this->dividedBy(1, 0);
|
||||
|
||||
return BigInteger::create($zeroScaleDecimal->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toBigDecimal() : BigDecimal
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toBigRational() : BigRational
|
||||
{
|
||||
$numerator = BigInteger::create($this->value);
|
||||
$denominator = BigInteger::create('1' . \str_repeat('0', $this->scale));
|
||||
|
||||
return BigRational::create($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
||||
{
|
||||
if ($scale === $this->scale) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->dividedBy(BigDecimal::one(), $scale, $roundingMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toInt() : int
|
||||
{
|
||||
return $this->toBigInteger()->toInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toFloat() : float
|
||||
{
|
||||
return (float) (string) $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString() : string
|
||||
{
|
||||
if ($this->scale === 0) {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
$value = $this->getUnscaledValueWithLeadingZeros();
|
||||
|
||||
return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required for serializing the object and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array{value: string, scale: int}
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return ['value' => $this->value, 'scale' => $this->scale];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-suppress RedundantPropertyInitializationCheck
|
||||
*
|
||||
* @param array{value: string, scale: int} $data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
if (isset($this->value)) {
|
||||
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||
}
|
||||
|
||||
$this->value = $data['value'];
|
||||
$this->scale = $data['scale'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required by interface Serializable and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize() : string
|
||||
{
|
||||
return $this->value . ':' . $this->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here to implement interface Serializable and cannot be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-suppress RedundantPropertyInitializationCheck
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function unserialize($value) : void
|
||||
{
|
||||
if (isset($this->value)) {
|
||||
throw new \LogicException('unserialize() is an internal function, it must not be called directly.');
|
||||
}
|
||||
|
||||
[$value, $scale] = \explode(':', $value);
|
||||
|
||||
$this->value = $value;
|
||||
$this->scale = (int) $scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the internal values of the given decimal numbers on the same scale.
|
||||
*
|
||||
* @param BigDecimal $x The first decimal number.
|
||||
* @param BigDecimal $y The second decimal number.
|
||||
*
|
||||
* @return array{string, string} The scaled integer values of $x and $y.
|
||||
*/
|
||||
private function scaleValues(BigDecimal $x, BigDecimal $y) : array
|
||||
{
|
||||
$a = $x->value;
|
||||
$b = $y->value;
|
||||
|
||||
if ($b !== '0' && $x->scale > $y->scale) {
|
||||
$b .= \str_repeat('0', $x->scale - $y->scale);
|
||||
} elseif ($a !== '0' && $x->scale < $y->scale) {
|
||||
$a .= \str_repeat('0', $y->scale - $x->scale);
|
||||
}
|
||||
|
||||
return [$a, $b];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $scale
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function valueWithMinScale(int $scale) : string
|
||||
{
|
||||
$value = $this->value;
|
||||
|
||||
if ($this->value !== '0' && $scale > $this->scale) {
|
||||
$value .= \str_repeat('0', $scale - $this->scale);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getUnscaledValueWithLeadingZeros() : string
|
||||
{
|
||||
$value = $this->value;
|
||||
$targetLength = $this->scale + 1;
|
||||
$negative = ($value[0] === '-');
|
||||
$length = \strlen($value);
|
||||
|
||||
if ($negative) {
|
||||
$length--;
|
||||
}
|
||||
|
||||
if ($length >= $targetLength) {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
if ($negative) {
|
||||
$value = \substr($value, 1);
|
||||
}
|
||||
|
||||
$value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT);
|
||||
|
||||
if ($negative) {
|
||||
$value = '-' . $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
1184
vendor/brick/math/src/BigInteger.php
vendored
1184
vendor/brick/math/src/BigInteger.php
vendored
File diff suppressed because it is too large
Load Diff
572
vendor/brick/math/src/BigNumber.php
vendored
572
vendor/brick/math/src/BigNumber.php
vendored
@ -1,572 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math;
|
||||
|
||||
use Brick\Math\Exception\DivisionByZeroException;
|
||||
use Brick\Math\Exception\MathException;
|
||||
use Brick\Math\Exception\NumberFormatException;
|
||||
use Brick\Math\Exception\RoundingNecessaryException;
|
||||
|
||||
/**
|
||||
* Common interface for arbitrary-precision rational numbers.
|
||||
*
|
||||
* @psalm-immutable
|
||||
*/
|
||||
abstract class BigNumber implements \Serializable, \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* The regular expression used to parse integer, decimal and rational numbers.
|
||||
*/
|
||||
private const PARSE_REGEXP =
|
||||
'/^' .
|
||||
'(?<sign>[\-\+])?' .
|
||||
'(?:' .
|
||||
'(?:' .
|
||||
'(?<integral>[0-9]+)?' .
|
||||
'(?<point>\.)?' .
|
||||
'(?<fractional>[0-9]+)?' .
|
||||
'(?:[eE](?<exponent>[\-\+]?[0-9]+))?' .
|
||||
')|(?:' .
|
||||
'(?<numerator>[0-9]+)' .
|
||||
'\/?' .
|
||||
'(?<denominator>[0-9]+)' .
|
||||
')' .
|
||||
')' .
|
||||
'$/';
|
||||
|
||||
/**
|
||||
* Creates a BigNumber of the given value.
|
||||
*
|
||||
* The concrete return type is dependent on the given value, with the following rules:
|
||||
*
|
||||
* - BigNumber instances are returned as is
|
||||
* - integer numbers are returned as BigInteger
|
||||
* - floating point numbers are converted to a string then parsed as such
|
||||
* - strings containing a `/` character are returned as BigRational
|
||||
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal
|
||||
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
|
||||
*
|
||||
* @param BigNumber|int|float|string $value
|
||||
*
|
||||
* @return BigNumber
|
||||
*
|
||||
* @throws NumberFormatException If the format of the number is not valid.
|
||||
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function of($value) : BigNumber
|
||||
{
|
||||
if ($value instanceof BigNumber) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (\is_int($value)) {
|
||||
return new BigInteger((string) $value);
|
||||
}
|
||||
|
||||
/** @psalm-suppress RedundantCastGivenDocblockType We cannot trust the untyped $value here! */
|
||||
$value = \is_float($value) ? self::floatToString($value) : (string) $value;
|
||||
|
||||
$throw = static function() use ($value) : void {
|
||||
throw new NumberFormatException(\sprintf(
|
||||
'The given value "%s" does not represent a valid number.',
|
||||
$value
|
||||
));
|
||||
};
|
||||
|
||||
if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) {
|
||||
$throw();
|
||||
}
|
||||
|
||||
$getMatch = static function(string $value) use ($matches) : ?string {
|
||||
return isset($matches[$value]) && $matches[$value] !== '' ? $matches[$value] : null;
|
||||
};
|
||||
|
||||
$sign = $getMatch('sign');
|
||||
$numerator = $getMatch('numerator');
|
||||
$denominator = $getMatch('denominator');
|
||||
|
||||
if ($numerator !== null) {
|
||||
assert($denominator !== null);
|
||||
|
||||
if ($sign !== null) {
|
||||
$numerator = $sign . $numerator;
|
||||
}
|
||||
|
||||
$numerator = self::cleanUp($numerator);
|
||||
$denominator = self::cleanUp($denominator);
|
||||
|
||||
if ($denominator === '0') {
|
||||
throw DivisionByZeroException::denominatorMustNotBeZero();
|
||||
}
|
||||
|
||||
return new BigRational(
|
||||
new BigInteger($numerator),
|
||||
new BigInteger($denominator),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
$point = $getMatch('point');
|
||||
$integral = $getMatch('integral');
|
||||
$fractional = $getMatch('fractional');
|
||||
$exponent = $getMatch('exponent');
|
||||
|
||||
if ($integral === null && $fractional === null) {
|
||||
$throw();
|
||||
}
|
||||
|
||||
if ($integral === null) {
|
||||
$integral = '0';
|
||||
}
|
||||
|
||||
if ($point !== null || $exponent !== null) {
|
||||
$fractional = ($fractional ?? '');
|
||||
$exponent = ($exponent !== null) ? (int) $exponent : 0;
|
||||
|
||||
if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) {
|
||||
throw new NumberFormatException('Exponent too large.');
|
||||
}
|
||||
|
||||
$unscaledValue = self::cleanUp(($sign ?? ''). $integral . $fractional);
|
||||
|
||||
$scale = \strlen($fractional) - $exponent;
|
||||
|
||||
if ($scale < 0) {
|
||||
if ($unscaledValue !== '0') {
|
||||
$unscaledValue .= \str_repeat('0', - $scale);
|
||||
}
|
||||
$scale = 0;
|
||||
}
|
||||
|
||||
return new BigDecimal($unscaledValue, $scale);
|
||||
}
|
||||
|
||||
$integral = self::cleanUp(($sign ?? '') . $integral);
|
||||
|
||||
return new BigInteger($integral);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely converts float to string, avoiding locale-dependent issues.
|
||||
*
|
||||
* @see https://github.com/brick/math/pull/20
|
||||
*
|
||||
* @param float $float
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureFunctionCall
|
||||
*/
|
||||
private static function floatToString(float $float) : string
|
||||
{
|
||||
$currentLocale = \setlocale(LC_NUMERIC, '0');
|
||||
\setlocale(LC_NUMERIC, 'C');
|
||||
|
||||
$result = (string) $float;
|
||||
|
||||
\setlocale(LC_NUMERIC, $currentLocale);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy method to access protected constructors from sibling classes.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @param mixed ...$args The arguments to the constructor.
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-suppress TooManyArguments
|
||||
* @psalm-suppress UnsafeInstantiation
|
||||
*/
|
||||
protected static function create(... $args) : BigNumber
|
||||
{
|
||||
return new static(... $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum of the given values.
|
||||
*
|
||||
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
|
||||
* to an instance of the class this method is called on.
|
||||
*
|
||||
* @return static The minimum value.
|
||||
*
|
||||
* @throws \InvalidArgumentException If no values are given.
|
||||
* @throws MathException If an argument is not valid.
|
||||
*
|
||||
* @psalm-suppress LessSpecificReturnStatement
|
||||
* @psalm-suppress MoreSpecificReturnType
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function min(...$values) : BigNumber
|
||||
{
|
||||
$min = null;
|
||||
|
||||
foreach ($values as $value) {
|
||||
$value = static::of($value);
|
||||
|
||||
if ($min === null || $value->isLessThan($min)) {
|
||||
$min = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($min === null) {
|
||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||
}
|
||||
|
||||
return $min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum of the given values.
|
||||
*
|
||||
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
|
||||
* to an instance of the class this method is called on.
|
||||
*
|
||||
* @return static The maximum value.
|
||||
*
|
||||
* @throws \InvalidArgumentException If no values are given.
|
||||
* @throws MathException If an argument is not valid.
|
||||
*
|
||||
* @psalm-suppress LessSpecificReturnStatement
|
||||
* @psalm-suppress MoreSpecificReturnType
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function max(...$values) : BigNumber
|
||||
{
|
||||
$max = null;
|
||||
|
||||
foreach ($values as $value) {
|
||||
$value = static::of($value);
|
||||
|
||||
if ($max === null || $value->isGreaterThan($max)) {
|
||||
$max = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($max === null) {
|
||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||
}
|
||||
|
||||
return $max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of the given values.
|
||||
*
|
||||
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers need to be convertible
|
||||
* to an instance of the class this method is called on.
|
||||
*
|
||||
* @return static The sum.
|
||||
*
|
||||
* @throws \InvalidArgumentException If no values are given.
|
||||
* @throws MathException If an argument is not valid.
|
||||
*
|
||||
* @psalm-suppress LessSpecificReturnStatement
|
||||
* @psalm-suppress MoreSpecificReturnType
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function sum(...$values) : BigNumber
|
||||
{
|
||||
/** @var BigNumber|null $sum */
|
||||
$sum = null;
|
||||
|
||||
foreach ($values as $value) {
|
||||
$value = static::of($value);
|
||||
|
||||
$sum = $sum === null ? $value : self::add($sum, $value);
|
||||
}
|
||||
|
||||
if ($sum === null) {
|
||||
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
|
||||
}
|
||||
|
||||
return $sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds two BigNumber instances in the correct order to avoid a RoundingNecessaryException.
|
||||
*
|
||||
* @todo This could be better resolved by creating an abstract protected method in BigNumber, and leaving to
|
||||
* concrete classes the responsibility to perform the addition themselves or delegate it to the given number,
|
||||
* depending on their ability to perform the operation. This will also require a version bump because we're
|
||||
* potentially breaking custom BigNumber implementations (if any...)
|
||||
*
|
||||
* @param BigNumber $a
|
||||
* @param BigNumber $b
|
||||
*
|
||||
* @return BigNumber
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
private static function add(BigNumber $a, BigNumber $b) : BigNumber
|
||||
{
|
||||
if ($a instanceof BigRational) {
|
||||
return $a->plus($b);
|
||||
}
|
||||
|
||||
if ($b instanceof BigRational) {
|
||||
return $b->plus($a);
|
||||
}
|
||||
|
||||
if ($a instanceof BigDecimal) {
|
||||
return $a->plus($b);
|
||||
}
|
||||
|
||||
if ($b instanceof BigDecimal) {
|
||||
return $b->plus($a);
|
||||
}
|
||||
|
||||
/** @var BigInteger $a */
|
||||
|
||||
return $a->plus($b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes optional leading zeros and + sign from the given number.
|
||||
*
|
||||
* @param string $number The number, validated as a non-empty string of digits with optional leading sign.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
private static function cleanUp(string $number) : string
|
||||
{
|
||||
$firstChar = $number[0];
|
||||
|
||||
if ($firstChar === '+' || $firstChar === '-') {
|
||||
$number = \substr($number, 1);
|
||||
}
|
||||
|
||||
$number = \ltrim($number, '0');
|
||||
|
||||
if ($number === '') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
if ($firstChar === '-') {
|
||||
return '-' . $number;
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is equal to the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEqualTo($that) : bool
|
||||
{
|
||||
return $this->compareTo($that) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly lower than the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLessThan($that) : bool
|
||||
{
|
||||
return $this->compareTo($that) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is lower than or equal to the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLessThanOrEqualTo($that) : bool
|
||||
{
|
||||
return $this->compareTo($that) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly greater than the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isGreaterThan($that) : bool
|
||||
{
|
||||
return $this->compareTo($that) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is greater than or equal to the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isGreaterThanOrEqualTo($that) : bool
|
||||
{
|
||||
return $this->compareTo($that) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number equals zero.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isZero() : bool
|
||||
{
|
||||
return $this->getSign() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly negative.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNegative() : bool
|
||||
{
|
||||
return $this->getSign() < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is negative or zero.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNegativeOrZero() : bool
|
||||
{
|
||||
return $this->getSign() <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is strictly positive.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPositive() : bool
|
||||
{
|
||||
return $this->getSign() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this number is positive or zero.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPositiveOrZero() : bool
|
||||
{
|
||||
return $this->getSign() >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sign of this number.
|
||||
*
|
||||
* @return int -1 if the number is negative, 0 if zero, 1 if positive.
|
||||
*/
|
||||
abstract public function getSign() : int;
|
||||
|
||||
/**
|
||||
* Compares this number to the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that
|
||||
*
|
||||
* @return int [-1,0,1] If `$this` is lower than, equal to, or greater than `$that`.
|
||||
*
|
||||
* @throws MathException If the number is not valid.
|
||||
*/
|
||||
abstract public function compareTo($that) : int;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigInteger.
|
||||
*
|
||||
* @return BigInteger The converted number.
|
||||
*
|
||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
|
||||
*/
|
||||
abstract public function toBigInteger() : BigInteger;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigDecimal.
|
||||
*
|
||||
* @return BigDecimal The converted number.
|
||||
*
|
||||
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
|
||||
*/
|
||||
abstract public function toBigDecimal() : BigDecimal;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigRational.
|
||||
*
|
||||
* @return BigRational The converted number.
|
||||
*/
|
||||
abstract public function toBigRational() : BigRational;
|
||||
|
||||
/**
|
||||
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
|
||||
*
|
||||
* @param int $scale The scale of the resulting `BigDecimal`.
|
||||
* @param int $roundingMode A `RoundingMode` constant.
|
||||
*
|
||||
* @return BigDecimal
|
||||
*
|
||||
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
|
||||
* This only applies when RoundingMode::UNNECESSARY is used.
|
||||
*/
|
||||
abstract public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
|
||||
|
||||
/**
|
||||
* Returns the exact value of this number as a native integer.
|
||||
*
|
||||
* If this number cannot be converted to a native integer without losing precision, an exception is thrown.
|
||||
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
|
||||
*
|
||||
* @return int The converted value.
|
||||
*
|
||||
* @throws MathException If this number cannot be exactly converted to a native integer.
|
||||
*/
|
||||
abstract public function toInt() : int;
|
||||
|
||||
/**
|
||||
* Returns an approximation of this number as a floating-point value.
|
||||
*
|
||||
* Note that this method can discard information as the precision of a floating-point value
|
||||
* is inherently limited.
|
||||
*
|
||||
* If the number is greater than the largest representable floating point number, positive infinity is returned.
|
||||
* If the number is less than the smallest representable floating point number, negative infinity is returned.
|
||||
*
|
||||
* @return float The converted value.
|
||||
*/
|
||||
abstract public function toFloat() : float;
|
||||
|
||||
/**
|
||||
* Returns a string representation of this number.
|
||||
*
|
||||
* The output of this method can be parsed by the `of()` factory method;
|
||||
* this will yield an object equal to this one, without any information loss.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function __toString() : string;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function jsonSerialize() : string
|
||||
{
|
||||
return $this->__toString();
|
||||
}
|
||||
}
|
523
vendor/brick/math/src/BigRational.php
vendored
523
vendor/brick/math/src/BigRational.php
vendored
@ -1,523 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math;
|
||||
|
||||
use Brick\Math\Exception\DivisionByZeroException;
|
||||
use Brick\Math\Exception\MathException;
|
||||
use Brick\Math\Exception\NumberFormatException;
|
||||
use Brick\Math\Exception\RoundingNecessaryException;
|
||||
|
||||
/**
|
||||
* An arbitrarily large rational number.
|
||||
*
|
||||
* This class is immutable.
|
||||
*
|
||||
* @psalm-immutable
|
||||
*/
|
||||
final class BigRational extends BigNumber
|
||||
{
|
||||
/**
|
||||
* The numerator.
|
||||
*
|
||||
* @var BigInteger
|
||||
*/
|
||||
private $numerator;
|
||||
|
||||
/**
|
||||
* The denominator. Always strictly positive.
|
||||
*
|
||||
* @var BigInteger
|
||||
*/
|
||||
private $denominator;
|
||||
|
||||
/**
|
||||
* Protected constructor. Use a factory method to obtain an instance.
|
||||
*
|
||||
* @param BigInteger $numerator The numerator.
|
||||
* @param BigInteger $denominator The denominator.
|
||||
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
|
||||
*
|
||||
* @throws DivisionByZeroException If the denominator is zero.
|
||||
*/
|
||||
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
|
||||
{
|
||||
if ($checkDenominator) {
|
||||
if ($denominator->isZero()) {
|
||||
throw DivisionByZeroException::denominatorMustNotBeZero();
|
||||
}
|
||||
|
||||
if ($denominator->isNegative()) {
|
||||
$numerator = $numerator->negated();
|
||||
$denominator = $denominator->negated();
|
||||
}
|
||||
}
|
||||
|
||||
$this->numerator = $numerator;
|
||||
$this->denominator = $denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BigRational of the given value.
|
||||
*
|
||||
* @param BigNumber|int|float|string $value
|
||||
*
|
||||
* @return BigRational
|
||||
*
|
||||
* @throws MathException If the value cannot be converted to a BigRational.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function of($value) : BigNumber
|
||||
{
|
||||
return parent::of($value)->toBigRational();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BigRational out of a numerator and a denominator.
|
||||
*
|
||||
* If the denominator is negative, the signs of both the numerator and the denominator
|
||||
* will be inverted to ensure that the denominator is always positive.
|
||||
*
|
||||
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
|
||||
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
|
||||
*
|
||||
* @return BigRational
|
||||
*
|
||||
* @throws NumberFormatException If an argument does not represent a valid number.
|
||||
* @throws RoundingNecessaryException If an argument represents a non-integer number.
|
||||
* @throws DivisionByZeroException If the denominator is zero.
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function nd($numerator, $denominator) : BigRational
|
||||
{
|
||||
$numerator = BigInteger::of($numerator);
|
||||
$denominator = BigInteger::of($denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigRational representing zero.
|
||||
*
|
||||
* @return BigRational
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function zero() : BigRational
|
||||
{
|
||||
/**
|
||||
* @psalm-suppress ImpureStaticVariable
|
||||
* @var BigRational|null $zero
|
||||
*/
|
||||
static $zero;
|
||||
|
||||
if ($zero === null) {
|
||||
$zero = new BigRational(BigInteger::zero(), BigInteger::one(), false);
|
||||
}
|
||||
|
||||
return $zero;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigRational representing one.
|
||||
*
|
||||
* @return BigRational
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function one() : BigRational
|
||||
{
|
||||
/**
|
||||
* @psalm-suppress ImpureStaticVariable
|
||||
* @var BigRational|null $one
|
||||
*/
|
||||
static $one;
|
||||
|
||||
if ($one === null) {
|
||||
$one = new BigRational(BigInteger::one(), BigInteger::one(), false);
|
||||
}
|
||||
|
||||
return $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BigRational representing ten.
|
||||
*
|
||||
* @return BigRational
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function ten() : BigRational
|
||||
{
|
||||
/**
|
||||
* @psalm-suppress ImpureStaticVariable
|
||||
* @var BigRational|null $ten
|
||||
*/
|
||||
static $ten;
|
||||
|
||||
if ($ten === null) {
|
||||
$ten = new BigRational(BigInteger::ten(), BigInteger::one(), false);
|
||||
}
|
||||
|
||||
return $ten;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BigInteger
|
||||
*/
|
||||
public function getNumerator() : BigInteger
|
||||
{
|
||||
return $this->numerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BigInteger
|
||||
*/
|
||||
public function getDenominator() : BigInteger
|
||||
{
|
||||
return $this->denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient of the division of the numerator by the denominator.
|
||||
*
|
||||
* @return BigInteger
|
||||
*/
|
||||
public function quotient() : BigInteger
|
||||
{
|
||||
return $this->numerator->quotient($this->denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remainder of the division of the numerator by the denominator.
|
||||
*
|
||||
* @return BigInteger
|
||||
*/
|
||||
public function remainder() : BigInteger
|
||||
{
|
||||
return $this->numerator->remainder($this->denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quotient and remainder of the division of the numerator by the denominator.
|
||||
*
|
||||
* @return BigInteger[]
|
||||
*/
|
||||
public function quotientAndRemainder() : array
|
||||
{
|
||||
return $this->numerator->quotientAndRemainder($this->denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sum of this number and the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The number to add.
|
||||
*
|
||||
* @return BigRational The result.
|
||||
*
|
||||
* @throws MathException If the number is not valid.
|
||||
*/
|
||||
public function plus($that) : BigRational
|
||||
{
|
||||
$that = BigRational::of($that);
|
||||
|
||||
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||
$numerator = $numerator->plus($that->numerator->multipliedBy($this->denominator));
|
||||
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference of this number and the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The number to subtract.
|
||||
*
|
||||
* @return BigRational The result.
|
||||
*
|
||||
* @throws MathException If the number is not valid.
|
||||
*/
|
||||
public function minus($that) : BigRational
|
||||
{
|
||||
$that = BigRational::of($that);
|
||||
|
||||
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||
$numerator = $numerator->minus($that->numerator->multipliedBy($this->denominator));
|
||||
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the product of this number and the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The multiplier.
|
||||
*
|
||||
* @return BigRational The result.
|
||||
*
|
||||
* @throws MathException If the multiplier is not a valid number.
|
||||
*/
|
||||
public function multipliedBy($that) : BigRational
|
||||
{
|
||||
$that = BigRational::of($that);
|
||||
|
||||
$numerator = $this->numerator->multipliedBy($that->numerator);
|
||||
$denominator = $this->denominator->multipliedBy($that->denominator);
|
||||
|
||||
return new BigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the division of this number by the given one.
|
||||
*
|
||||
* @param BigNumber|int|float|string $that The divisor.
|
||||
*
|
||||
* @return BigRational The result.
|
||||
*
|
||||
* @throws MathException If the divisor is not a valid number, or is zero.
|
||||
*/
|
||||
public function dividedBy($that) : BigRational
|
||||
{
|
||||
$that = BigRational::of($that);
|
||||
|
||||
$numerator = $this->numerator->multipliedBy($that->denominator);
|
||||
$denominator = $this->denominator->multipliedBy($that->numerator);
|
||||
|
||||
return new BigRational($numerator, $denominator, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this number exponentiated to the given value.
|
||||
*
|
||||
* @param int $exponent The exponent.
|
||||
*
|
||||
* @return BigRational The result.
|
||||
*
|
||||
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
|
||||
*/
|
||||
public function power(int $exponent) : BigRational
|
||||
{
|
||||
if ($exponent === 0) {
|
||||
$one = BigInteger::one();
|
||||
|
||||
return new BigRational($one, $one, false);
|
||||
}
|
||||
|
||||
if ($exponent === 1) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new BigRational(
|
||||
$this->numerator->power($exponent),
|
||||
$this->denominator->power($exponent),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reciprocal of this BigRational.
|
||||
*
|
||||
* The reciprocal has the numerator and denominator swapped.
|
||||
*
|
||||
* @return BigRational
|
||||
*
|
||||
* @throws DivisionByZeroException If the numerator is zero.
|
||||
*/
|
||||
public function reciprocal() : BigRational
|
||||
{
|
||||
return new BigRational($this->denominator, $this->numerator, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute value of this BigRational.
|
||||
*
|
||||
* @return BigRational
|
||||
*/
|
||||
public function abs() : BigRational
|
||||
{
|
||||
return new BigRational($this->numerator->abs(), $this->denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated value of this BigRational.
|
||||
*
|
||||
* @return BigRational
|
||||
*/
|
||||
public function negated() : BigRational
|
||||
{
|
||||
return new BigRational($this->numerator->negated(), $this->denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the simplified value of this BigRational.
|
||||
*
|
||||
* @return BigRational
|
||||
*/
|
||||
public function simplified() : BigRational
|
||||
{
|
||||
$gcd = $this->numerator->gcd($this->denominator);
|
||||
|
||||
$numerator = $this->numerator->quotient($gcd);
|
||||
$denominator = $this->denominator->quotient($gcd);
|
||||
|
||||
return new BigRational($numerator, $denominator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compareTo($that) : int
|
||||
{
|
||||
return $this->minus($that)->getSign();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSign() : int
|
||||
{
|
||||
return $this->numerator->getSign();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toBigInteger() : BigInteger
|
||||
{
|
||||
$simplified = $this->simplified();
|
||||
|
||||
if (! $simplified->denominator->isEqualTo(1)) {
|
||||
throw new RoundingNecessaryException('This rational number cannot be represented as an integer value without rounding.');
|
||||
}
|
||||
|
||||
return $simplified->numerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toBigDecimal() : BigDecimal
|
||||
{
|
||||
return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toBigRational() : BigRational
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toScale(int $scale, int $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
|
||||
{
|
||||
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toInt() : int
|
||||
{
|
||||
return $this->toBigInteger()->toInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toFloat() : float
|
||||
{
|
||||
return $this->numerator->toFloat() / $this->denominator->toFloat();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString() : string
|
||||
{
|
||||
$numerator = (string) $this->numerator;
|
||||
$denominator = (string) $this->denominator;
|
||||
|
||||
if ($denominator === '1') {
|
||||
return $numerator;
|
||||
}
|
||||
|
||||
return $this->numerator . '/' . $this->denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required for serializing the object and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array{numerator: BigInteger, denominator: BigInteger}
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
return ['numerator' => $this->numerator, 'denominator' => $this->denominator];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here to allow unserializing the object and cannot be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-suppress RedundantPropertyInitializationCheck
|
||||
*
|
||||
* @param array{numerator: BigInteger, denominator: BigInteger} $data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
if (isset($this->numerator)) {
|
||||
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
|
||||
}
|
||||
|
||||
$this->numerator = $data['numerator'];
|
||||
$this->denominator = $data['denominator'];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is required by interface Serializable and SHOULD NOT be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function serialize() : string
|
||||
{
|
||||
return $this->numerator . '/' . $this->denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only here to implement interface Serializable and cannot be accessed directly.
|
||||
*
|
||||
* @internal
|
||||
* @psalm-suppress RedundantPropertyInitializationCheck
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function unserialize($value) : void
|
||||
{
|
||||
if (isset($this->numerator)) {
|
||||
throw new \LogicException('unserialize() is an internal function, it must not be called directly.');
|
||||
}
|
||||
|
||||
[$numerator, $denominator] = \explode('/', $value);
|
||||
|
||||
$this->numerator = BigInteger::of($numerator);
|
||||
$this->denominator = BigInteger::of($denominator);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a division by zero occurs.
|
||||
*/
|
||||
class DivisionByZeroException extends MathException
|
||||
{
|
||||
/**
|
||||
* @return DivisionByZeroException
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function divisionByZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('Division by zero.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DivisionByZeroException
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function modulusMustNotBeZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('The modulus must not be zero.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DivisionByZeroException
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function denominatorMustNotBeZero() : DivisionByZeroException
|
||||
{
|
||||
return new self('The denominator of a rational number cannot be zero.');
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
use Brick\Math\BigInteger;
|
||||
|
||||
/**
|
||||
* Exception thrown when an integer overflow occurs.
|
||||
*/
|
||||
class IntegerOverflowException extends MathException
|
||||
{
|
||||
/**
|
||||
* @param BigInteger $value
|
||||
*
|
||||
* @return IntegerOverflowException
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function toIntOverflow(BigInteger $value) : IntegerOverflowException
|
||||
{
|
||||
$message = '%s is out of range %d to %d and cannot be represented as an integer.';
|
||||
|
||||
return new self(\sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX));
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Base class for all math exceptions.
|
||||
*
|
||||
* This class is abstract to ensure that only fine-grained exceptions are thrown throughout the code.
|
||||
*/
|
||||
class MathException extends \RuntimeException
|
||||
{
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when attempting to perform an unsupported operation, such as a square root, on a negative number.
|
||||
*/
|
||||
class NegativeNumberException extends MathException
|
||||
{
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when attempting to create a number from a string with an invalid format.
|
||||
*/
|
||||
class NumberFormatException extends MathException
|
||||
{
|
||||
/**
|
||||
* @param string $char The failing character.
|
||||
*
|
||||
* @return NumberFormatException
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function charNotInAlphabet(string $char) : self
|
||||
{
|
||||
$ord = \ord($char);
|
||||
|
||||
if ($ord < 32 || $ord > 126) {
|
||||
$char = \strtoupper(\dechex($ord));
|
||||
|
||||
if ($ord < 10) {
|
||||
$char = '0' . $char;
|
||||
}
|
||||
} else {
|
||||
$char = '"' . $char . '"';
|
||||
}
|
||||
|
||||
return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char));
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a number cannot be represented at the requested scale without rounding.
|
||||
*/
|
||||
class RoundingNecessaryException extends MathException
|
||||
{
|
||||
/**
|
||||
* @return RoundingNecessaryException
|
||||
*
|
||||
* @psalm-pure
|
||||
*/
|
||||
public static function roundingNecessary() : RoundingNecessaryException
|
||||
{
|
||||
return new self('Rounding is necessary to represent the result of the operation at this scale.');
|
||||
}
|
||||
}
|
756
vendor/brick/math/src/Internal/Calculator.php
vendored
756
vendor/brick/math/src/Internal/Calculator.php
vendored
@ -1,756 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal;
|
||||
|
||||
use Brick\Math\Exception\RoundingNecessaryException;
|
||||
use Brick\Math\RoundingMode;
|
||||
|
||||
/**
|
||||
* Performs basic operations on arbitrary size integers.
|
||||
*
|
||||
* Unless otherwise specified, all parameters must be validated as non-empty strings of digits,
|
||||
* without leading zero, and with an optional leading minus sign if the number is not zero.
|
||||
*
|
||||
* Any other parameter format will lead to undefined behaviour.
|
||||
* All methods must return strings respecting this format, unless specified otherwise.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @psalm-immutable
|
||||
*/
|
||||
abstract class Calculator
|
||||
{
|
||||
/**
|
||||
* The maximum exponent value allowed for the pow() method.
|
||||
*/
|
||||
public const MAX_POWER = 1000000;
|
||||
|
||||
/**
|
||||
* The alphabet for converting from and to base 2 to 36, lowercase.
|
||||
*/
|
||||
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
/**
|
||||
* The Calculator instance in use.
|
||||
*
|
||||
* @var Calculator|null
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* Sets the Calculator instance to use.
|
||||
*
|
||||
* An instance is typically set only in unit tests: the autodetect is usually the best option.
|
||||
*
|
||||
* @param Calculator|null $calculator The calculator instance, or NULL to revert to autodetect.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
final public static function set(?Calculator $calculator) : void
|
||||
{
|
||||
self::$instance = $calculator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Calculator instance to use.
|
||||
*
|
||||
* If none has been explicitly set, the fastest available implementation will be returned.
|
||||
*
|
||||
* @return Calculator
|
||||
*
|
||||
* @psalm-pure
|
||||
* @psalm-suppress ImpureStaticProperty
|
||||
*/
|
||||
final public static function get() : Calculator
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
/** @psalm-suppress ImpureMethodCall */
|
||||
self::$instance = self::detect();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fastest available Calculator implementation.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @return Calculator
|
||||
*/
|
||||
private static function detect() : Calculator
|
||||
{
|
||||
if (\extension_loaded('gmp')) {
|
||||
return new Calculator\GmpCalculator();
|
||||
}
|
||||
|
||||
if (\extension_loaded('bcmath')) {
|
||||
return new Calculator\BcMathCalculator();
|
||||
}
|
||||
|
||||
return new Calculator\NativeCalculator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the sign & digits of the operands.
|
||||
*
|
||||
* @param string $a The first operand.
|
||||
* @param string $b The second operand.
|
||||
*
|
||||
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
|
||||
*/
|
||||
final protected function init(string $a, string $b) : array
|
||||
{
|
||||
return [
|
||||
$aNeg = ($a[0] === '-'),
|
||||
$bNeg = ($b[0] === '-'),
|
||||
|
||||
$aNeg ? \substr($a, 1) : $a,
|
||||
$bNeg ? \substr($b, 1) : $b,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute value of a number.
|
||||
*
|
||||
* @param string $n The number.
|
||||
*
|
||||
* @return string The absolute value.
|
||||
*/
|
||||
final public function abs(string $n) : string
|
||||
{
|
||||
return ($n[0] === '-') ? \substr($n, 1) : $n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negates a number.
|
||||
*
|
||||
* @param string $n The number.
|
||||
*
|
||||
* @return string The negated value.
|
||||
*/
|
||||
final public function neg(string $n) : string
|
||||
{
|
||||
if ($n === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
if ($n[0] === '-') {
|
||||
return \substr($n, 1);
|
||||
}
|
||||
|
||||
return '-' . $n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two numbers.
|
||||
*
|
||||
* @param string $a The first number.
|
||||
* @param string $b The second number.
|
||||
*
|
||||
* @return int [-1, 0, 1] If the first number is less than, equal to, or greater than the second number.
|
||||
*/
|
||||
final public function cmp(string $a, string $b) : int
|
||||
{
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
if ($aNeg && ! $bNeg) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($bNeg && ! $aNeg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$aLen = \strlen($aDig);
|
||||
$bLen = \strlen($bDig);
|
||||
|
||||
if ($aLen < $bLen) {
|
||||
$result = -1;
|
||||
} elseif ($aLen > $bLen) {
|
||||
$result = 1;
|
||||
} else {
|
||||
$result = $aDig <=> $bDig;
|
||||
}
|
||||
|
||||
return $aNeg ? -$result : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds two numbers.
|
||||
*
|
||||
* @param string $a The augend.
|
||||
* @param string $b The addend.
|
||||
*
|
||||
* @return string The sum.
|
||||
*/
|
||||
abstract public function add(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Subtracts two numbers.
|
||||
*
|
||||
* @param string $a The minuend.
|
||||
* @param string $b The subtrahend.
|
||||
*
|
||||
* @return string The difference.
|
||||
*/
|
||||
abstract public function sub(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Multiplies two numbers.
|
||||
*
|
||||
* @param string $a The multiplicand.
|
||||
* @param string $b The multiplier.
|
||||
*
|
||||
* @return string The product.
|
||||
*/
|
||||
abstract public function mul(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Returns the quotient of the division of two numbers.
|
||||
*
|
||||
* @param string $a The dividend.
|
||||
* @param string $b The divisor, must not be zero.
|
||||
*
|
||||
* @return string The quotient.
|
||||
*/
|
||||
abstract public function divQ(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Returns the remainder of the division of two numbers.
|
||||
*
|
||||
* @param string $a The dividend.
|
||||
* @param string $b The divisor, must not be zero.
|
||||
*
|
||||
* @return string The remainder.
|
||||
*/
|
||||
abstract public function divR(string $a, string $b) : string;
|
||||
|
||||
/**
|
||||
* Returns the quotient and remainder of the division of two numbers.
|
||||
*
|
||||
* @param string $a The dividend.
|
||||
* @param string $b The divisor, must not be zero.
|
||||
*
|
||||
* @return string[] An array containing the quotient and remainder.
|
||||
*/
|
||||
abstract public function divQR(string $a, string $b) : array;
|
||||
|
||||
/**
|
||||
* Exponentiates a number.
|
||||
*
|
||||
* @param string $a The base number.
|
||||
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
|
||||
*
|
||||
* @return string The power.
|
||||
*/
|
||||
abstract public function pow(string $a, int $e) : string;
|
||||
|
||||
/**
|
||||
* @param string $a
|
||||
* @param string $b The modulus; must not be zero.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function mod(string $a, string $b) : string
|
||||
{
|
||||
return $this->divR($this->add($this->divR($a, $b), $b), $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the modular multiplicative inverse of $x modulo $m.
|
||||
*
|
||||
* If $x has no multiplicative inverse mod m, this method must return null.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
|
||||
*
|
||||
* @param string $x
|
||||
* @param string $m The modulus; must not be negative or zero.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function modInverse(string $x, string $m) : ?string
|
||||
{
|
||||
if ($m === '1') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
$modVal = $x;
|
||||
|
||||
if ($x[0] === '-' || ($this->cmp($this->abs($x), $m) >= 0)) {
|
||||
$modVal = $this->mod($x, $m);
|
||||
}
|
||||
|
||||
$x = '0';
|
||||
$y = '0';
|
||||
$g = $this->gcdExtended($modVal, $m, $x, $y);
|
||||
|
||||
if ($g !== '1') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->mod($this->add($this->mod($x, $m), $m), $m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises a number into power with modulo.
|
||||
*
|
||||
* @param string $base The base number; must be positive or zero.
|
||||
* @param string $exp The exponent; must be positive or zero.
|
||||
* @param string $mod The modulus; must be strictly positive.
|
||||
*
|
||||
* @return string The power.
|
||||
*/
|
||||
abstract public function modPow(string $base, string $exp, string $mod) : string;
|
||||
|
||||
/**
|
||||
* Returns the greatest common divisor of the two numbers.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for GCD calculations.
|
||||
*
|
||||
* @param string $a The first number.
|
||||
* @param string $b The second number.
|
||||
*
|
||||
* @return string The GCD, always positive, or zero if both arguments are zero.
|
||||
*/
|
||||
public function gcd(string $a, string $b) : string
|
||||
{
|
||||
if ($a === '0') {
|
||||
return $this->abs($b);
|
||||
}
|
||||
|
||||
if ($b === '0') {
|
||||
return $this->abs($a);
|
||||
}
|
||||
|
||||
return $this->gcd($b, $this->divR($a, $b));
|
||||
}
|
||||
|
||||
private function gcdExtended(string $a, string $b, string &$x, string &$y) : string
|
||||
{
|
||||
if ($a === '0') {
|
||||
$x = '0';
|
||||
$y = '1';
|
||||
|
||||
return $b;
|
||||
}
|
||||
|
||||
$x1 = '0';
|
||||
$y1 = '0';
|
||||
|
||||
$gcd = $this->gcdExtended($this->mod($b, $a), $a, $x1, $y1);
|
||||
|
||||
$x = $this->sub($y1, $this->mul($this->divQ($b, $a), $x1));
|
||||
$y = $x1;
|
||||
|
||||
return $gcd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the square root of the given number, rounded down.
|
||||
*
|
||||
* The result is the largest x such that x² ≤ n.
|
||||
* The input MUST NOT be negative.
|
||||
*
|
||||
* @param string $n The number.
|
||||
*
|
||||
* @return string The square root.
|
||||
*/
|
||||
abstract public function sqrt(string $n) : string;
|
||||
|
||||
/**
|
||||
* Converts a number from an arbitrary base.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for base conversion.
|
||||
*
|
||||
* @param string $number The number, positive or zero, non-empty, case-insensitively validated for the given base.
|
||||
* @param int $base The base of the number, validated from 2 to 36.
|
||||
*
|
||||
* @return string The converted number, following the Calculator conventions.
|
||||
*/
|
||||
public function fromBase(string $number, int $base) : string
|
||||
{
|
||||
return $this->fromArbitraryBase(\strtolower($number), self::ALPHABET, $base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a number to an arbitrary base.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for base conversion.
|
||||
*
|
||||
* @param string $number The number to convert, following the Calculator conventions.
|
||||
* @param int $base The base to convert to, validated from 2 to 36.
|
||||
*
|
||||
* @return string The converted number, lowercase.
|
||||
*/
|
||||
public function toBase(string $number, int $base) : string
|
||||
{
|
||||
$negative = ($number[0] === '-');
|
||||
|
||||
if ($negative) {
|
||||
$number = \substr($number, 1);
|
||||
}
|
||||
|
||||
$number = $this->toArbitraryBase($number, self::ALPHABET, $base);
|
||||
|
||||
if ($negative) {
|
||||
return '-' . $number;
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a non-negative number in an arbitrary base using a custom alphabet, to base 10.
|
||||
*
|
||||
* @param string $number The number to convert, validated as a non-empty string,
|
||||
* containing only chars in the given alphabet/base.
|
||||
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
|
||||
* @param int $base The base of the number, validated from 2 to alphabet length.
|
||||
*
|
||||
* @return string The number in base 10, following the Calculator conventions.
|
||||
*/
|
||||
final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string
|
||||
{
|
||||
// remove leading "zeros"
|
||||
$number = \ltrim($number, $alphabet[0]);
|
||||
|
||||
if ($number === '') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// optimize for "one"
|
||||
if ($number === $alphabet[1]) {
|
||||
return '1';
|
||||
}
|
||||
|
||||
$result = '0';
|
||||
$power = '1';
|
||||
|
||||
$base = (string) $base;
|
||||
|
||||
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
|
||||
$index = \strpos($alphabet, $number[$i]);
|
||||
|
||||
if ($index !== 0) {
|
||||
$result = $this->add($result, ($index === 1)
|
||||
? $power
|
||||
: $this->mul($power, (string) $index)
|
||||
);
|
||||
}
|
||||
|
||||
if ($i !== 0) {
|
||||
$power = $this->mul($power, $base);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a non-negative number to an arbitrary base using a custom alphabet.
|
||||
*
|
||||
* @param string $number The number to convert, positive or zero, following the Calculator conventions.
|
||||
* @param string $alphabet The alphabet that contains every digit, validated as 2 chars minimum.
|
||||
* @param int $base The base to convert to, validated from 2 to alphabet length.
|
||||
*
|
||||
* @return string The converted number in the given alphabet.
|
||||
*/
|
||||
final public function toArbitraryBase(string $number, string $alphabet, int $base) : string
|
||||
{
|
||||
if ($number === '0') {
|
||||
return $alphabet[0];
|
||||
}
|
||||
|
||||
$base = (string) $base;
|
||||
$result = '';
|
||||
|
||||
while ($number !== '0') {
|
||||
[$number, $remainder] = $this->divQR($number, $base);
|
||||
$remainder = (int) $remainder;
|
||||
|
||||
$result .= $alphabet[$remainder];
|
||||
}
|
||||
|
||||
return \strrev($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a rounded division.
|
||||
*
|
||||
* Rounding is performed when the remainder of the division is not zero.
|
||||
*
|
||||
* @param string $a The dividend.
|
||||
* @param string $b The divisor, must not be zero.
|
||||
* @param int $roundingMode The rounding mode.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \InvalidArgumentException If the rounding mode is invalid.
|
||||
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
|
||||
*/
|
||||
final public function divRound(string $a, string $b, int $roundingMode) : string
|
||||
{
|
||||
[$quotient, $remainder] = $this->divQR($a, $b);
|
||||
|
||||
$hasDiscardedFraction = ($remainder !== '0');
|
||||
$isPositiveOrZero = ($a[0] === '-') === ($b[0] === '-');
|
||||
|
||||
$discardedFractionSign = function() use ($remainder, $b) : int {
|
||||
$r = $this->abs($this->mul($remainder, '2'));
|
||||
$b = $this->abs($b);
|
||||
|
||||
return $this->cmp($r, $b);
|
||||
};
|
||||
|
||||
$increment = false;
|
||||
|
||||
switch ($roundingMode) {
|
||||
case RoundingMode::UNNECESSARY:
|
||||
if ($hasDiscardedFraction) {
|
||||
throw RoundingNecessaryException::roundingNecessary();
|
||||
}
|
||||
break;
|
||||
|
||||
case RoundingMode::UP:
|
||||
$increment = $hasDiscardedFraction;
|
||||
break;
|
||||
|
||||
case RoundingMode::DOWN:
|
||||
break;
|
||||
|
||||
case RoundingMode::CEILING:
|
||||
$increment = $hasDiscardedFraction && $isPositiveOrZero;
|
||||
break;
|
||||
|
||||
case RoundingMode::FLOOR:
|
||||
$increment = $hasDiscardedFraction && ! $isPositiveOrZero;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_UP:
|
||||
$increment = $discardedFractionSign() >= 0;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_DOWN:
|
||||
$increment = $discardedFractionSign() > 0;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_CEILING:
|
||||
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_FLOOR:
|
||||
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
||||
break;
|
||||
|
||||
case RoundingMode::HALF_EVEN:
|
||||
$lastDigit = (int) $quotient[-1];
|
||||
$lastDigitIsEven = ($lastDigit % 2 === 0);
|
||||
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid rounding mode.');
|
||||
}
|
||||
|
||||
if ($increment) {
|
||||
return $this->add($quotient, $isPositiveOrZero ? '1' : '-1');
|
||||
}
|
||||
|
||||
return $quotient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates bitwise AND of two numbers.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for bitwise operations.
|
||||
*
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function and(string $a, string $b) : string
|
||||
{
|
||||
return $this->bitwise('and', $a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates bitwise OR of two numbers.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for bitwise operations.
|
||||
*
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function or(string $a, string $b) : string
|
||||
{
|
||||
return $this->bitwise('or', $a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates bitwise XOR of two numbers.
|
||||
*
|
||||
* This method can be overridden by the concrete implementation if the underlying library
|
||||
* has built-in support for bitwise operations.
|
||||
*
|
||||
* @param string $a
|
||||
* @param string $b
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function xor(string $a, string $b) : string
|
||||
{
|
||||
return $this->bitwise('xor', $a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a bitwise operation on a decimal number.
|
||||
*
|
||||
* @param string $operator The operator to use, must be "and", "or" or "xor".
|
||||
* @param string $a The left operand.
|
||||
* @param string $b The right operand.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function bitwise(string $operator, string $a, string $b) : string
|
||||
{
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
$aBin = $this->toBinary($aDig);
|
||||
$bBin = $this->toBinary($bDig);
|
||||
|
||||
$aLen = \strlen($aBin);
|
||||
$bLen = \strlen($bBin);
|
||||
|
||||
if ($aLen > $bLen) {
|
||||
$bBin = \str_repeat("\x00", $aLen - $bLen) . $bBin;
|
||||
} elseif ($bLen > $aLen) {
|
||||
$aBin = \str_repeat("\x00", $bLen - $aLen) . $aBin;
|
||||
}
|
||||
|
||||
if ($aNeg) {
|
||||
$aBin = $this->twosComplement($aBin);
|
||||
}
|
||||
if ($bNeg) {
|
||||
$bBin = $this->twosComplement($bBin);
|
||||
}
|
||||
|
||||
switch ($operator) {
|
||||
case 'and':
|
||||
$value = $aBin & $bBin;
|
||||
$negative = ($aNeg and $bNeg);
|
||||
break;
|
||||
|
||||
case 'or':
|
||||
$value = $aBin | $bBin;
|
||||
$negative = ($aNeg or $bNeg);
|
||||
break;
|
||||
|
||||
case 'xor':
|
||||
$value = $aBin ^ $bBin;
|
||||
$negative = ($aNeg xor $bNeg);
|
||||
break;
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid bitwise operator.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ($negative) {
|
||||
$value = $this->twosComplement($value);
|
||||
}
|
||||
|
||||
$result = $this->toDecimal($value);
|
||||
|
||||
return $negative ? $this->neg($result) : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $number A positive, binary number.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function twosComplement(string $number) : string
|
||||
{
|
||||
$xor = \str_repeat("\xff", \strlen($number));
|
||||
|
||||
$number ^= $xor;
|
||||
|
||||
for ($i = \strlen($number) - 1; $i >= 0; $i--) {
|
||||
$byte = \ord($number[$i]);
|
||||
|
||||
if (++$byte !== 256) {
|
||||
$number[$i] = \chr($byte);
|
||||
break;
|
||||
}
|
||||
|
||||
$number[$i] = "\x00";
|
||||
|
||||
if ($i === 0) {
|
||||
$number = "\x01" . $number;
|
||||
}
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a decimal number to a binary string.
|
||||
*
|
||||
* @param string $number The number to convert, positive or zero, only digits.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function toBinary(string $number) : string
|
||||
{
|
||||
$result = '';
|
||||
|
||||
while ($number !== '0') {
|
||||
[$number, $remainder] = $this->divQR($number, '256');
|
||||
$result .= \chr((int) $remainder);
|
||||
}
|
||||
|
||||
return \strrev($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the positive decimal representation of a binary number.
|
||||
*
|
||||
* @param string $bytes The bytes representing the number.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function toDecimal(string $bytes) : string
|
||||
{
|
||||
$result = '0';
|
||||
$power = '1';
|
||||
|
||||
for ($i = \strlen($bytes) - 1; $i >= 0; $i--) {
|
||||
$index = \ord($bytes[$i]);
|
||||
|
||||
if ($index !== 0) {
|
||||
$result = $this->add($result, ($index === 1)
|
||||
? $power
|
||||
: $this->mul($power, (string) $index)
|
||||
);
|
||||
}
|
||||
|
||||
if ($i !== 0) {
|
||||
$power = $this->mul($power, '256');
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal\Calculator;
|
||||
|
||||
use Brick\Math\Internal\Calculator;
|
||||
|
||||
/**
|
||||
* Calculator implementation built around the bcmath library.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @psalm-immutable
|
||||
*/
|
||||
class BcMathCalculator extends Calculator
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add(string $a, string $b) : string
|
||||
{
|
||||
return \bcadd($a, $b, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return \bcsub($a, $b, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mul(string $a, string $b) : string
|
||||
{
|
||||
return \bcmul($a, $b, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @psalm-suppress InvalidNullableReturnType
|
||||
* @psalm-suppress NullableReturnStatement
|
||||
*/
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return \bcdiv($a, $b, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @psalm-suppress InvalidNullableReturnType
|
||||
* @psalm-suppress NullableReturnStatement
|
||||
*/
|
||||
public function divR(string $a, string $b) : string
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '7.2') >= 0) {
|
||||
return \bcmod($a, $b, 0);
|
||||
}
|
||||
|
||||
return \bcmod($a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function divQR(string $a, string $b) : array
|
||||
{
|
||||
$q = \bcdiv($a, $b, 0);
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.2') >= 0) {
|
||||
$r = \bcmod($a, $b, 0);
|
||||
} else {
|
||||
$r = \bcmod($a, $b);
|
||||
}
|
||||
|
||||
assert($q !== null);
|
||||
assert($r !== null);
|
||||
|
||||
return [$q, $r];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pow(string $a, int $e) : string
|
||||
{
|
||||
return \bcpow($a, (string) $e, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @psalm-suppress InvalidNullableReturnType
|
||||
* @psalm-suppress NullableReturnStatement
|
||||
*/
|
||||
public function modPow(string $base, string $exp, string $mod) : string
|
||||
{
|
||||
return \bcpowmod($base, $exp, $mod, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @psalm-suppress NullableReturnStatement
|
||||
* @psalm-suppress InvalidNullableReturnType
|
||||
*/
|
||||
public function sqrt(string $n) : string
|
||||
{
|
||||
return \bcsqrt($n, 0);
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal\Calculator;
|
||||
|
||||
use Brick\Math\Internal\Calculator;
|
||||
|
||||
/**
|
||||
* Calculator implementation built around the GMP library.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @psalm-immutable
|
||||
*/
|
||||
class GmpCalculator extends Calculator
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_add($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_sub($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mul(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_mul($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_div_q($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function divR(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_div_r($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function divQR(string $a, string $b) : array
|
||||
{
|
||||
[$q, $r] = \gmp_div_qr($a, $b);
|
||||
|
||||
return [
|
||||
\gmp_strval($q),
|
||||
\gmp_strval($r)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pow(string $a, int $e) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_pow($a, $e));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function modInverse(string $x, string $m) : ?string
|
||||
{
|
||||
$result = \gmp_invert($x, $m);
|
||||
|
||||
if ($result === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \gmp_strval($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function modPow(string $base, string $exp, string $mod) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_powm($base, $exp, $mod));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gcd(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_gcd($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fromBase(string $number, int $base) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_init($number, $base));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toBase(string $number, int $base) : string
|
||||
{
|
||||
return \gmp_strval($number, $base);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function and(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_and($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function or(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_or($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function xor(string $a, string $b) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_xor($a, $b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function sqrt(string $n) : string
|
||||
{
|
||||
return \gmp_strval(\gmp_sqrt($n));
|
||||
}
|
||||
}
|
@ -1,634 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math\Internal\Calculator;
|
||||
|
||||
use Brick\Math\Internal\Calculator;
|
||||
|
||||
/**
|
||||
* Calculator implementation using only native PHP code.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @psalm-immutable
|
||||
*/
|
||||
class NativeCalculator extends Calculator
|
||||
{
|
||||
/**
|
||||
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
|
||||
* For multiplication, this represents the max sum of the lengths of both operands.
|
||||
*
|
||||
* For addition, it is assumed that an extra digit can hold a carry (1) without overflowing.
|
||||
* Example: 32-bit: max number 1,999,999,999 (9 digits + carry)
|
||||
* 64-bit: max number 1,999,999,999,999,999,999 (18 digits + carry)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $maxDigits;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
switch (PHP_INT_SIZE) {
|
||||
case 4:
|
||||
$this->maxDigits = 9;
|
||||
break;
|
||||
|
||||
case 8:
|
||||
$this->maxDigits = 18;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add(string $a, string $b) : string
|
||||
{
|
||||
/**
|
||||
* @psalm-var numeric-string $a
|
||||
* @psalm-var numeric-string $b
|
||||
*/
|
||||
$result = $a + $b;
|
||||
|
||||
if (is_int($result)) {
|
||||
return (string) $result;
|
||||
}
|
||||
|
||||
if ($a === '0') {
|
||||
return $b;
|
||||
}
|
||||
|
||||
if ($b === '0') {
|
||||
return $a;
|
||||
}
|
||||
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
$result = $aNeg === $bNeg ? $this->doAdd($aDig, $bDig) : $this->doSub($aDig, $bDig);
|
||||
|
||||
if ($aNeg) {
|
||||
$result = $this->neg($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sub(string $a, string $b) : string
|
||||
{
|
||||
return $this->add($a, $this->neg($b));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mul(string $a, string $b) : string
|
||||
{
|
||||
/**
|
||||
* @psalm-var numeric-string $a
|
||||
* @psalm-var numeric-string $b
|
||||
*/
|
||||
$result = $a * $b;
|
||||
|
||||
if (is_int($result)) {
|
||||
return (string) $result;
|
||||
}
|
||||
|
||||
if ($a === '0' || $b === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
if ($a === '1') {
|
||||
return $b;
|
||||
}
|
||||
|
||||
if ($b === '1') {
|
||||
return $a;
|
||||
}
|
||||
|
||||
if ($a === '-1') {
|
||||
return $this->neg($b);
|
||||
}
|
||||
|
||||
if ($b === '-1') {
|
||||
return $this->neg($a);
|
||||
}
|
||||
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
$result = $this->doMul($aDig, $bDig);
|
||||
|
||||
if ($aNeg !== $bNeg) {
|
||||
$result = $this->neg($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function divQ(string $a, string $b) : string
|
||||
{
|
||||
return $this->divQR($a, $b)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function divR(string $a, string $b): string
|
||||
{
|
||||
return $this->divQR($a, $b)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function divQR(string $a, string $b) : array
|
||||
{
|
||||
if ($a === '0') {
|
||||
return ['0', '0'];
|
||||
}
|
||||
|
||||
if ($a === $b) {
|
||||
return ['1', '0'];
|
||||
}
|
||||
|
||||
if ($b === '1') {
|
||||
return [$a, '0'];
|
||||
}
|
||||
|
||||
if ($b === '-1') {
|
||||
return [$this->neg($a), '0'];
|
||||
}
|
||||
|
||||
/** @psalm-var numeric-string $a */
|
||||
$na = $a * 1; // cast to number
|
||||
|
||||
if (is_int($na)) {
|
||||
/** @psalm-var numeric-string $b */
|
||||
$nb = $b * 1;
|
||||
|
||||
if (is_int($nb)) {
|
||||
// the only division that may overflow is PHP_INT_MIN / -1,
|
||||
// which cannot happen here as we've already handled a divisor of -1 above.
|
||||
$r = $na % $nb;
|
||||
$q = ($na - $r) / $nb;
|
||||
|
||||
assert(is_int($q));
|
||||
|
||||
return [
|
||||
(string) $q,
|
||||
(string) $r
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
[$aNeg, $bNeg, $aDig, $bDig] = $this->init($a, $b);
|
||||
|
||||
[$q, $r] = $this->doDiv($aDig, $bDig);
|
||||
|
||||
if ($aNeg !== $bNeg) {
|
||||
$q = $this->neg($q);
|
||||
}
|
||||
|
||||
if ($aNeg) {
|
||||
$r = $this->neg($r);
|
||||
}
|
||||
|
||||
return [$q, $r];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pow(string $a, int $e) : string
|
||||
{
|
||||
if ($e === 0) {
|
||||
return '1';
|
||||
}
|
||||
|
||||
if ($e === 1) {
|
||||
return $a;
|
||||
}
|
||||
|
||||
$odd = $e % 2;
|
||||
$e -= $odd;
|
||||
|
||||
$aa = $this->mul($a, $a);
|
||||
|
||||
/** @psalm-suppress PossiblyInvalidArgument We're sure that $e / 2 is an int now */
|
||||
$result = $this->pow($aa, $e / 2);
|
||||
|
||||
if ($odd === 1) {
|
||||
$result = $this->mul($result, $a);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Algorithm from: https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function modPow(string $base, string $exp, string $mod) : string
|
||||
{
|
||||
// special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0)
|
||||
if ($base === '0' && $exp === '0' && $mod === '1') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
|
||||
if ($exp === '0' && $mod === '1') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
$x = $base;
|
||||
|
||||
$res = '1';
|
||||
|
||||
// numbers are positive, so we can use remainder instead of modulo
|
||||
$x = $this->divR($x, $mod);
|
||||
|
||||
while ($exp !== '0') {
|
||||
if (in_array($exp[-1], ['1', '3', '5', '7', '9'])) { // odd
|
||||
$res = $this->divR($this->mul($res, $x), $mod);
|
||||
}
|
||||
|
||||
$exp = $this->divQ($exp, '2');
|
||||
$x = $this->divR($this->mul($x, $x), $mod);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapted from https://cp-algorithms.com/num_methods/roots_newton.html
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function sqrt(string $n) : string
|
||||
{
|
||||
if ($n === '0') {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// initial approximation
|
||||
$x = \str_repeat('9', \intdiv(\strlen($n), 2) ?: 1);
|
||||
|
||||
$decreased = false;
|
||||
|
||||
for (;;) {
|
||||
$nx = $this->divQ($this->add($x, $this->divQ($n, $x)), '2');
|
||||
|
||||
if ($x === $nx || $this->cmp($nx, $x) > 0 && $decreased) {
|
||||
break;
|
||||
}
|
||||
|
||||
$decreased = $this->cmp($nx, $x) < 0;
|
||||
$x = $nx;
|
||||
}
|
||||
|
||||
return $x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the addition of two non-signed large integers.
|
||||
*
|
||||
* @param string $a The first operand.
|
||||
* @param string $b The second operand.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function doAdd(string $a, string $b) : string
|
||||
{
|
||||
[$a, $b, $length] = $this->pad($a, $b);
|
||||
|
||||
$carry = 0;
|
||||
$result = '';
|
||||
|
||||
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
|
||||
$blockLength = $this->maxDigits;
|
||||
|
||||
if ($i < 0) {
|
||||
$blockLength += $i;
|
||||
/** @psalm-suppress LoopInvalidation */
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
/** @psalm-var numeric-string $blockA */
|
||||
$blockA = \substr($a, $i, $blockLength);
|
||||
|
||||
/** @psalm-var numeric-string $blockB */
|
||||
$blockB = \substr($b, $i, $blockLength);
|
||||
|
||||
$sum = (string) ($blockA + $blockB + $carry);
|
||||
$sumLength = \strlen($sum);
|
||||
|
||||
if ($sumLength > $blockLength) {
|
||||
$sum = \substr($sum, 1);
|
||||
$carry = 1;
|
||||
} else {
|
||||
if ($sumLength < $blockLength) {
|
||||
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
|
||||
}
|
||||
$carry = 0;
|
||||
}
|
||||
|
||||
$result = $sum . $result;
|
||||
|
||||
if ($i === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($carry === 1) {
|
||||
$result = '1' . $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the subtraction of two non-signed large integers.
|
||||
*
|
||||
* @param string $a The first operand.
|
||||
* @param string $b The second operand.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function doSub(string $a, string $b) : string
|
||||
{
|
||||
if ($a === $b) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
// Ensure that we always subtract to a positive result: biggest minus smallest.
|
||||
$cmp = $this->doCmp($a, $b);
|
||||
|
||||
$invert = ($cmp === -1);
|
||||
|
||||
if ($invert) {
|
||||
$c = $a;
|
||||
$a = $b;
|
||||
$b = $c;
|
||||
}
|
||||
|
||||
[$a, $b, $length] = $this->pad($a, $b);
|
||||
|
||||
$carry = 0;
|
||||
$result = '';
|
||||
|
||||
$complement = 10 ** $this->maxDigits;
|
||||
|
||||
for ($i = $length - $this->maxDigits;; $i -= $this->maxDigits) {
|
||||
$blockLength = $this->maxDigits;
|
||||
|
||||
if ($i < 0) {
|
||||
$blockLength += $i;
|
||||
/** @psalm-suppress LoopInvalidation */
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
/** @psalm-var numeric-string $blockA */
|
||||
$blockA = \substr($a, $i, $blockLength);
|
||||
|
||||
/** @psalm-var numeric-string $blockB */
|
||||
$blockB = \substr($b, $i, $blockLength);
|
||||
|
||||
$sum = $blockA - $blockB - $carry;
|
||||
|
||||
if ($sum < 0) {
|
||||
$sum += $complement;
|
||||
$carry = 1;
|
||||
} else {
|
||||
$carry = 0;
|
||||
}
|
||||
|
||||
$sum = (string) $sum;
|
||||
$sumLength = \strlen($sum);
|
||||
|
||||
if ($sumLength < $blockLength) {
|
||||
$sum = \str_repeat('0', $blockLength - $sumLength) . $sum;
|
||||
}
|
||||
|
||||
$result = $sum . $result;
|
||||
|
||||
if ($i === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Carry cannot be 1 when the loop ends, as a > b
|
||||
assert($carry === 0);
|
||||
|
||||
$result = \ltrim($result, '0');
|
||||
|
||||
if ($invert) {
|
||||
$result = $this->neg($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the multiplication of two non-signed large integers.
|
||||
*
|
||||
* @param string $a The first operand.
|
||||
* @param string $b The second operand.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function doMul(string $a, string $b) : string
|
||||
{
|
||||
$x = \strlen($a);
|
||||
$y = \strlen($b);
|
||||
|
||||
$maxDigits = \intdiv($this->maxDigits, 2);
|
||||
$complement = 10 ** $maxDigits;
|
||||
|
||||
$result = '0';
|
||||
|
||||
for ($i = $x - $maxDigits;; $i -= $maxDigits) {
|
||||
$blockALength = $maxDigits;
|
||||
|
||||
if ($i < 0) {
|
||||
$blockALength += $i;
|
||||
/** @psalm-suppress LoopInvalidation */
|
||||
$i = 0;
|
||||
}
|
||||
|
||||
$blockA = (int) \substr($a, $i, $blockALength);
|
||||
|
||||
$line = '';
|
||||
$carry = 0;
|
||||
|
||||
for ($j = $y - $maxDigits;; $j -= $maxDigits) {
|
||||
$blockBLength = $maxDigits;
|
||||
|
||||
if ($j < 0) {
|
||||
$blockBLength += $j;
|
||||
/** @psalm-suppress LoopInvalidation */
|
||||
$j = 0;
|
||||
}
|
||||
|
||||
$blockB = (int) \substr($b, $j, $blockBLength);
|
||||
|
||||
$mul = $blockA * $blockB + $carry;
|
||||
$value = $mul % $complement;
|
||||
$carry = ($mul - $value) / $complement;
|
||||
|
||||
$value = (string) $value;
|
||||
$value = \str_pad($value, $maxDigits, '0', STR_PAD_LEFT);
|
||||
|
||||
$line = $value . $line;
|
||||
|
||||
if ($j === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($carry !== 0) {
|
||||
$line = $carry . $line;
|
||||
}
|
||||
|
||||
$line = \ltrim($line, '0');
|
||||
|
||||
if ($line !== '') {
|
||||
$line .= \str_repeat('0', $x - $blockALength - $i);
|
||||
$result = $this->add($result, $line);
|
||||
}
|
||||
|
||||
if ($i === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the division of two non-signed large integers.
|
||||
*
|
||||
* @param string $a The first operand.
|
||||
* @param string $b The second operand.
|
||||
*
|
||||
* @return string[] The quotient and remainder.
|
||||
*/
|
||||
private function doDiv(string $a, string $b) : array
|
||||
{
|
||||
$cmp = $this->doCmp($a, $b);
|
||||
|
||||
if ($cmp === -1) {
|
||||
return ['0', $a];
|
||||
}
|
||||
|
||||
$x = \strlen($a);
|
||||
$y = \strlen($b);
|
||||
|
||||
// we now know that a >= b && x >= y
|
||||
|
||||
$q = '0'; // quotient
|
||||
$r = $a; // remainder
|
||||
$z = $y; // focus length, always $y or $y+1
|
||||
|
||||
for (;;) {
|
||||
$focus = \substr($a, 0, $z);
|
||||
|
||||
$cmp = $this->doCmp($focus, $b);
|
||||
|
||||
if ($cmp === -1) {
|
||||
if ($z === $x) { // remainder < dividend
|
||||
break;
|
||||
}
|
||||
|
||||
$z++;
|
||||
}
|
||||
|
||||
$zeros = \str_repeat('0', $x - $z);
|
||||
|
||||
$q = $this->add($q, '1' . $zeros);
|
||||
$a = $this->sub($a, $b . $zeros);
|
||||
|
||||
$r = $a;
|
||||
|
||||
if ($r === '0') { // remainder == 0
|
||||
break;
|
||||
}
|
||||
|
||||
$x = \strlen($a);
|
||||
|
||||
if ($x < $y) { // remainder < dividend
|
||||
break;
|
||||
}
|
||||
|
||||
$z = $y;
|
||||
}
|
||||
|
||||
return [$q, $r];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two non-signed large numbers.
|
||||
*
|
||||
* @param string $a The first operand.
|
||||
* @param string $b The second operand.
|
||||
*
|
||||
* @return int [-1, 0, 1]
|
||||
*/
|
||||
private function doCmp(string $a, string $b) : int
|
||||
{
|
||||
$x = \strlen($a);
|
||||
$y = \strlen($b);
|
||||
|
||||
$cmp = $x <=> $y;
|
||||
|
||||
if ($cmp !== 0) {
|
||||
return $cmp;
|
||||
}
|
||||
|
||||
return \strcmp($a, $b) <=> 0; // enforce [-1, 0, 1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the left of one of the given numbers with zeros if necessary to make both numbers the same length.
|
||||
*
|
||||
* The numbers must only consist of digits, without leading minus sign.
|
||||
*
|
||||
* @param string $a The first operand.
|
||||
* @param string $b The second operand.
|
||||
*
|
||||
* @return array{string, string, int}
|
||||
*/
|
||||
private function pad(string $a, string $b) : array
|
||||
{
|
||||
$x = \strlen($a);
|
||||
$y = \strlen($b);
|
||||
|
||||
if ($x > $y) {
|
||||
$b = \str_repeat('0', $x - $y) . $b;
|
||||
|
||||
return [$a, $b, $x];
|
||||
}
|
||||
|
||||
if ($x < $y) {
|
||||
$a = \str_repeat('0', $y - $x) . $a;
|
||||
|
||||
return [$a, $b, $y];
|
||||
}
|
||||
|
||||
return [$a, $b, $x];
|
||||
}
|
||||
}
|
107
vendor/brick/math/src/RoundingMode.php
vendored
107
vendor/brick/math/src/RoundingMode.php
vendored
@ -1,107 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Brick\Math;
|
||||
|
||||
/**
|
||||
* Specifies a rounding behavior for numerical operations capable of discarding precision.
|
||||
*
|
||||
* Each rounding mode indicates how the least significant returned digit of a rounded result
|
||||
* is to be calculated. If fewer digits are returned than the digits needed to represent the
|
||||
* exact numerical result, the discarded digits will be referred to as the discarded fraction
|
||||
* regardless the digits' contribution to the value of the number. In other words, considered
|
||||
* as a numerical value, the discarded fraction could have an absolute value greater than one.
|
||||
*/
|
||||
final class RoundingMode
|
||||
{
|
||||
/**
|
||||
* Private constructor. This class is not instantiable.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the requested operation has an exact result, hence no rounding is necessary.
|
||||
*
|
||||
* If this rounding mode is specified on an operation that yields a result that
|
||||
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
|
||||
*/
|
||||
public const UNNECESSARY = 0;
|
||||
|
||||
/**
|
||||
* Rounds away from zero.
|
||||
*
|
||||
* Always increments the digit prior to a nonzero discarded fraction.
|
||||
* Note that this rounding mode never decreases the magnitude of the calculated value.
|
||||
*/
|
||||
public const UP = 1;
|
||||
|
||||
/**
|
||||
* Rounds towards zero.
|
||||
*
|
||||
* Never increments the digit prior to a discarded fraction (i.e., truncates).
|
||||
* Note that this rounding mode never increases the magnitude of the calculated value.
|
||||
*/
|
||||
public const DOWN = 2;
|
||||
|
||||
/**
|
||||
* Rounds towards positive infinity.
|
||||
*
|
||||
* If the result is positive, behaves as for UP; if negative, behaves as for DOWN.
|
||||
* Note that this rounding mode never decreases the calculated value.
|
||||
*/
|
||||
public const CEILING = 3;
|
||||
|
||||
/**
|
||||
* Rounds towards negative infinity.
|
||||
*
|
||||
* If the result is positive, behave as for DOWN; if negative, behave as for UP.
|
||||
* Note that this rounding mode never increases the calculated value.
|
||||
*/
|
||||
public const FLOOR = 4;
|
||||
|
||||
/**
|
||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
|
||||
*
|
||||
* Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN.
|
||||
* Note that this is the rounding mode commonly taught at school.
|
||||
*/
|
||||
public const HALF_UP = 5;
|
||||
|
||||
/**
|
||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
|
||||
*
|
||||
* Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN.
|
||||
*/
|
||||
public const HALF_DOWN = 6;
|
||||
|
||||
/**
|
||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
|
||||
*
|
||||
* If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN.
|
||||
*/
|
||||
public const HALF_CEILING = 7;
|
||||
|
||||
/**
|
||||
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
|
||||
*
|
||||
* If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP.
|
||||
*/
|
||||
public const HALF_FLOOR = 8;
|
||||
|
||||
/**
|
||||
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor.
|
||||
*
|
||||
* Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd;
|
||||
* behaves as for HALF_DOWN if it's even.
|
||||
*
|
||||
* Note that this is the rounding mode that statistically minimizes
|
||||
* cumulative error when applied repeatedly over a sequence of calculations.
|
||||
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA.
|
||||
*/
|
||||
public const HALF_EVEN = 9;
|
||||
}
|
2
vendor/clue/block-react/.github/FUNDING.yml
vendored
2
vendor/clue/block-react/.github/FUNDING.yml
vendored
@ -1,2 +0,0 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
152
vendor/clue/block-react/CHANGELOG.md
vendored
152
vendor/clue/block-react/CHANGELOG.md
vendored
@ -1,152 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 1.5.0 (2021-10-20)
|
||||
|
||||
* Feature: Simplify usage by supporting new [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
(#60 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
Clue\React\Block\await($promise, $loop);
|
||||
Clue\React\Block\awaitAny($promises, $loop);
|
||||
Clue\React\Block\awaitAll($promises, $loop);
|
||||
|
||||
// new (using default loop)
|
||||
Clue\React\Block\await($promise);
|
||||
Clue\React\Block\awaitAny($promises);
|
||||
Clue\React\Block\awaitAll($promises);
|
||||
```
|
||||
|
||||
* Feature: Added support for upcoming react/promise v3.
|
||||
(#61 by @davidcole1340 and @SimonFrings)
|
||||
|
||||
* Improve error reporting by appending previous message for `Throwable`s.
|
||||
(#57 by @clue)
|
||||
|
||||
* Deprecate `$timeout` argument for `await*()` functions.
|
||||
(#59 by @clue)
|
||||
|
||||
```php
|
||||
// deprecated
|
||||
Clue\React\Block\await($promise, $loop, $timeout);
|
||||
Clue\React\Block\awaitAny($promises, $loop, $timeout);
|
||||
Clue\React\Block\awaitAll($promises, $loop, $timeout);
|
||||
|
||||
// still supported
|
||||
Clue\React\Block\await($promise, $loop);
|
||||
Clue\React\Block\awaitAny($promises, $loop);
|
||||
Clue\React\Block\awaitAll($promises, $loop);
|
||||
```
|
||||
|
||||
* Improve API documentation.
|
||||
(#58 and #63 by @clue and #55 by @PaulRotmann)
|
||||
|
||||
* Improve test suite and use GitHub actions for continuous integration (CI).
|
||||
(#54 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2020-08-21)
|
||||
|
||||
* Improve API documentation, update README and add examples.
|
||||
(#45 by @clue and #51 by @SimonFrings)
|
||||
|
||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Prepare PHP 8 support, update to PHPUnit 9, run tests on PHP 7.4 and simplify test matrix.
|
||||
(#46, #47 and #50 by @SimonFrings)
|
||||
|
||||
## 1.3.1 (2019-04-09)
|
||||
|
||||
* Fix: Fix getting the type of unexpected rejection reason when not rejecting with an `Exception`.
|
||||
(#42 by @Furgas and @clue)
|
||||
|
||||
* Fix: Check if the function is declared before declaring it.
|
||||
(#39 by @Niko9911)
|
||||
|
||||
## 1.3.0 (2018-06-14)
|
||||
|
||||
* Feature: Improve memory consumption by cleaning up garbage references.
|
||||
(#35 by @clue)
|
||||
|
||||
* Fix minor documentation typos.
|
||||
(#28 by @seregazhuk)
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build,
|
||||
support PHPUnit 6 and update Travis config to also test against PHP 7.2.
|
||||
(#30 by @clue, #31 by @carusogabriel and #32 by @andreybolonin)
|
||||
|
||||
* Update project homepage.
|
||||
(#34 by @clue)
|
||||
|
||||
## 1.2.0 (2017-08-03)
|
||||
|
||||
* Feature / Fix: Forward compatibility with future EventLoop v1.0 and v0.5 and
|
||||
cap small timeout values for legacy EventLoop
|
||||
(#26 by @clue)
|
||||
|
||||
```php
|
||||
// now works across all versions
|
||||
Block\sleep(0.000001, $loop);
|
||||
```
|
||||
|
||||
* Feature / Fix: Throw `UnexpectedValueException` if Promise gets rejected with non-Exception
|
||||
(#27 by @clue)
|
||||
|
||||
```php
|
||||
// now throws an UnexceptedValueException
|
||||
Block\await(Promise\reject(false), $loop);
|
||||
```
|
||||
|
||||
* First class support for legacy PHP 5.3 through PHP 7.1 and HHVM
|
||||
(#24 and #25 by @clue)
|
||||
|
||||
* Improve testsuite by adding PHPUnit to require-dev and
|
||||
Fix HHVM build for now again and ignore future HHVM build errors
|
||||
(#23 and #24 by @clue)
|
||||
|
||||
## 1.1.0 (2016-03-09)
|
||||
|
||||
* Feature: Add optional timeout parameter to all await*() functions
|
||||
(#17 by @clue)
|
||||
|
||||
* Feature: Cancellation is now supported across all PHP versions
|
||||
(#16 by @clue)
|
||||
|
||||
## 1.0.0 (2015-11-13)
|
||||
|
||||
* First stable release, now following SemVer
|
||||
* Improved documentation
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.3.0 release.
|
||||
|
||||
## 0.3.0 (2015-07-09)
|
||||
|
||||
* BC break: Use functional API approach instead of pseudo-OOP.
|
||||
All existing methods are now exposed as simple functions.
|
||||
([#13](https://github.com/clue/php-block-react/pull/13))
|
||||
```php
|
||||
// old
|
||||
$blocker = new Block\Blocker($loop);
|
||||
$result = $blocker->await($promise);
|
||||
|
||||
// new
|
||||
$result = Block\await($promise, $loop);
|
||||
```
|
||||
|
||||
## 0.2.0 (2015-07-05)
|
||||
|
||||
* BC break: Rename methods in order to avoid confusion.
|
||||
* Rename `wait()` to `sleep()`.
|
||||
([#8](https://github.com/clue/php-block-react/pull/8))
|
||||
* Rename `awaitRace()` to `awaitAny()`.
|
||||
([#9](https://github.com/clue/php-block-react/pull/9))
|
||||
* Rename `awaitOne()` to `await()`.
|
||||
([#10](https://github.com/clue/php-block-react/pull/10))
|
||||
|
||||
## 0.1.1 (2015-04-05)
|
||||
|
||||
* `run()` the loop instead of making it `tick()`.
|
||||
This results in significant performance improvements (less resource utilization) by avoiding busy waiting
|
||||
([#1](https://github.com/clue/php-block-react/pull/1))
|
||||
|
||||
## 0.1.0 (2015-04-04)
|
||||
|
||||
* First tagged release
|
21
vendor/clue/block-react/LICENSE
vendored
21
vendor/clue/block-react/LICENSE
vendored
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Christian Lück
|
||||
|
||||
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.
|
335
vendor/clue/block-react/README.md
vendored
335
vendor/clue/block-react/README.md
vendored
@ -1,335 +0,0 @@
|
||||
# clue/reactphp-block
|
||||
|
||||
[](https://github.com/clue/reactphp-block/actions)
|
||||
[](https://packagist.org/packages/clue/block-react)
|
||||
|
||||
Lightweight library that eases integrating async components built for
|
||||
[ReactPHP](https://reactphp.org/) in a traditional, blocking environment.
|
||||
|
||||
[ReactPHP](https://reactphp.org/) provides you a great set of base components and
|
||||
a huge ecosystem of third party libraries in order to perform async operations.
|
||||
The event-driven paradigm and asynchronous processing of any number of streams
|
||||
in real time enables you to build a whole new set of application on top of it.
|
||||
This is great for building modern, scalable applications from scratch and will
|
||||
likely result in you relying on a whole new software architecture.
|
||||
|
||||
But let's face it: Your day-to-day business is unlikely to allow you to build
|
||||
everything from scratch and ditch your existing production environment.
|
||||
This is where this library comes into play:
|
||||
|
||||
*Let's block ReactPHP*
|
||||
More specifically, this library eases the pain of integrating async components
|
||||
into your traditional, synchronous (blocking) application stack.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Support us](#support-us)
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [sleep()](#sleep)
|
||||
* [await()](#await)
|
||||
* [awaitAny()](#awaitany)
|
||||
* [awaitAll()](#awaitall)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Support us
|
||||
|
||||
We invest a lot of time developing, maintaining and updating our awesome
|
||||
open-source projects. You can help us sustain this high-quality of our work by
|
||||
[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
|
||||
numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
|
||||
for details.
|
||||
|
||||
Let's take these projects to the next level together! 🚀
|
||||
|
||||
### Quickstart example
|
||||
|
||||
The following example code demonstrates how this library can be used along with
|
||||
an [async HTTP client](https://github.com/reactphp/http#client-usage) to process two
|
||||
non-blocking HTTP requests and block until the first (faster) one resolves.
|
||||
|
||||
```php
|
||||
function blockingExample()
|
||||
{
|
||||
// this example uses an HTTP client
|
||||
// this could be pretty much everything that binds to an event loop
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
// set up two parallel requests
|
||||
$request1 = $browser->get('http://www.google.com/');
|
||||
$request2 = $browser->get('http://www.google.co.uk/');
|
||||
|
||||
// keep the loop running (i.e. block) until the first response arrives
|
||||
$fasterResponse = Clue\React\Block\awaitAny(array($request1, $request2));
|
||||
|
||||
return $fasterResponse->getBody();
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
This lightweight library consists only of a few simple functions.
|
||||
All functions reside under the `Clue\React\Block` namespace.
|
||||
|
||||
The below examples refer to all functions with their fully-qualified names like this:
|
||||
|
||||
```php
|
||||
Clue\React\Block\await(…);
|
||||
```
|
||||
|
||||
As of PHP 5.6+ you can also import each required function into your code like this:
|
||||
|
||||
```php
|
||||
use function Clue\React\Block\await;
|
||||
|
||||
await(…);
|
||||
```
|
||||
|
||||
Alternatively, you can also use an import statement similar to this:
|
||||
|
||||
```php
|
||||
use Clue\React\Block;
|
||||
|
||||
Block\await(…);
|
||||
```
|
||||
|
||||
### sleep()
|
||||
|
||||
The `sleep(float $seconds, ?LoopInterface $loop = null): void` function can be used to
|
||||
wait/sleep for `$time` seconds.
|
||||
|
||||
```php
|
||||
Clue\React\Block\sleep(1.5, $loop);
|
||||
```
|
||||
|
||||
This function will only return after the given `$time` has elapsed. In the
|
||||
meantime, the event loop will run any other events attached to the same loop
|
||||
until the timer fires. If there are no other events attached to this loop,
|
||||
it will behave similar to the built-in [`sleep()`](https://www.php.net/manual/en/function.sleep.php).
|
||||
|
||||
Internally, the `$time` argument will be used as a timer for the loop so that
|
||||
it keeps running until this timer triggers. This implies that if you pass a
|
||||
really small (or negative) value, it will still start a timer and will thus
|
||||
trigger at the earliest possible time in the future.
|
||||
|
||||
This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
pass the event loop instance to use. 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.
|
||||
|
||||
Note that this function will assume control over the event loop. Internally, it
|
||||
will actually `run()` the loop until the timer fires and then calls `stop()` to
|
||||
terminate execution of the loop. This means this function is more suited for
|
||||
short-lived program executions when using async APIs is not feasible. For
|
||||
long-running applications, using event-driven APIs by leveraging timers
|
||||
is usually preferable.
|
||||
|
||||
### await()
|
||||
|
||||
The `await(PromiseInterface $promise, ?LoopInterface $loop = null, ?float $timeout = null): mixed` function can be used to
|
||||
block waiting for the given `$promise` to be fulfilled.
|
||||
|
||||
```php
|
||||
$result = Clue\React\Block\await($promise);
|
||||
```
|
||||
|
||||
This function will only return after the given `$promise` has settled, i.e.
|
||||
either fulfilled or rejected. In the meantime, the event loop will run any
|
||||
events attached to the same loop until the promise settles.
|
||||
|
||||
Once the promise is fulfilled, this function will return whatever the promise
|
||||
resolved to.
|
||||
|
||||
Once the promise is rejected, this will throw whatever the promise rejected
|
||||
with. If the promise did not reject with an `Exception`, then this function
|
||||
will throw an `UnexpectedValueException` instead.
|
||||
|
||||
```php
|
||||
try {
|
||||
$result = Clue\React\Block\await($promise);
|
||||
// promise successfully fulfilled with $result
|
||||
echo 'Result: ' . $result;
|
||||
} catch (Exception $exception) {
|
||||
// promise rejected with $exception
|
||||
echo 'ERROR: ' . $exception->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
See also the [examples](examples/).
|
||||
|
||||
This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
pass the event loop instance to use. 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.
|
||||
|
||||
If no `$timeout` argument is given and the promise stays pending, then this
|
||||
will potentially wait/block forever until the promise is settled. To avoid
|
||||
this, API authors creating promises are expected to provide means to
|
||||
configure a timeout for the promise instead. For more details, see also the
|
||||
[`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
|
||||
If the deprecated `$timeout` argument is given and the promise is still pending once the
|
||||
timeout triggers, this will `cancel()` the promise and throw a `TimeoutException`.
|
||||
This implies that if you pass a really small (or negative) value, it will still
|
||||
start a timer and will thus trigger at the earliest possible time in the future.
|
||||
|
||||
Note that this function will assume control over the event loop. Internally, it
|
||||
will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
terminate execution of the loop. This means this function is more suited for
|
||||
short-lived promise executions when using promise-based APIs is not feasible.
|
||||
For long-running applications, using promise-based APIs by leveraging chained
|
||||
`then()` calls is usually preferable.
|
||||
|
||||
### awaitAny()
|
||||
|
||||
The `awaitAny(PromiseInterface[] $promises, ?LoopInterface $loop = null, ?float $timeout = null): mixed` function can be used to
|
||||
wait for ANY of the given promises to be fulfilled.
|
||||
|
||||
```php
|
||||
$promises = array(
|
||||
$promise1,
|
||||
$promise2
|
||||
);
|
||||
|
||||
$firstResult = Clue\React\Block\awaitAny($promises);
|
||||
|
||||
echo 'First result: ' . $firstResult;
|
||||
```
|
||||
|
||||
See also the [examples](examples/).
|
||||
|
||||
This function will only return after ANY of the given `$promises` has been
|
||||
fulfilled or will throw when ALL of them have been rejected. In the meantime,
|
||||
the event loop will run any events attached to the same loop.
|
||||
|
||||
Once ANY promise is fulfilled, this function will return whatever this
|
||||
promise resolved to and will try to `cancel()` all remaining promises.
|
||||
|
||||
Once ALL promises reject, this function will fail and throw an `UnderflowException`.
|
||||
Likewise, this will throw if an empty array of `$promises` is passed.
|
||||
|
||||
This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
pass the event loop instance to use. 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.
|
||||
|
||||
If no `$timeout` argument is given and ALL promises stay pending, then this
|
||||
will potentially wait/block forever until the promise is fulfilled. To avoid
|
||||
this, API authors creating promises are expected to provide means to
|
||||
configure a timeout for the promise instead. For more details, see also the
|
||||
[`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
|
||||
If the deprecated `$timeout` argument is given and ANY promises are still pending once
|
||||
the timeout triggers, this will `cancel()` all pending promises and throw a
|
||||
`TimeoutException`. This implies that if you pass a really small (or negative)
|
||||
value, it will still start a timer and will thus trigger at the earliest
|
||||
possible time in the future.
|
||||
|
||||
Note that this function will assume control over the event loop. Internally, it
|
||||
will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
terminate execution of the loop. This means this function is more suited for
|
||||
short-lived promise executions when using promise-based APIs is not feasible.
|
||||
For long-running applications, using promise-based APIs by leveraging chained
|
||||
`then()` calls is usually preferable.
|
||||
|
||||
### awaitAll()
|
||||
|
||||
The `awaitAll(PromiseInterface[] $promises, ?LoopInterface $loop = null, ?float $timeout = null): mixed[]` function can be used to
|
||||
wait for ALL of the given promises to be fulfilled.
|
||||
|
||||
```php
|
||||
$promises = array(
|
||||
$promise1,
|
||||
$promise2
|
||||
);
|
||||
|
||||
$allResults = Clue\React\Block\awaitAll($promises);
|
||||
|
||||
echo 'First promise resolved with: ' . $allResults[0];
|
||||
```
|
||||
|
||||
See also the [examples](examples/).
|
||||
|
||||
This function will only return after ALL of the given `$promises` have been
|
||||
fulfilled or will throw when ANY of them have been rejected. In the meantime,
|
||||
the event loop will run any events attached to the same loop.
|
||||
|
||||
Once ALL promises are fulfilled, this will return an array with whatever
|
||||
each promise resolves to. Array keys will be left intact, i.e. they can
|
||||
be used to correlate the return array to the promises passed.
|
||||
Likewise, this will return an empty array if an empty array of `$promises` is passed.
|
||||
|
||||
Once ANY promise rejects, this will try to `cancel()` all remaining promises
|
||||
and throw an `Exception`. If the promise did not reject with an `Exception`,
|
||||
then this function will throw an `UnexpectedValueException` instead.
|
||||
|
||||
This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
pass the event loop instance to use. 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.
|
||||
|
||||
If no `$timeout` argument is given and ANY promises stay pending, then this
|
||||
will potentially wait/block forever until the promise is fulfilled. To avoid
|
||||
this, API authors creating promises are expected to provide means to
|
||||
configure a timeout for the promise instead. For more details, see also the
|
||||
[`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
|
||||
If the deprecated `$timeout` argument is given and ANY promises are still pending once
|
||||
the timeout triggers, this will `cancel()` all pending promises and throw a
|
||||
`TimeoutException`. This implies that if you pass a really small (or negative)
|
||||
value, it will still start a timer and will thus trigger at the earliest
|
||||
possible time in the future.
|
||||
|
||||
Note that this function will assume control over the event loop. Internally, it
|
||||
will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
terminate execution of the loop. This means this function is more suited for
|
||||
short-lived promise executions when using promise-based APIs is not feasible.
|
||||
For long-running applications, using promise-based APIs by leveraging chained
|
||||
`then()` calls is usually preferable.
|
||||
|
||||
## 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 clue/block-react:^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.
|
||||
|
||||
## 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
|
||||
|
||||
This project is released under the permissive [MIT license](LICENSE).
|
||||
|
||||
> Did you know that I offer custom development services and issuing invoices for
|
||||
sponsorships of releases and for contributions? Contact me (@clue) for details.
|
29
vendor/clue/block-react/composer.json
vendored
29
vendor/clue/block-react/composer.json
vendored
@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "clue/block-react",
|
||||
"description": "Lightweight library that eases integrating async components built for ReactPHP in a traditional, blocking environment.",
|
||||
"keywords": ["blocking", "await", "sleep", "Event Loop", "synchronous", "Promise", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-block",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"files": [ "src/functions_include.php" ]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Block\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.0 || ^2.7 || ^1.2.1",
|
||||
"react/promise-timer": "^1.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
|
||||
"react/http": "^1.4"
|
||||
}
|
||||
}
|
357
vendor/clue/block-react/src/functions.php
vendored
357
vendor/clue/block-react/src/functions.php
vendored
@ -1,357 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Block;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Timer;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
use Exception;
|
||||
use UnderflowException;
|
||||
|
||||
/**
|
||||
* Wait/sleep for `$time` seconds.
|
||||
*
|
||||
* ```php
|
||||
* Clue\React\Block\sleep(1.5, $loop);
|
||||
* ```
|
||||
*
|
||||
* This function will only return after the given `$time` has elapsed. In the
|
||||
* meantime, the event loop will run any other events attached to the same loop
|
||||
* until the timer fires. If there are no other events attached to this loop,
|
||||
* it will behave similar to the built-in [`sleep()`](https://www.php.net/manual/en/function.sleep.php).
|
||||
*
|
||||
* Internally, the `$time` argument will be used as a timer for the loop so that
|
||||
* it keeps running until this timer triggers. This implies that if you pass a
|
||||
* really small (or negative) value, it will still start a timer and will thus
|
||||
* trigger at the earliest possible time in the future.
|
||||
*
|
||||
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use. 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.
|
||||
*
|
||||
* Note that this function will assume control over the event loop. Internally, it
|
||||
* will actually `run()` the loop until the timer fires and then calls `stop()` to
|
||||
* terminate execution of the loop. This means this function is more suited for
|
||||
* short-lived program executions when using async APIs is not feasible. For
|
||||
* long-running applications, using event-driven APIs by leveraging timers
|
||||
* is usually preferable.
|
||||
*
|
||||
* @param float $time
|
||||
* @param ?LoopInterface $loop
|
||||
* @return void
|
||||
*/
|
||||
function sleep($time, LoopInterface $loop = null)
|
||||
{
|
||||
await(Timer\resolve($time, $loop), $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block waiting for the given `$promise` to be fulfilled.
|
||||
*
|
||||
* ```php
|
||||
* $result = Clue\React\Block\await($promise, $loop);
|
||||
* ```
|
||||
*
|
||||
* This function will only return after the given `$promise` has settled, i.e.
|
||||
* either fulfilled or rejected. In the meantime, the event loop will run any
|
||||
* events attached to the same loop until the promise settles.
|
||||
*
|
||||
* Once the promise is fulfilled, this function will return whatever the promise
|
||||
* resolved to.
|
||||
*
|
||||
* Once the promise is rejected, this will throw whatever the promise rejected
|
||||
* with. If the promise did not reject with an `Exception`, then this function
|
||||
* will throw an `UnexpectedValueException` instead.
|
||||
*
|
||||
* ```php
|
||||
* try {
|
||||
* $result = Clue\React\Block\await($promise, $loop);
|
||||
* // promise successfully fulfilled with $result
|
||||
* echo 'Result: ' . $result;
|
||||
* } catch (Exception $exception) {
|
||||
* // promise rejected with $exception
|
||||
* echo 'ERROR: ' . $exception->getMessage();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* See also the [examples](../examples/).
|
||||
*
|
||||
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use. 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.
|
||||
*
|
||||
* If no `$timeout` argument is given and the promise stays pending, then this
|
||||
* will potentially wait/block forever until the promise is settled. To avoid
|
||||
* this, API authors creating promises are expected to provide means to
|
||||
* configure a timeout for the promise instead. For more details, see also the
|
||||
* [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
*
|
||||
* If the deprecated `$timeout` argument is given and the promise is still pending once the
|
||||
* timeout triggers, this will `cancel()` the promise and throw a `TimeoutException`.
|
||||
* This implies that if you pass a really small (or negative) value, it will still
|
||||
* start a timer and will thus trigger at the earliest possible time in the future.
|
||||
*
|
||||
* Note that this function will assume control over the event loop. Internally, it
|
||||
* will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
* terminate execution of the loop. This means this function is more suited for
|
||||
* short-lived promise executions when using promise-based APIs is not feasible.
|
||||
* For long-running applications, using promise-based APIs by leveraging chained
|
||||
* `then()` calls is usually preferable.
|
||||
*
|
||||
* @param PromiseInterface $promise
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?float $timeout [deprecated] (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return mixed returns whatever the promise resolves to
|
||||
* @throws Exception when the promise is rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function await(PromiseInterface $promise, LoopInterface $loop = null, $timeout = null)
|
||||
{
|
||||
$wait = true;
|
||||
$resolved = null;
|
||||
$exception = null;
|
||||
$rejected = false;
|
||||
$loop = $loop ?: Loop::get();
|
||||
|
||||
if ($timeout !== null) {
|
||||
$promise = Timer\timeout($promise, $timeout, $loop);
|
||||
}
|
||||
|
||||
$promise->then(
|
||||
function ($c) use (&$resolved, &$wait, $loop) {
|
||||
$resolved = $c;
|
||||
$wait = false;
|
||||
$loop->stop();
|
||||
},
|
||||
function ($error) use (&$exception, &$rejected, &$wait, $loop) {
|
||||
$exception = $error;
|
||||
$rejected = true;
|
||||
$wait = false;
|
||||
$loop->stop();
|
||||
}
|
||||
);
|
||||
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$promise = null;
|
||||
|
||||
while ($wait) {
|
||||
$loop->run();
|
||||
}
|
||||
|
||||
if ($rejected) {
|
||||
if (!$exception instanceof \Exception && !$exception instanceof \Throwable) {
|
||||
$exception = new \UnexpectedValueException(
|
||||
'Promise rejected with unexpected value of type ' . (is_object($exception) ? get_class($exception) : gettype($exception))
|
||||
);
|
||||
} elseif (!$exception instanceof \Exception) {
|
||||
$exception = new \UnexpectedValueException(
|
||||
'Promise rejected with unexpected ' . get_class($exception) . ': ' . $exception->getMessage(),
|
||||
$exception->getCode(),
|
||||
$exception
|
||||
);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for ANY of the given promises to be fulfilled.
|
||||
*
|
||||
* ```php
|
||||
* $promises = array(
|
||||
* $promise1,
|
||||
* $promise2
|
||||
* );
|
||||
*
|
||||
* $firstResult = Clue\React\Block\awaitAny($promises, $loop);
|
||||
*
|
||||
* echo 'First result: ' . $firstResult;
|
||||
* ```
|
||||
*
|
||||
* See also the [examples](../examples/).
|
||||
*
|
||||
* This function will only return after ANY of the given `$promises` has been
|
||||
* fulfilled or will throw when ALL of them have been rejected. In the meantime,
|
||||
* the event loop will run any events attached to the same loop.
|
||||
*
|
||||
* Once ANY promise is fulfilled, this function will return whatever this
|
||||
* promise resolved to and will try to `cancel()` all remaining promises.
|
||||
*
|
||||
* Once ALL promises reject, this function will fail and throw an `UnderflowException`.
|
||||
* Likewise, this will throw if an empty array of `$promises` is passed.
|
||||
*
|
||||
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use. 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.
|
||||
*
|
||||
* If no `$timeout` argument is given and ALL promises stay pending, then this
|
||||
* will potentially wait/block forever until the promise is fulfilled. To avoid
|
||||
* this, API authors creating promises are expected to provide means to
|
||||
* configure a timeout for the promise instead. For more details, see also the
|
||||
* [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
*
|
||||
* If the deprecated `$timeout` argument is given and ANY promises are still pending once
|
||||
* the timeout triggers, this will `cancel()` all pending promises and throw a
|
||||
* `TimeoutException`. This implies that if you pass a really small (or negative)
|
||||
* value, it will still start a timer and will thus trigger at the earliest
|
||||
* possible time in the future.
|
||||
*
|
||||
* Note that this function will assume control over the event loop. Internally, it
|
||||
* will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
* terminate execution of the loop. This means this function is more suited for
|
||||
* short-lived promise executions when using promise-based APIs is not feasible.
|
||||
* For long-running applications, using promise-based APIs by leveraging chained
|
||||
* `then()` calls is usually preferable.
|
||||
*
|
||||
* @param PromiseInterface[] $promises
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?float $timeout [deprecated] (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return mixed returns whatever the first promise resolves to
|
||||
* @throws Exception if ALL promises are rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function awaitAny(array $promises, LoopInterface $loop = null, $timeout = null)
|
||||
{
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$all = $promises;
|
||||
$promises = null;
|
||||
|
||||
try {
|
||||
// Promise\any() does not cope with an empty input array, so reject this here
|
||||
if (!$all) {
|
||||
throw new UnderflowException('Empty input array');
|
||||
}
|
||||
|
||||
$ret = await(Promise\any($all)->then(null, function () {
|
||||
// rejects with an array of rejection reasons => reject with Exception instead
|
||||
throw new Exception('All promises rejected');
|
||||
}), $loop, $timeout);
|
||||
} catch (TimeoutException $e) {
|
||||
// the timeout fired
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
// if the above throws, then ALL promises are already rejected
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw new UnderflowException('No promise could resolve', 0, $e);
|
||||
}
|
||||
|
||||
// if we reach this, then ANY of the given promises resolved
|
||||
// => try to cancel all promises (settled ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for ALL of the given promises to be fulfilled.
|
||||
*
|
||||
* ```php
|
||||
* $promises = array(
|
||||
* $promise1,
|
||||
* $promise2
|
||||
* );
|
||||
*
|
||||
* $allResults = Clue\React\Block\awaitAll($promises, $loop);
|
||||
*
|
||||
* echo 'First promise resolved with: ' . $allResults[0];
|
||||
* ```
|
||||
*
|
||||
* See also the [examples](../examples/).
|
||||
*
|
||||
* This function will only return after ALL of the given `$promises` have been
|
||||
* fulfilled or will throw when ANY of them have been rejected. In the meantime,
|
||||
* the event loop will run any events attached to the same loop.
|
||||
*
|
||||
* Once ALL promises are fulfilled, this will return an array with whatever
|
||||
* each promise resolves to. Array keys will be left intact, i.e. they can
|
||||
* be used to correlate the return array to the promises passed.
|
||||
*
|
||||
* Once ANY promise rejects, this will try to `cancel()` all remaining promises
|
||||
* and throw an `Exception`. If the promise did not reject with an `Exception`,
|
||||
* then this function will throw an `UnexpectedValueException` instead.
|
||||
*
|
||||
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
* pass the event loop instance to use. 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.
|
||||
*
|
||||
* If no `$timeout` argument is given and ANY promises stay pending, then this
|
||||
* will potentially wait/block forever until the promise is fulfilled. To avoid
|
||||
* this, API authors creating promises are expected to provide means to
|
||||
* configure a timeout for the promise instead. For more details, see also the
|
||||
* [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
|
||||
*
|
||||
* If the deprecated `$timeout` argument is given and ANY promises are still pending once
|
||||
* the timeout triggers, this will `cancel()` all pending promises and throw a
|
||||
* `TimeoutException`. This implies that if you pass a really small (or negative)
|
||||
* value, it will still start a timer and will thus trigger at the earliest
|
||||
* possible time in the future.
|
||||
*
|
||||
* Note that this function will assume control over the event loop. Internally, it
|
||||
* will actually `run()` the loop until the promise settles and then calls `stop()` to
|
||||
* terminate execution of the loop. This means this function is more suited for
|
||||
* short-lived promise executions when using promise-based APIs is not feasible.
|
||||
* For long-running applications, using promise-based APIs by leveraging chained
|
||||
* `then()` calls is usually preferable.
|
||||
*
|
||||
* @param PromiseInterface[] $promises
|
||||
* @param ?LoopInterface $loop
|
||||
* @param ?float $timeout [deprecated] (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return array returns an array with whatever each promise resolves to
|
||||
* @throws Exception when ANY promise is rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function awaitAll(array $promises, LoopInterface $loop = null, $timeout = null)
|
||||
{
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$all = $promises;
|
||||
$promises = null;
|
||||
|
||||
try {
|
||||
return await(Promise\all($all), $loop, $timeout);
|
||||
} catch (Exception $e) {
|
||||
// ANY of the given promises rejected or the timeout fired
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* internal helper function used to iterate over an array of Promise instances and cancel() each
|
||||
*
|
||||
* @internal
|
||||
* @param array $promises
|
||||
* @return void
|
||||
*/
|
||||
function _cancelAllPromises(array $promises)
|
||||
{
|
||||
foreach ($promises as $promise) {
|
||||
if ($promise instanceof PromiseInterface && ($promise instanceof CancellablePromiseInterface || !\interface_exists('React\Promise\CancellablePromiseInterface'))) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Block;
|
||||
|
||||
if (!function_exists('Clue\\React\\Block\\sleep')) {
|
||||
require __DIR__ . '/functions.php';
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
191
vendor/clue/connection-manager-extra/CHANGELOG.md
vendored
191
vendor/clue/connection-manager-extra/CHANGELOG.md
vendored
@ -1,191 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 1.3.0 (2022-08-30)
|
||||
|
||||
* Feature: Simplify usage by supporting new default loop.
|
||||
(#33 by @SimonFrings)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$connector = new ConnectionManagerTimeout($connector, 3.0, $loop);
|
||||
$delayed = new ConnectionManagerDelayed($connector, 0.5, $loop);
|
||||
|
||||
// new (using default loop)
|
||||
$connector = new ConnectionManagerTimeout($connector, 3.0);
|
||||
$delayed = new ConnectionManagerDelayed($connector, 0.5);
|
||||
```
|
||||
|
||||
* Feature: Full support for PHP 8.1 and PHP 8.2.
|
||||
(#36 and #37 by @SimonFrings)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Promise v3.
|
||||
(#34 by @clue)
|
||||
|
||||
* Improve test suite and add badge to show number of project installations.
|
||||
(#35 by @SimonFrings and #31 by @PaulRotmann)
|
||||
|
||||
## 1.2.0 (2020-12-12)
|
||||
|
||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
|
||||
(#24, #25 and #26 by @clue and #27, #28, #29 and #30 by @SimonFrings)
|
||||
|
||||
## 1.1.0 (2017-08-03)
|
||||
|
||||
* Feature: Support custom rejection reason for ConnectionManagerReject
|
||||
(#23 by @clue)
|
||||
|
||||
```php
|
||||
$connector = new ConnectionManagerReject(function ($uri) {
|
||||
throw new RuntimeException($uri . ' blocked');
|
||||
});
|
||||
```
|
||||
|
||||
## 1.0.1 (2017-06-23)
|
||||
|
||||
* Fix: Ignore URI scheme when matching selective connectors
|
||||
(#21 by @clue)
|
||||
|
||||
* Fix HHVM build for now again and ignore future HHVM build errors
|
||||
(#22 by @clue)
|
||||
|
||||
## 1.0.0 (2017-05-09)
|
||||
|
||||
* First stable release, now following SemVer
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.7 releases.
|
||||
|
||||
## 0.7.1 (2017-05-09)
|
||||
|
||||
* Fix: Reject promise for invalid URI passed to ConnectionManagerSelective
|
||||
(#19 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8 and
|
||||
upcoming EventLoop v1.0 and v0.5
|
||||
(#18 and #20 by @clue)
|
||||
|
||||
## 0.7.0 (2017-04-10)
|
||||
|
||||
* Feature / BC break: Replace deprecated SocketClient with new Socket component
|
||||
(#17 by @clue)
|
||||
|
||||
This implies that all connectors from this package now implement the
|
||||
`React\Socket\ConnectorInterface` instead of the legacy
|
||||
`React\SocketClient\ConnectorInterface`.
|
||||
|
||||
## 0.6.0 (2017-04-07)
|
||||
|
||||
* Feature / BC break: Update SocketClient to v0.7 or v0.6
|
||||
(#16 by @clue)
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev
|
||||
(#15 by @clue)
|
||||
|
||||
## 0.5.0 (2016-06-01)
|
||||
|
||||
* BC break: Change $retries to $tries
|
||||
(#14 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
// 1 try plus 2 retries => 3 total tries
|
||||
$c = new ConnectionManagerRepeat($c, 2);
|
||||
|
||||
// new
|
||||
// 3 total tries (1 try plus 2 retries)
|
||||
$c = new ConnectionManagerRepeat($c, 3);
|
||||
```
|
||||
|
||||
* BC break: Timed connectors now use $loop as last argument
|
||||
(#13 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
// $c = new ConnectionManagerDelay($c, $loop, 1.0);
|
||||
$c = new ConnectionManagerTimeout($c, $loop, 1.0);
|
||||
|
||||
// new
|
||||
$c = new ConnectionManagerTimeout($c, 1.0, $loop);
|
||||
```
|
||||
|
||||
* BC break: Move all connector lists to the constructor
|
||||
(#12 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
// $c = new ConnectionManagerConcurrent();
|
||||
// $c = new ConnectionManagerRandom();
|
||||
$c = new ConnectionManagerConsecutive();
|
||||
$c->addConnectionManager($c1);
|
||||
$c->addConnectionManager($c2);
|
||||
|
||||
// new
|
||||
$c = new ConnectionManagerConsecutive(array(
|
||||
$c1,
|
||||
$c2
|
||||
));
|
||||
```
|
||||
|
||||
* BC break: ConnectionManagerSelective now accepts connector list in constructor
|
||||
(#11 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$c = new ConnectionManagerSelective();
|
||||
$c->addConnectionManagerFor($c1, 'host1');
|
||||
$c->addConnectionManagerFor($c2, 'host2');
|
||||
|
||||
// new
|
||||
$c = new ConnectionManagerSelective(array(
|
||||
'host1' => $c1,
|
||||
'host2' => $c2
|
||||
));
|
||||
```
|
||||
|
||||
## 0.4.0 (2016-05-30)
|
||||
|
||||
* Feature: Add `ConnectionManagerConcurrent`
|
||||
(#10 by @clue)
|
||||
|
||||
* Feature: Support Promise cancellation for all connectors
|
||||
(#9 by @clue)
|
||||
|
||||
## 0.3.3 (2016-05-29)
|
||||
|
||||
* Fix repetitions for `ConnectionManagerRepeat`
|
||||
(#8 by @clue)
|
||||
|
||||
* First class support for PHP 5.3 through PHP 7 and HHVM
|
||||
(#7 by @clue)
|
||||
|
||||
## 0.3.2 (2016-03-19)
|
||||
|
||||
* Compatibility with react/socket-client:v0.5 (keeping full BC)
|
||||
(#6 by @clue)
|
||||
|
||||
## 0.3.1 (2014-09-27)
|
||||
|
||||
* Support React PHP v0.4 (while preserving BC with React PHP v0.3)
|
||||
(#4)
|
||||
|
||||
## 0.3.0 (2013-06-24)
|
||||
|
||||
* BC break: Switch from (deprecated) `clue/connection-manager` to `react/socket-client`
|
||||
and thus replace each occurance of `getConnect($host, $port)` with `create($host, $port)`
|
||||
(#1)
|
||||
|
||||
* Fix: Timeouts in `ConnectionManagerTimeout` now actually work
|
||||
(#1)
|
||||
|
||||
* Fix: Properly reject promise in `ConnectionManagerSelective` when no targets
|
||||
have been found
|
||||
(#1)
|
||||
|
||||
## 0.2.0 (2013-02-08)
|
||||
|
||||
* Feature: Add `ConnectionManagerSelective` which works like a network/firewall ACL
|
||||
|
||||
## 0.1.0 (2013-01-12)
|
||||
|
||||
* First tagged release
|
||||
|
21
vendor/clue/connection-manager-extra/LICENSE
vendored
21
vendor/clue/connection-manager-extra/LICENSE
vendored
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
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.
|
287
vendor/clue/connection-manager-extra/README.md
vendored
287
vendor/clue/connection-manager-extra/README.md
vendored
@ -1,287 +0,0 @@
|
||||
# clue/reactphp-connection-manager-extra
|
||||
|
||||
[](https://github.com/clue/reactphp-connection-manager-extra/actions)
|
||||
[](https://packagist.org/packages/clue/connection-manager-extra)
|
||||
|
||||
This project provides _extra_ (in terms of "additional", "extraordinary", "special" and "unusual") decorators,
|
||||
built on top of [ReactPHP's Socket](https://github.com/reactphp/socket).
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
* [Support us](#support-us)
|
||||
* [Introduction](#introduction)
|
||||
* [Usage](#usage)
|
||||
* [Repeat](#repeat)
|
||||
* [Timeout](#timeout)
|
||||
* [Delay](#delay)
|
||||
* [Reject](#reject)
|
||||
* [Swappable](#swappable)
|
||||
* [Consecutive](#consecutive)
|
||||
* [Random](#random)
|
||||
* [Concurrent](#concurrent)
|
||||
* [Selective](#selective)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Support us
|
||||
|
||||
We invest a lot of time developing, maintaining and updating our awesome
|
||||
open-source projects. You can help us sustain this high-quality of our work by
|
||||
[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
|
||||
numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
|
||||
for details.
|
||||
|
||||
Let's take these projects to the next level together! 🚀
|
||||
|
||||
## Introduction
|
||||
|
||||
If you're not already familar with [react/socket](https://github.com/reactphp/socket),
|
||||
think of it as an async (non-blocking) version of [`fsockopen()`](https://www.php.net/manual/en/function.fsockopen.php)
|
||||
or [`stream_socket_client()`](https://www.php.net/manual/en/function.stream-socket-client.php).
|
||||
I.e. before you can send and receive data to/from a remote server, you first have to establish a connection - which
|
||||
takes its time because it involves several steps.
|
||||
In order to be able to establish several connections at the same time, [react/socket](https://github.com/reactphp/socket) provides a simple
|
||||
API to establish simple connections in an async (non-blocking) way.
|
||||
|
||||
This project includes several classes that extend this base functionality by implementing the same simple `ConnectorInterface`.
|
||||
This interface provides a single promise-based method `connect($uri)` which can be used to easily notify
|
||||
when the connection is successfully established or the `Connector` gives up and the connection fails.
|
||||
|
||||
```php
|
||||
$connector->connect('www.google.com:80')->then(function ($stream) {
|
||||
echo 'connection successfully established';
|
||||
$stream->write("GET / HTTP/1.0\r\nHost: www.google.com\r\n\r\n");
|
||||
$stream->end();
|
||||
}, function ($exception) {
|
||||
echo 'connection attempt failed: ' . $exception->getMessage();
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
Because everything uses the same simple API, the resulting `Connector` classes can be easily interchanged
|
||||
and be used in places that expect the normal `ConnectorInterface`. This can be used to stack them into each other,
|
||||
like using [timeouts](#timeout) for TCP connections, [delaying](#delay) SSL/TLS connections,
|
||||
[retrying](#repeat) failed connection attempts, [randomly](#random) picking a `Connector` or
|
||||
any combination thereof.
|
||||
|
||||
## Usage
|
||||
|
||||
This section lists all features of this library along with some examples.
|
||||
The examples assume you've [installed](#install) this library and
|
||||
already [set up a `Socket/Connector` instance `$connector`](https://github.com/reactphp/socket#connector).
|
||||
|
||||
All classes are located in the `ConnectionManager\Extra` namespace.
|
||||
|
||||
### Repeat
|
||||
|
||||
The `ConnectionManagerRepeat($connector, $tries)` tries connecting to the given location up to a maximum
|
||||
of `$tries` times when the connection fails.
|
||||
|
||||
If you pass a value of `3` to it, it will first issue a normal connection attempt
|
||||
and then retry up to 2 times if the connection attempt fails:
|
||||
|
||||
```php
|
||||
$connectorRepeater = new ConnectionManagerRepeat($connector, 3);
|
||||
|
||||
$connectorRepeater->connect('www.google.com:80')->then(function ($stream) {
|
||||
echo 'connection successfully established';
|
||||
$stream->close();
|
||||
});
|
||||
```
|
||||
|
||||
### Timeout
|
||||
|
||||
The `ConnectionManagerTimeout($connector, $timeout, $loop = null)` sets a maximum `$timeout` in seconds on when to give up
|
||||
waiting for the connection to complete.
|
||||
|
||||
```php
|
||||
$connector = new ConnectionManagerTimeout($connector, 3.0);
|
||||
```
|
||||
|
||||
### Delay
|
||||
|
||||
The `ConnectionManagerDelay($connector, $delay, $loop = null)` sets a fixed initial `$delay` in seconds before actually
|
||||
trying to connect. (Not to be confused with [`ConnectionManagerTimeout`](#timeout) which sets a _maximum timeout_.)
|
||||
|
||||
```php
|
||||
$delayed = new ConnectionManagerDelayed($connector, 0.5);
|
||||
```
|
||||
|
||||
### Reject
|
||||
|
||||
The `ConnectionManagerReject(null|string|callable $reason)` simply rejects every single connection attempt.
|
||||
This is particularly useful for the below [`ConnectionManagerSelective`](#selective) to reject connection attempts
|
||||
to only certain destinations (for example blocking advertisements or harmful sites).
|
||||
|
||||
The constructor accepts an optional rejection reason which will be used for
|
||||
rejecting the resulting promise.
|
||||
|
||||
You can explicitly pass a `string` value which will be used as the message for
|
||||
the `Exception` instance:
|
||||
|
||||
```php
|
||||
$connector = new ConnectionManagerReject('Blocked');
|
||||
$connector->connect('www.google.com:80')->then(null, function ($e) {
|
||||
assert($e instanceof \Exception);
|
||||
assert($e->getMessage() === 'Blocked');
|
||||
});
|
||||
```
|
||||
|
||||
You can explicitly pass a `callable` value which will be used to either
|
||||
`throw` or `return` a custom `Exception` instance:
|
||||
|
||||
```php
|
||||
$connector = new ConnectionManagerReject(function ($uri) {
|
||||
throw new RuntimeException($uri . ' blocked');
|
||||
});
|
||||
$connector->connect('www.google.com:80')->then(null, function ($e) {
|
||||
assert($e instanceof \RuntimeException);
|
||||
assert($e->getMessage() === 'www.google.com:80 blocked');
|
||||
});
|
||||
```
|
||||
|
||||
### Swappable
|
||||
|
||||
The `ConnectionManagerSwappable($connector)` is a simple decorator for other `ConnectionManager`s to
|
||||
simplify exchanging the actual `ConnectionManager` during runtime (`->setConnectionManager($connector)`).
|
||||
|
||||
### Consecutive
|
||||
|
||||
The `ConnectionManagerConsecutive($connectors)` establishes connections by trying to connect through
|
||||
any of the given `ConnectionManager`s in consecutive order until the first one succeeds.
|
||||
|
||||
```php
|
||||
$consecutive = new ConnectionManagerConsecutive(array(
|
||||
$connector1,
|
||||
$connector2
|
||||
));
|
||||
```
|
||||
|
||||
### Random
|
||||
|
||||
The `ConnectionManagerRandom($connectors)` works much like `ConnectionManagerConsecutive` but instead
|
||||
of using a fixed order, it always uses a randomly shuffled order.
|
||||
|
||||
```php
|
||||
$random = new ConnectionManagerRandom(array(
|
||||
$connector1,
|
||||
$connector2
|
||||
));
|
||||
```
|
||||
|
||||
### Concurrent
|
||||
|
||||
The `ConnectionManagerConcurrent($connectors)` establishes connections by trying to connect through
|
||||
ALL of the given `ConnectionManager`s at once, until the first one succeeds.
|
||||
|
||||
```php
|
||||
$concurrent = new ConnectionManagerConcurrent(array(
|
||||
$connector1,
|
||||
$connector2
|
||||
));
|
||||
```
|
||||
|
||||
### Selective
|
||||
|
||||
The `ConnectionManagerSelective($connectors)` manages a list of `Connector`s and
|
||||
forwards each connection through the first matching one.
|
||||
This can be used to implement networking access control lists (ACLs) or firewall
|
||||
rules like a blacklist or whitelist.
|
||||
|
||||
This allows fine-grained control on how to handle outgoing connections, like
|
||||
rejecting advertisements, delaying unencrypted HTTP requests or forwarding HTTPS
|
||||
connection through a foreign country.
|
||||
|
||||
If none of the entries in the list matches, the connection will be rejected.
|
||||
This can be used to implement a very simple whitelist like this:
|
||||
|
||||
```php
|
||||
$selective = new ConnectionManagerSelective(array(
|
||||
'github.com' => $connector,
|
||||
'*:443' => $connector
|
||||
));
|
||||
```
|
||||
|
||||
If you want to implement a blacklist (i.e. reject only certain targets), make
|
||||
sure to add a default target to the end of the list like this:
|
||||
|
||||
```php
|
||||
$reject = new ConnectionManagerReject();
|
||||
$selective = new ConnectionManagerSelective(array(
|
||||
'ads.example.com' => $reject,
|
||||
'*:80-81' => $reject,
|
||||
'*' => $connector
|
||||
));
|
||||
```
|
||||
|
||||
Similarly, you can also combine any of the other connectors to implement more
|
||||
advanced connection setups, such as delaying unencrypted connections only and
|
||||
retrying unreliable hosts:
|
||||
|
||||
```php
|
||||
// delay connection by 2 seconds
|
||||
$delayed = new ConnectionManagerDelay($connector, 2.0);
|
||||
|
||||
// maximum of 3 tries, each taking no longer than 2.0 seconds
|
||||
$retry = new ConnectionManagerRepeat(
|
||||
new ConnectionManagerTimeout($connector, 2.0),
|
||||
3
|
||||
);
|
||||
|
||||
$selective = new ConnectionManagerSelective(array(
|
||||
'*:80' => $delayed,
|
||||
'unreliable.example.com' => $retry,
|
||||
'*' => $connector
|
||||
));
|
||||
```
|
||||
|
||||
Each entry in the list MUST be in the form `host` or `host:port`, where
|
||||
`host` may contain the `*` wildcard character and `port` may be given as
|
||||
either an exact port number or as a range in the form of `min-max`.
|
||||
Passing anything else will result in an `InvalidArgumentException`.
|
||||
|
||||
> Note that the host will be matched exactly as-is otherwise. This means that
|
||||
if you only block `youtube.com`, this has no effect on `www.youtube.com`.
|
||||
You may want to add a second rule for `*.youtube.com` in this case.
|
||||
|
||||
## 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 clue/connection-manager-extra:^1.3
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is released under the permissive [MIT license](LICENSE).
|
||||
|
||||
> Did you know that I offer custom development services and issuing invoices for
|
||||
sponsorships of releases and for contributions? Contact me (@clue) for details.
|
@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "clue/connection-manager-extra",
|
||||
"description": "Extra decorators for creating async TCP/IP connections, built on top of ReactPHP's Socket component",
|
||||
"keywords": ["Socket", "network", "connection", "timeout", "delay", "reject", "repeat", "retry", "random", "acl", "firewall", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-connection-manager-extra",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "ConnectionManager\\Extra\\": "src" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "ConnectionManager\\Tests\\Extra\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3 || ^2.1 || ^1.2.1",
|
||||
"react/promise-timer": "^1.9",
|
||||
"react/socket": "^1.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8"
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Timer;
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
class ConnectionManagerDelay implements ConnectorInterface
|
||||
{
|
||||
/** @var ConnectorInterface */
|
||||
private $connectionManager;
|
||||
|
||||
/** @var float */
|
||||
private $delay;
|
||||
|
||||
/** @var LoopInterface */
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* @param ConnectorInterface $connectionManager
|
||||
* @param float $delay
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct(ConnectorInterface $connectionManager, $delay, LoopInterface $loop = null)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->delay = $delay;
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$connectionManager = $this->connectionManager;
|
||||
|
||||
return Timer\resolve($this->delay, $this->loop)->then(function () use ($connectionManager, $uri) {
|
||||
return $connectionManager->connect($uri);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Promise;
|
||||
use Exception;
|
||||
|
||||
// a simple connection manager that rejects every single connection attempt
|
||||
class ConnectionManagerReject implements ConnectorInterface
|
||||
{
|
||||
private $reason = 'Connection rejected';
|
||||
|
||||
/**
|
||||
* @param null|string|callable $reason
|
||||
*/
|
||||
public function __construct($reason = null)
|
||||
{
|
||||
if ($reason !== null) {
|
||||
$this->reason = $reason;
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$reason = $this->reason;
|
||||
if (!is_string($reason)) {
|
||||
try {
|
||||
$reason = $reason($uri);
|
||||
} catch (\Exception $e) {
|
||||
$reason = $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$reason instanceof \Exception) {
|
||||
$reason = new Exception($reason);
|
||||
}
|
||||
|
||||
return Promise\reject($reason);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class ConnectionManagerRepeat implements ConnectorInterface
|
||||
{
|
||||
protected $connectionManager;
|
||||
protected $maximumTries;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager, $maximumTries)
|
||||
{
|
||||
if ($maximumTries < 1) {
|
||||
throw new InvalidArgumentException('Maximum number of tries must be >= 1');
|
||||
}
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->maximumTries = $maximumTries;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$tries = $this->maximumTries;
|
||||
$connector = $this->connectionManager;
|
||||
|
||||
return new Promise(function ($resolve, $reject) use ($uri, &$pending, &$tries, $connector) {
|
||||
$try = function ($error = null) use (&$try, &$pending, &$tries, $uri, $connector, $resolve, $reject) {
|
||||
if ($tries > 0) {
|
||||
--$tries;
|
||||
$pending = $connector->connect($uri);
|
||||
$pending->then($resolve, $try);
|
||||
} else {
|
||||
$reject(new Exception('Connection still fails even after retrying', 0, $error));
|
||||
}
|
||||
};
|
||||
|
||||
$try();
|
||||
}, function ($_, $reject) use (&$pending, &$tries) {
|
||||
// stop retrying, reject results and cancel pending attempt
|
||||
$tries = 0;
|
||||
$reject(new \RuntimeException('Cancelled'));
|
||||
|
||||
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
|
||||
$pending->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
// connection manager decorator which simplifies exchanging the actual connection manager during runtime
|
||||
class ConnectionManagerSwappable implements ConnectorInterface
|
||||
{
|
||||
protected $connectionManager;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
return $this->connectionManager->connect($uri);
|
||||
}
|
||||
|
||||
public function setConnectionManager(ConnectorInterface $connectionManager)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Timer;
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
class ConnectionManagerTimeout implements ConnectorInterface
|
||||
{
|
||||
/** @var ConnectorInterface */
|
||||
private $connectionManager;
|
||||
|
||||
/** @var float */
|
||||
private $timeout;
|
||||
|
||||
/** @var LoopInterface */
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* @param ConnectorInterface $connectionManager
|
||||
* @param float $timeout
|
||||
* @param ?LoopInterface $loop
|
||||
*/
|
||||
public function __construct(ConnectorInterface $connectionManager, $timeout, LoopInterface $loop = null)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->timeout = $timeout;
|
||||
$this->loop = $loop ?: Loop::get();
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$promise = $this->connectionManager->connect($uri);
|
||||
|
||||
return Timer\timeout($promise, $this->timeout, $this->loop)->then(null, function ($e) use ($promise) {
|
||||
// connection successfully established but timeout already expired => close successful connection
|
||||
$promise->then(function ($connection) {
|
||||
$connection->end();
|
||||
});
|
||||
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class ConnectionManagerConcurrent extends ConnectionManagerConsecutive
|
||||
{
|
||||
public function connect($uri)
|
||||
{
|
||||
$all = array();
|
||||
foreach ($this->managers as $connector) {
|
||||
$all []= $connector->connect($uri);
|
||||
}
|
||||
return Promise\any($all)->then(function ($conn) use ($all) {
|
||||
// a connection attempt succeeded
|
||||
// => cancel all pending connection attempts
|
||||
foreach ($all as $promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
|
||||
// if promise resolves despite cancellation, immediately close stream
|
||||
$promise->then(function ($stream) use ($conn) {
|
||||
if ($stream !== $conn) {
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
return $conn;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use UnderflowException;
|
||||
|
||||
class ConnectionManagerConsecutive implements ConnectorInterface
|
||||
{
|
||||
protected $managers;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
*/
|
||||
public function __construct(array $managers)
|
||||
{
|
||||
if (!$managers) {
|
||||
throw new \InvalidArgumentException('List of connectors must not be empty');
|
||||
}
|
||||
$this->managers = $managers;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
return $this->tryConnection($this->managers, $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
* @param string $uri
|
||||
* @return Promise
|
||||
* @internal
|
||||
*/
|
||||
public function tryConnection(array $managers, $uri)
|
||||
{
|
||||
return new Promise\Promise(function ($resolve, $reject) use (&$managers, &$pending, $uri) {
|
||||
$try = function () use (&$try, &$managers, $uri, $resolve, $reject, &$pending) {
|
||||
if (!$managers) {
|
||||
return $reject(new UnderflowException('No more managers to try to connect through'));
|
||||
}
|
||||
|
||||
$manager = array_shift($managers);
|
||||
$pending = $manager->connect($uri);
|
||||
$pending->then($resolve, $try);
|
||||
};
|
||||
|
||||
$try();
|
||||
}, function ($_, $reject) use (&$managers, &$pending) {
|
||||
// stop retrying, reject results and cancel pending attempt
|
||||
$managers = array();
|
||||
$reject(new \RuntimeException('Cancelled'));
|
||||
|
||||
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
|
||||
$pending->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
class ConnectionManagerRandom extends ConnectionManagerConsecutive
|
||||
{
|
||||
public function connect($uri)
|
||||
{
|
||||
$managers = $this->managers;
|
||||
shuffle($managers);
|
||||
|
||||
return $this->tryConnection($managers, $uri);
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Promise;
|
||||
use UnderflowException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ConnectionManagerSelective implements ConnectorInterface
|
||||
{
|
||||
private $managers;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
*/
|
||||
public function __construct(array $managers)
|
||||
{
|
||||
foreach ($managers as $filter => $manager) {
|
||||
$host = $filter;
|
||||
$portMin = 0;
|
||||
$portMax = 65535;
|
||||
|
||||
// search colon (either single one OR preceded by "]" due to IPv6)
|
||||
$colon = strrpos($host, ':');
|
||||
if ($colon !== false && (strpos($host, ':') === $colon || substr($host, $colon - 1, 1) === ']' )) {
|
||||
if (!isset($host[$colon + 1])) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no port after colon');
|
||||
}
|
||||
|
||||
$minus = strpos($host, '-', $colon);
|
||||
if ($minus === false) {
|
||||
$portMin = $portMax = (int)substr($host, $colon + 1);
|
||||
|
||||
if (substr($host, $colon + 1) !== (string)$portMin) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no valid port after colon');
|
||||
}
|
||||
} else {
|
||||
$portMin = (int)substr($host, $colon + 1, ($minus - $colon));
|
||||
$portMax = (int)substr($host, $minus + 1);
|
||||
|
||||
if (substr($host, $colon + 1) !== ($portMin . '-' . $portMax)) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no valid port range after colon');
|
||||
}
|
||||
|
||||
if ($portMin > $portMax) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has port range mixed up');
|
||||
}
|
||||
}
|
||||
$host = substr($host, 0, $colon);
|
||||
}
|
||||
|
||||
if ($host === '') {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has an empty host');
|
||||
}
|
||||
|
||||
if (!$manager instanceof ConnectorInterface) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" is not a valid connector');
|
||||
}
|
||||
}
|
||||
|
||||
$this->managers = $managers;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$parts = parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri);
|
||||
if (!isset($parts) || !isset($parts['scheme'], $parts['host'], $parts['port'])) {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid URI'));
|
||||
}
|
||||
|
||||
$connector = $this->getConnectorForTarget(
|
||||
trim($parts['host'], '[]'),
|
||||
$parts['port']
|
||||
);
|
||||
|
||||
if ($connector === null) {
|
||||
return Promise\reject(new UnderflowException('No connector for given target found'));
|
||||
}
|
||||
|
||||
return $connector->connect($uri);
|
||||
}
|
||||
|
||||
private function getConnectorForTarget($targetHost, $targetPort)
|
||||
{
|
||||
foreach ($this->managers as $host => $connector) {
|
||||
$portMin = 0;
|
||||
$portMax = 65535;
|
||||
|
||||
// search colon (either single one OR preceded by "]" due to IPv6)
|
||||
$colon = strrpos($host, ':');
|
||||
if ($colon !== false && (strpos($host, ':') === $colon || substr($host, $colon - 1, 1) === ']' )) {
|
||||
$minus = strpos($host, '-', $colon);
|
||||
if ($minus === false) {
|
||||
$portMin = $portMax = (int)substr($host, $colon + 1);
|
||||
} else {
|
||||
$portMin = (int)substr($host, $colon + 1, ($minus - $colon));
|
||||
$portMax = (int)substr($host, $minus + 1);
|
||||
}
|
||||
$host = trim(substr($host, 0, $colon), '[]');
|
||||
}
|
||||
|
||||
if ($targetPort >= $portMin && $targetPort <= $portMax && fnmatch($host, $targetHost)) {
|
||||
return $connector;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
200
vendor/clue/http-proxy-react/CHANGELOG.md
vendored
200
vendor/clue/http-proxy-react/CHANGELOG.md
vendored
@ -1,200 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 1.8.0 (2022-09-01)
|
||||
|
||||
* Feature: Full support for PHP 8.1 and PHP 8.2.
|
||||
(#47 and #48 by @SimonFrings)
|
||||
|
||||
* Feature: Mark passwords and URIs as `#[\SensitiveParameter]` (PHP 8.2+).
|
||||
(#49 by @SimonFrings)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Promise v3.
|
||||
(#44 by @clue)
|
||||
|
||||
* Fix: Fix invalid references in exception stack trace.
|
||||
(#45 by @clue)
|
||||
|
||||
* Improve test suite and fix legacy HHVM build.
|
||||
(#46 by @SimonFrings)
|
||||
|
||||
## 1.7.0 (2021-08-06)
|
||||
|
||||
* Feature: Simplify usage by supporting new default loop and making `Connector` optional.
|
||||
(#41 and #42 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector(
|
||||
'127.0.0.1:8080',
|
||||
new React\Socket\Connector($loop)
|
||||
);
|
||||
|
||||
// new (using default loop)
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
```
|
||||
|
||||
* Documentation improvements and updated examples.
|
||||
(#39 and #43 by @clue and #40 by @PaulRotmann)
|
||||
|
||||
* Improve test suite and use GitHub actions for continuous integration (CI).
|
||||
(#38 by @SimonFrings)
|
||||
|
||||
## 1.6.0 (2020-10-23)
|
||||
|
||||
* Enhanced documentation for ReactPHP's new HTTP client.
|
||||
(#35 and #37 by @SimonFrings)
|
||||
|
||||
* Improve test suite, prepare PHP 8 support and support PHPUnit 9.3.
|
||||
(#36 by @SimonFrings)
|
||||
|
||||
## 1.5.0 (2020-06-19)
|
||||
|
||||
* Feature / Fix: Support PHP 7.4 by skipping unneeded cleanup of exception trace args.
|
||||
(#33 by @clue)
|
||||
|
||||
* Clean up test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Run tests on PHP 7.4, PHPUnit 9 and simplify test matrix.
|
||||
Link to using SSH proxy (SSH tunnel) as an alternative.
|
||||
(#27 by @clue and #31, #32 and #34 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2018-10-30)
|
||||
|
||||
* Feature: Improve error reporting for failed connection attempts and improve
|
||||
cancellation forwarding during proxy connection setup.
|
||||
(#23 and #26 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.
|
||||
Similarly, any underlying connection issues to the proxy server will now be
|
||||
reported as part of the previous exception.
|
||||
|
||||
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 = $proxy->connect('tcp://example.com:80');
|
||||
$promise->then(function (ConnectionInterface $connection) {
|
||||
// …
|
||||
}, function (Exception $e) {
|
||||
echo $e->getMessage();
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Add support for custom HTTP request headers.
|
||||
(#25 by @valga and @clue)
|
||||
|
||||
```php
|
||||
// new: now supports custom HTTP request headers
|
||||
$proxy = new ProxyConnector('127.0.0.1:8080', $connector, array(
|
||||
'Proxy-Authorization' => 'Bearer abc123',
|
||||
'User-Agent' => 'ReactPHP'
|
||||
));
|
||||
```
|
||||
|
||||
* Fix: Fix connecting to IPv6 destination hosts.
|
||||
(#22 by @clue)
|
||||
|
||||
* Link to clue/reactphp-buzz for HTTP requests and update project homepage.
|
||||
(#21 and #24 by @clue)
|
||||
|
||||
## 1.3.0 (2018-02-13)
|
||||
|
||||
* Feature: Support communication over Unix domain sockets (UDS)
|
||||
(#20 by @clue)
|
||||
|
||||
```php
|
||||
// new: now supports communication over Unix domain sockets (UDS)
|
||||
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
|
||||
```
|
||||
|
||||
* Reduce memory consumption by avoiding circular reference from stream reader
|
||||
(#18 by @valga)
|
||||
|
||||
* Improve documentation
|
||||
(#19 by @clue)
|
||||
|
||||
## 1.2.0 (2017-08-30)
|
||||
|
||||
* Feature: Use socket error codes for connection rejections
|
||||
(#17 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $proxy->connect('imap.example.com:143');
|
||||
$promise->then(null, function (Exeption $e) {
|
||||
if ($e->getCode() === SOCKET_EACCES) {
|
||||
echo 'Failed to authenticate with proxy!';
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite by locking Travis distro so new defaults will not break the build and
|
||||
optionally exclude tests that rely on working internet connection
|
||||
(#15 and #16 by @clue)
|
||||
|
||||
## 1.1.0 (2017-06-11)
|
||||
|
||||
* Feature: Support proxy authentication if proxy URL contains username/password
|
||||
(#14 by @clue)
|
||||
|
||||
```php
|
||||
// new: username/password will now be passed to HTTP proxy server
|
||||
$proxy = new ProxyConnector('user:pass@127.0.0.1:8080', $connector);
|
||||
```
|
||||
|
||||
## 1.0.0 (2017-06-10)
|
||||
|
||||
* First stable release, now following SemVer
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.3.2 release.
|
||||
|
||||
## 0.3.2 (2017-06-10)
|
||||
|
||||
* Fix: Fix rejecting invalid URIs and unexpected URI schemes
|
||||
(#13 by @clue)
|
||||
|
||||
* Fix HHVM build for now again and ignore future HHVM build errors
|
||||
(#12 by @clue)
|
||||
|
||||
* Documentation for Connector concepts (TCP/TLS, timeouts, DNS resolution)
|
||||
(#11 by @clue)
|
||||
|
||||
## 0.3.1 (2017-05-10)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8
|
||||
(#10 by @clue)
|
||||
|
||||
## 0.3.0 (2017-04-10)
|
||||
|
||||
* Feature / BC break: Replace deprecated SocketClient with new Socket component
|
||||
(#9 by @clue)
|
||||
|
||||
This implies that the `ProxyConnector` from this package now implements the
|
||||
`React\Socket\ConnectorInterface` instead of the legacy
|
||||
`React\SocketClient\ConnectorInterface`.
|
||||
|
||||
## 0.2.0 (2017-04-10)
|
||||
|
||||
* Feature / BC break: Update SocketClient to v0.7 or v0.6 and
|
||||
use `connect($uri)` instead of `create($host, $port)`
|
||||
(#8 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$connector->create($host, $port)->then(function (Stream $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
|
||||
// new
|
||||
$connector->connect($uri)->then(function (ConnectionInterface $conn) {
|
||||
$conn->write("…");
|
||||
});
|
||||
```
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev
|
||||
(#7 by @clue)
|
||||
|
||||
|
||||
## 0.1.0 (2016-11-01)
|
||||
|
||||
* First tagged release
|
21
vendor/clue/http-proxy-react/LICENSE
vendored
21
vendor/clue/http-proxy-react/LICENSE
vendored
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
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.
|
510
vendor/clue/http-proxy-react/README.md
vendored
510
vendor/clue/http-proxy-react/README.md
vendored
@ -1,510 +0,0 @@
|
||||
# clue/reactphp-http-proxy
|
||||
|
||||
[](https://github.com/clue/reactphp-http-proxy/actions)
|
||||
[](https://packagist.org/packages/clue/http-proxy-react)
|
||||
|
||||
Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTP
|
||||
CONNECT proxy server, built on top of [ReactPHP](https://reactphp.org/).
|
||||
|
||||
HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy")
|
||||
are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), to
|
||||
conceal the origin address (anonymity) or to circumvent address blocking
|
||||
(geoblocking). While many (public) HTTP CONNECT proxy servers often limit this
|
||||
to HTTPS port `443` only, this can technically be used to tunnel any
|
||||
TCP/IP-based protocol (HTTP, SMTP, IMAP etc.).
|
||||
This library provides a simple API to create these tunneled connections for you.
|
||||
Because it implements ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
|
||||
it can simply be used in place of a normal connector.
|
||||
This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
|
||||
existing higher-level protocol implementation.
|
||||
|
||||
* **Async execution of connections** -
|
||||
Send any number of HTTP CONNECT requests in parallel and process their
|
||||
responses as soon as results come in.
|
||||
The Promise-based design provides a *sane* interface to working with out of
|
||||
order responses and possible connection errors.
|
||||
* **Standard interfaces** -
|
||||
Allows easy integration with existing higher-level components by implementing
|
||||
ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface).
|
||||
* **Lightweight, SOLID design** -
|
||||
Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
|
||||
and does not get in your way.
|
||||
Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
|
||||
* **Good test coverage** -
|
||||
Comes with an automated tests suite and is regularly tested against actual proxy servers in the wild.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Support us](#support-us)
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [ProxyConnector](#proxyconnector)
|
||||
* [Plain TCP connections](#plain-tcp-connections)
|
||||
* [Secure TLS connections](#secure-tls-connections)
|
||||
* [HTTP requests](#http-requests)
|
||||
* [Connection timeout](#connection-timeout)
|
||||
* [DNS resolution](#dns-resolution)
|
||||
* [Authentication](#authentication)
|
||||
* [Advanced HTTP headers](#advanced-http-headers)
|
||||
* [Advanced secure proxy connections](#advanced-secure-proxy-connections)
|
||||
* [Advanced Unix domain sockets](#advanced-unix-domain-sockets)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
* [More](#more)
|
||||
|
||||
## Support us
|
||||
|
||||
We invest a lot of time developing, maintaining and updating our awesome
|
||||
open-source projects. You can help us sustain this high-quality of our work by
|
||||
[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
|
||||
numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
|
||||
for details.
|
||||
|
||||
Let's take these projects to the next level together! 🚀
|
||||
|
||||
## Quickstart example
|
||||
|
||||
The following example code demonstrates how this library can be used to send a
|
||||
secure HTTPS request to google.com through a local HTTP proxy server:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
|
||||
$connector = new React\Socket\Connector(array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$browser = new React\Http\Browser($connector);
|
||||
|
||||
$browser->get('https://google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
var_dump($response->getHeaders(), (string) $response->getBody());
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Usage
|
||||
|
||||
### ProxyConnector
|
||||
|
||||
The `ProxyConnector` is responsible for creating plain TCP/IP connections to
|
||||
any destination by using an intermediary HTTP CONNECT proxy.
|
||||
|
||||
```
|
||||
[you] -> [proxy] -> [destination]
|
||||
```
|
||||
|
||||
Its constructor simply accepts an HTTP proxy URL with the proxy server address:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
```
|
||||
|
||||
The proxy URL may or may not contain a scheme and port definition. The default
|
||||
port will be `80` for HTTP (or `443` for HTTPS), but many common HTTP proxy
|
||||
servers use custom ports (often the alternative HTTP port `8080`).
|
||||
|
||||
If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
|
||||
proxy servers etc.), you can explicitly pass a custom instance of the
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
|
||||
|
||||
```php
|
||||
$connector = new React\Socket\Connector(array(
|
||||
'dns' => '127.0.0.1',
|
||||
'tcp' => array(
|
||||
'bindto' => '192.168.10.1:0'
|
||||
),
|
||||
'tls' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false
|
||||
)
|
||||
));
|
||||
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector);
|
||||
```
|
||||
|
||||
This is the main class in this package.
|
||||
Because it implements ReactPHP's standard
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface),
|
||||
it can simply be used in place of a normal connector.
|
||||
Accordingly, it provides only a single public method, the
|
||||
[`connect()`](https://github.com/reactphp/socket#connect) method.
|
||||
The `connect(string $uri): PromiseInterface<ConnectionInterface, Exception>`
|
||||
method can be used to establish a streaming connection.
|
||||
It returns a [Promise](https://github.com/reactphp/promise) which either
|
||||
fulfills with a [ConnectionInterface](https://github.com/reactphp/socket#connectioninterface)
|
||||
on success or rejects with an `Exception` on error.
|
||||
|
||||
This makes it fairly simple to add HTTP CONNECT proxy support to pretty much any
|
||||
higher-level component:
|
||||
|
||||
```diff
|
||||
- $acme = new AcmeApi($connector);
|
||||
+ $proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector);
|
||||
+ $acme = new AcmeApi($proxy);
|
||||
```
|
||||
|
||||
#### Plain TCP connections
|
||||
|
||||
HTTP CONNECT proxies are most frequently used to issue HTTPS requests to your destination.
|
||||
However, this is actually performed on a higher protocol layer and this
|
||||
connector is actually inherently a general-purpose plain TCP/IP connector.
|
||||
As documented above, you can simply invoke its `connect()` method to establish
|
||||
a streaming plain TCP/IP connection and use any higher level protocol like so:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
|
||||
$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) {
|
||||
$connection->write("EHLO local\r\n");
|
||||
$connection->on('data', function ($chunk) use ($connection) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
You can either use the `ProxyConnector` directly or you may want to wrap this connector
|
||||
in ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
|
||||
$connector = new React\Socket\Connector(array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface $connection) {
|
||||
$connection->write("EHLO local\r\n");
|
||||
$connection->on('data', function ($chunk) use ($connection) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Note that HTTP CONNECT proxies often restrict which ports one may connect to.
|
||||
Many (public) proxy servers do in fact limit this to HTTPS (443) only.
|
||||
|
||||
#### Secure TLS connections
|
||||
|
||||
This class can also be used if you want to establish a secure TLS connection
|
||||
(formerly known as SSL) between you and your destination, such as when using
|
||||
secure HTTPS to your destination site. You can simply wrap this connector in
|
||||
ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector):
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
|
||||
$connector = new React\Socket\Connector(array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$connector->connect('tls://smtp.googlemail.com:465')->then(function (React\Socket\ConnectionInterface $connection) {
|
||||
$connection->write("EHLO local\r\n");
|
||||
$connection->on('data', function ($chunk) use ($connection) {
|
||||
echo $chunk;
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
> Note how secure TLS connections are in fact entirely handled outside of
|
||||
this HTTP CONNECT client implementation.
|
||||
|
||||
#### HTTP requests
|
||||
|
||||
This library also allows you to send HTTP requests through an HTTP CONNECT proxy server.
|
||||
|
||||
In order to send HTTP requests, you first have to add a dependency for
|
||||
[ReactPHP's async HTTP client](https://github.com/reactphp/http#client-usage).
|
||||
This allows you to send both plain HTTP and TLS-encrypted HTTPS requests like this:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
|
||||
$connector = new React\Socket\Connector(array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
|
||||
$browser = new React\Http\Browser($connector);
|
||||
|
||||
$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
|
||||
var_dump($response->getHeaders(), (string) $response->getBody());
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
See also [ReactPHP's HTTP client](https://github.com/reactphp/http#client-usage)
|
||||
and any of the [examples](examples) for more details.
|
||||
|
||||
#### Connection timeout
|
||||
|
||||
By default, the `ProxyConnector` does not implement any timeouts for establishing remote
|
||||
connections.
|
||||
Your underlying operating system may impose limits on pending and/or idle TCP/IP
|
||||
connections, anywhere in a range of a few minutes to several hours.
|
||||
|
||||
Many use cases require more control over the timeout and likely values much
|
||||
smaller, usually in the range of a few seconds only.
|
||||
|
||||
You can use ReactPHP's [`Connector`](https://github.com/reactphp/socket#connector)
|
||||
to decorate any given `ConnectorInterface` instance.
|
||||
It provides the same `connect()` method, but will automatically reject the
|
||||
underlying connection attempt if it takes too long:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
|
||||
$connector = new React\Socket\Connector(array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false,
|
||||
'timeout' => 3.0
|
||||
));
|
||||
|
||||
$connector->connect('tcp://google.com:80')->then(function ($connection) {
|
||||
// connection succeeded within 3.0 seconds
|
||||
});
|
||||
```
|
||||
|
||||
See also any of the [examples](examples).
|
||||
|
||||
> Note how the connection timeout is in fact entirely handled outside of this
|
||||
HTTP CONNECT client implementation.
|
||||
|
||||
#### DNS resolution
|
||||
|
||||
By default, the `ProxyConnector` does not perform any DNS resolution at all and simply
|
||||
forwards any hostname you're trying to connect to the remote proxy server.
|
||||
The remote proxy server is thus responsible for looking up any hostnames via DNS
|
||||
(this default mode is thus called *remote DNS resolution*).
|
||||
|
||||
As an alternative, you can also send the destination IP to the remote proxy
|
||||
server.
|
||||
In this mode you either have to stick to using IPs only (which is ofen unfeasable)
|
||||
or perform any DNS lookups locally and only transmit the resolved destination IPs
|
||||
(this mode is thus called *local DNS resolution*).
|
||||
|
||||
The default *remote DNS resolution* is useful if your local `ProxyConnector` either can
|
||||
not resolve target hostnames because it has no direct access to the internet or
|
||||
if it should not resolve target hostnames because its outgoing DNS traffic might
|
||||
be intercepted.
|
||||
|
||||
As noted above, the `ProxyConnector` defaults to using remote DNS resolution.
|
||||
However, wrapping the `ProxyConnector` in ReactPHP's
|
||||
[`Connector`](https://github.com/reactphp/socket#connector) actually
|
||||
performs local DNS resolution unless explicitly defined otherwise.
|
||||
Given that remote DNS resolution is assumed to be the preferred mode, all
|
||||
other examples explicitly disable DNS resolution like this:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
|
||||
$connector = new React\Socket\Connector(array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => false
|
||||
));
|
||||
```
|
||||
|
||||
If you want to explicitly use *local DNS resolution*, you can use the following code:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
|
||||
|
||||
// set up Connector which uses Google's public DNS (8.8.8.8)
|
||||
$connector = new React\Socket\Connector(array(
|
||||
'tcp' => $proxy,
|
||||
'dns' => '8.8.8.8'
|
||||
));
|
||||
```
|
||||
|
||||
> Note how local DNS resolution is in fact entirely handled outside of this
|
||||
HTTP CONNECT client implementation.
|
||||
|
||||
#### Authentication
|
||||
|
||||
If your HTTP proxy server requires authentication, you may pass the username and
|
||||
password as part of the HTTP proxy URL like this:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('alice:password@127.0.0.1:8080');
|
||||
```
|
||||
|
||||
Note that both the username and password must be percent-encoded if they contain
|
||||
special characters:
|
||||
|
||||
```php
|
||||
$user = 'he:llo';
|
||||
$pass = 'p@ss';
|
||||
$url = rawurlencode($user) . ':' . rawurlencode($pass) . '@127.0.0.1:8080';
|
||||
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector($url);
|
||||
```
|
||||
|
||||
> The authentication details will be used for basic authentication and will be
|
||||
transferred in the `Proxy-Authorization` HTTP request header for each
|
||||
connection attempt.
|
||||
If the authentication details are missing or not accepted by the remote HTTP
|
||||
proxy server, it is expected to reject each connection attempt with a
|
||||
`407` (Proxy Authentication Required) response status code and an exception
|
||||
error code of `SOCKET_EACCES` (13).
|
||||
|
||||
#### Advanced HTTP headers
|
||||
|
||||
The `ProxyConnector` constructor accepts an optional array of custom request
|
||||
headers to send in the `CONNECT` request. This can be useful if you're using a
|
||||
custom proxy setup or authentication scheme if the proxy server does not support
|
||||
basic [authentication](#authentication) as documented above. This is rarely used
|
||||
in practice, but may be useful for some more advanced use cases. In this case,
|
||||
you may simply pass an assoc array of additional request headers like this:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector(
|
||||
'127.0.0.1:8080',
|
||||
null,
|
||||
array(
|
||||
'Proxy-Authorization' => 'Bearer abc123',
|
||||
'User-Agent' => 'ReactPHP'
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
#### Advanced secure proxy connections
|
||||
|
||||
Note that communication between the client and the proxy is usually via an
|
||||
unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
|
||||
setup, because you can still establish a TLS connection between you and the
|
||||
destination host as above.
|
||||
|
||||
If you want to connect to a (rather rare) HTTPS proxy, you may want use the
|
||||
`https://` scheme (HTTPS default port 443) to create a secure connection to the proxy:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('https://127.0.0.1:443');
|
||||
|
||||
$proxy->connect('tcp://smtp.googlemail.com:587');
|
||||
```
|
||||
|
||||
#### Advanced Unix domain sockets
|
||||
|
||||
HTTP CONNECT proxy servers support forwarding TCP/IP based connections and
|
||||
higher level protocols.
|
||||
In some advanced cases, it may be useful to let your HTTP CONNECT proxy server
|
||||
listen on a Unix domain socket (UDS) path instead of a IP:port combination.
|
||||
For example, this allows you to rely on file system permissions instead of
|
||||
having to rely on explicit [authentication](#authentication).
|
||||
|
||||
You can simply use the `http+unix://` URI scheme like this:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('http+unix:///tmp/proxy.sock');
|
||||
|
||||
$proxy->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface $connection) {
|
||||
// connected…
|
||||
});
|
||||
```
|
||||
|
||||
Similarly, you can also combine this with [authentication](#authentication)
|
||||
like this:
|
||||
|
||||
```php
|
||||
$proxy = new Clue\React\HttpProxy\ProxyConnector('http+unix://alice:password@/tmp/proxy.sock');
|
||||
```
|
||||
|
||||
> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
|
||||
has limited support for this.
|
||||
In particular, enabling [secure TLS](#secure-tls-connections) may not be
|
||||
supported.
|
||||
|
||||
> Note that the HTTP CONNECT protocol does not support the notion of UDS paths.
|
||||
The above works reasonably well because UDS is only used for the connection between
|
||||
client and proxy server and the path will not actually passed over the protocol.
|
||||
This implies that this does not support connecting to UDS destination paths.
|
||||
|
||||
## 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 clue/http-proxy-react:^1.8
|
||||
```
|
||||
|
||||
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 contains tests that rely on a working internet connection,
|
||||
alternatively you can also run it like this:
|
||||
|
||||
```bash
|
||||
vendor/bin/phpunit --exclude-group internet
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is released under the permissive [MIT license](LICENSE).
|
||||
|
||||
> Did you know that I offer custom development services and issuing invoices for
|
||||
sponsorships of releases and for contributions? Contact me (@clue) for details.
|
||||
|
||||
## More
|
||||
|
||||
* If you want to learn more about how the
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
and its usual implementations look like, refer to the documentation of the underlying
|
||||
[react/socket](https://github.com/reactphp/socket) component.
|
||||
* If you want to learn more about processing streams of data, refer to the
|
||||
documentation of the underlying
|
||||
[react/stream](https://github.com/reactphp/stream) component.
|
||||
* As an alternative to an HTTP CONNECT proxy, you may also want to look into
|
||||
using a SOCKS (SOCKS4/SOCKS5) proxy instead.
|
||||
You may want to use [clue/reactphp-socks](https://github.com/clue/reactphp-socks)
|
||||
which also provides an implementation of the same
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
so that supporting either proxy protocol should be fairly trivial.
|
||||
* As an alternative to an HTTP CONNECT proxy, you may also want to look into
|
||||
using an SSH proxy (SSH tunnel) instead.
|
||||
You may want to use [clue/reactphp-ssh-proxy](https://github.com/clue/reactphp-ssh-proxy)
|
||||
which also provides an implementation of the same
|
||||
[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
so that supporting either proxy protocol should be fairly trivial.
|
||||
* If you're dealing with public proxies, you'll likely have to work with mixed
|
||||
quality and unreliable proxies. You may want to look into using
|
||||
[clue/reactphp-connection-manager-extra](https://github.com/clue/reactphp-connection-manager-extra)
|
||||
which allows retrying unreliable ones, implying connection timeouts,
|
||||
concurrently working with multiple connectors and more.
|
||||
* If you're looking for an end-user HTTP CONNECT proxy server daemon, you may
|
||||
want to use [LeProxy](https://leproxy.org/).
|
31
vendor/clue/http-proxy-react/composer.json
vendored
31
vendor/clue/http-proxy-react/composer.json
vendored
@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "clue/http-proxy-react",
|
||||
"description": "Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
|
||||
"keywords": ["HTTP", "CONNECT", "proxy", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-http-proxy",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": "^3 || ^2.1 || ^1.2.1",
|
||||
"react/socket": "^1.12",
|
||||
"ringcentral/psr7": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.5",
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/http": "^1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\HttpProxy\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\HttpProxy\\": "tests/" }
|
||||
}
|
||||
}
|
278
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
278
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
@ -1,278 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\HttpProxy;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use RingCentral\Psr7;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
use React\Socket\UnixConnector;
|
||||
|
||||
/**
|
||||
* A simple Connector that uses an HTTP CONNECT proxy to create plain TCP/IP connections to any destination
|
||||
*
|
||||
* [you] -> [proxy] -> [destination]
|
||||
*
|
||||
* This is most frequently used to issue HTTPS requests to your destination.
|
||||
* However, this is actually performed on a higher protocol layer and this
|
||||
* connector is actually inherently a general-purpose plain TCP/IP connector.
|
||||
*
|
||||
* Note that HTTP CONNECT proxies often restrict which ports one may connect to.
|
||||
* Many (public) proxy servers do in fact limit this to HTTPS (443) only.
|
||||
*
|
||||
* If you want to establish a TLS connection (such as HTTPS) between you and
|
||||
* your destination, you may want to wrap this connector in a SecureConnector
|
||||
* instance.
|
||||
*
|
||||
* Note that communication between the client and the proxy is usually via an
|
||||
* unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
|
||||
* setup, because you can still establish a TLS connection between you and the
|
||||
* destination host as above.
|
||||
*
|
||||
* If you want to connect to a (rather rare) HTTPS proxy, you may want use its
|
||||
* HTTPS port (443) and use a SecureConnector instance to create a secure
|
||||
* connection to the proxy.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7231#section-4.3.6
|
||||
*/
|
||||
class ProxyConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $proxyUri;
|
||||
private $headers = '';
|
||||
|
||||
/**
|
||||
* Instantiate a new ProxyConnector which uses the given $proxyUrl
|
||||
*
|
||||
* @param string $proxyUrl The proxy URL may or may not contain a scheme and
|
||||
* port definition. The default port will be `80` for HTTP (or `443` for
|
||||
* HTTPS), but many common HTTP proxy servers use custom ports.
|
||||
* @param ?ConnectorInterface $connector (Optional) Connector to use.
|
||||
* @param array $httpHeaders Custom HTTP headers to be sent to the proxy.
|
||||
* @throws InvalidArgumentException if the proxy URL is invalid
|
||||
*/
|
||||
public function __construct(
|
||||
#[\SensitiveParameter]
|
||||
$proxyUrl,
|
||||
ConnectorInterface $connector = null,
|
||||
array $httpHeaders = array()
|
||||
) {
|
||||
// support `http+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
|
||||
// rewrite URI to parse authentication from dummy host
|
||||
$proxyUrl = 'http://' . $match[1] . 'localhost';
|
||||
|
||||
// connector uses Unix transport scheme and explicit path given
|
||||
$connector = new FixedUriConnector(
|
||||
'unix://' . $match[2],
|
||||
$connector ?: new UnixConnector()
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($proxyUrl, '://') === false) {
|
||||
$proxyUrl = 'http://' . $proxyUrl;
|
||||
}
|
||||
|
||||
$parts = parse_url($proxyUrl);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
|
||||
throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"');
|
||||
}
|
||||
|
||||
// apply default port and TCP/TLS transport for given scheme
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
|
||||
}
|
||||
$parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp';
|
||||
|
||||
$this->connector = $connector ?: new Connector();
|
||||
$this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
// prepare Proxy-Authorization header if URI contains username/password
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
$this->headers = 'Proxy-Authorization: Basic ' . base64_encode(
|
||||
rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : ''))
|
||||
) . "\r\n";
|
||||
}
|
||||
|
||||
// append any additional custom request headers
|
||||
foreach ($httpHeaders as $name => $values) {
|
||||
foreach ((array)$values as $value) {
|
||||
$this->headers .= $name . ': ' . $value . "\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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('Invalid target URI specified'));
|
||||
}
|
||||
|
||||
$target = $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
// construct URI to HTTP CONNECT proxy server to connect to
|
||||
$proxyUri = $this->proxyUri;
|
||||
|
||||
// append path from URI if given
|
||||
if (isset($parts['path'])) {
|
||||
$proxyUri .= $parts['path'];
|
||||
}
|
||||
|
||||
// parse query args
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// append hostname from URI to query string unless explicitly given
|
||||
if (!isset($args['hostname'])) {
|
||||
$args['hostname'] = trim($parts['host'], '[]');
|
||||
}
|
||||
|
||||
// append query string
|
||||
$proxyUri .= '?' . http_build_query($args, '', '&');
|
||||
|
||||
// append fragment from URI if given
|
||||
if (isset($parts['fragment'])) {
|
||||
$proxyUri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
$connecting = $this->connector->connect($proxyUri);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($connecting, $uri) {
|
||||
$reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled while waiting for proxy (ECONNABORTED)',
|
||||
defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
|
||||
// either close active connection or cancel pending connection attempt
|
||||
$connecting->then(function (ConnectionInterface $stream) {
|
||||
$stream->close();
|
||||
});
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
$headers = $this->headers;
|
||||
$connecting->then(function (ConnectionInterface $stream) use ($target, $headers, $deferred, $uri) {
|
||||
// keep buffering data until headers are complete
|
||||
$buffer = '';
|
||||
$stream->on('data', $fn = function ($chunk) use (&$buffer, $deferred, $stream, &$fn, $uri) {
|
||||
$buffer .= $chunk;
|
||||
|
||||
$pos = strpos($buffer, "\r\n\r\n");
|
||||
if ($pos !== false) {
|
||||
// end of headers received => stop buffering
|
||||
$stream->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
// try to parse headers as response message
|
||||
try {
|
||||
$response = Psr7\parse_response(substr($buffer, 0, $pos));
|
||||
} catch (Exception $e) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy returned invalid response (EBADMSG)',
|
||||
defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71,
|
||||
$e
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() === 407) {
|
||||
// map status code 407 (Proxy Authentication Required) to EACCES
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
} elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
|
||||
// map non-2xx status code to ECONNREFUSED
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
}
|
||||
|
||||
// all okay, resolve with stream instance
|
||||
$deferred->resolve($stream);
|
||||
|
||||
// emit remaining incoming as data event
|
||||
$buffer = (string)substr($buffer, $pos + 4);
|
||||
if ($buffer !== '') {
|
||||
$stream->emit('data', array($buffer));
|
||||
$buffer = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// stop buffering when 8 KiB have been read
|
||||
if (isset($buffer[8192])) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy response headers exceed maximum of 8 KiB (EMSGSIZE)',
|
||||
defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
|
||||
$stream->on('error', function (Exception $e) use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy caused a stream error (EIO)',
|
||||
defined('SOCKET_EIO') ? SOCKET_EIO : 5,
|
||||
$e
|
||||
));
|
||||
});
|
||||
|
||||
$stream->on('close', function () use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy was lost while waiting for response (ECONNRESET)',
|
||||
defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104
|
||||
));
|
||||
});
|
||||
|
||||
$stream->write("CONNECT " . $target . " HTTP/1.1\r\nHost: " . $target . "\r\n" . $headers . "\r\n");
|
||||
}, function (Exception $e) use ($deferred, $uri) {
|
||||
$deferred->reject($e = new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy failed (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111,
|
||||
$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);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
2
vendor/clue/mq-react/.github/FUNDING.yml
vendored
2
vendor/clue/mq-react/.github/FUNDING.yml
vendored
@ -1,2 +0,0 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
96
vendor/clue/mq-react/CHANGELOG.md
vendored
96
vendor/clue/mq-react/CHANGELOG.md
vendored
@ -1,96 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 1.6.0 (2023-07-28)
|
||||
|
||||
* Feature: Improve Promise v3 support and use template types.
|
||||
(#41 and #42 by @clue)
|
||||
|
||||
* Feature: Improve PHP 8.2+ support by refactoring queuing logic.
|
||||
(#43 by @clue)
|
||||
|
||||
* Improve test suite, ensure 100% code coverage and report failed assertions.
|
||||
(#37 and #39 by @clue)
|
||||
|
||||
## 1.5.0 (2022-09-30)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Promise v3.
|
||||
(#33 by @clue)
|
||||
|
||||
* Update to use new reactphp/async package instead of clue/reactphp-block.
|
||||
(#34 by @SimonFrings)
|
||||
|
||||
## 1.4.0 (2021-11-15)
|
||||
|
||||
* Feature: Support PHP 8.1, avoid deprecation warning concerning `\Countable::count(...)` return type.
|
||||
(#32 by @bartvanhoutte)
|
||||
|
||||
* Improve documentation and simplify examples by updating to new [default loop](https://reactphp.org/event-loop/#loop).
|
||||
(#27 and #29 by @PaulRotmann and #30 by @SimonFrings)
|
||||
|
||||
* Improve test suite to use GitHub actions for continuous integration (CI).
|
||||
(#28 by @SimonFrings)
|
||||
|
||||
## 1.3.0 (2020-10-16)
|
||||
|
||||
* Enhanced documentation for ReactPHP's new HTTP client and
|
||||
add support / sponsorship info.
|
||||
(#21 and #24 by @clue)
|
||||
|
||||
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
|
||||
Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
|
||||
(#22, #23 and #25 by @SimonFrings)
|
||||
|
||||
## 1.2.0 (2019-12-05)
|
||||
|
||||
* Feature: Add `any()` helper to await first successful fulfillment of operations.
|
||||
(#18 by @clue)
|
||||
|
||||
```php
|
||||
// new: limit concurrency while awaiting any operation to complete
|
||||
$promise = Queue::any(3, $urls, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
|
||||
$promise->then(function (ResponseInterface $response) {
|
||||
echo 'First successful: ' . $response->getStatusCode() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
* Minor documentation improvements (fix syntax issues and typos) and update examples.
|
||||
(#9 and #11 by @clue and #15 by @holtkamp)
|
||||
|
||||
* Improve test suite to test against PHP 7.4 and PHP 7.3, drop legacy HHVM support,
|
||||
update distro on Travis and update project homepage.
|
||||
(#10 and #19 by @clue)
|
||||
|
||||
## 1.1.0 (2018-04-30)
|
||||
|
||||
* Feature: Add `all()` helper to await successful fulfillment of all operations
|
||||
(#8 by @clue)
|
||||
|
||||
```php
|
||||
// new: limit concurrency while awaiting all operations to complete
|
||||
$promise = Queue::all(3, $urls, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
|
||||
$promise->then(function (array $responses) {
|
||||
echo 'All ' . count($responses) . ' successful!' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
* Fix: Implement cancellation forwarding for previously queued operations
|
||||
(#7 by @clue)
|
||||
|
||||
## 1.0.0 (2018-02-26)
|
||||
|
||||
* First stable release, following SemVer
|
||||
|
||||
I'd like to thank [Bergfreunde GmbH](https://www.bergfreunde.de/), a German
|
||||
online retailer for Outdoor Gear & Clothing, for sponsoring the first release! 🎉
|
||||
Thanks to sponsors like this, who understand the importance of open source
|
||||
development, I can justify spending time and focus on open source development
|
||||
instead of traditional paid work.
|
||||
|
||||
> Did you know that I offer custom development services and issuing invoices for
|
||||
sponsorships of releases and for contributions? Contact me (@clue) for details.
|
21
vendor/clue/mq-react/LICENSE
vendored
21
vendor/clue/mq-react/LICENSE
vendored
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Christian Lück
|
||||
|
||||
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.
|
532
vendor/clue/mq-react/README.md
vendored
532
vendor/clue/mq-react/README.md
vendored
@ -1,532 +0,0 @@
|
||||
# clue/reactphp-mq
|
||||
|
||||
[](https://github.com/clue/reactphp-mq/actions)
|
||||
[](#tests)
|
||||
[](https://packagist.org/packages/clue/mq-react)
|
||||
|
||||
Mini Queue, the lightweight in-memory message queue to concurrently do many (but not too many) things at once,
|
||||
built on top of [ReactPHP](https://reactphp.org/).
|
||||
|
||||
Let's say you crawl a page and find that you need to send 100 HTTP requests to
|
||||
following pages which each takes `0.2s`. You can either send them all
|
||||
sequentially (taking around `20s`) or you can use
|
||||
[ReactPHP](https://reactphp.org) to concurrently request all your pages at the
|
||||
same time. This works perfectly fine for a small number of operations, but
|
||||
sending an excessive number of requests can either take up all resources on your
|
||||
side or may get you banned by the remote side as it sees an unreasonable number
|
||||
of requests from your side.
|
||||
Instead, you can use this library to effectively rate limit your operations and
|
||||
queue excessives ones so that not too many operations are processed at once.
|
||||
This library provides a simple API that is easy to use in order to manage any
|
||||
kind of async operation without having to mess with most of the low-level details.
|
||||
You can use this to throttle multiple HTTP requests, database queries or pretty
|
||||
much any API that already uses Promises.
|
||||
|
||||
* **Async execution of operations** -
|
||||
Process any number of async operations and choose how many should be handled
|
||||
concurrently and how many operations can be queued in-memory. Process their
|
||||
results as soon as responses come in.
|
||||
The Promise-based design provides a *sane* interface to working with out of order results.
|
||||
* **Lightweight, SOLID design** -
|
||||
Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
|
||||
and does not get in your way.
|
||||
Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
|
||||
* **Good test coverage** -
|
||||
Comes with an automated tests suite and is regularly tested in the *real world*.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Support us](#support-us)
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [Queue](#queue)
|
||||
* [Promises](#promises)
|
||||
* [Cancellation](#cancellation)
|
||||
* [Timeout](#timeout)
|
||||
* [all()](#all)
|
||||
* [any()](#any)
|
||||
* [Blocking](#blocking)
|
||||
* [Install](#install)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Support us
|
||||
|
||||
We invest a lot of time developing, maintaining and updating our awesome
|
||||
open-source projects. You can help us sustain this high-quality of our work by
|
||||
[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
|
||||
numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
|
||||
for details.
|
||||
|
||||
Let's take these projects to the next level together! 🚀
|
||||
|
||||
## Quickstart example
|
||||
|
||||
Once [installed](#install), you can use the following code to access an
|
||||
HTTP webserver and send a large number of HTTP GET requests:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
// load a huge array of URLs to fetch
|
||||
$urls = file('urls.txt');
|
||||
|
||||
// each job should use the browser to GET a certain URL
|
||||
// limit number of concurrent jobs here
|
||||
$q = new Clue\React\Mq\Queue(3, null, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
|
||||
foreach ($urls as $url) {
|
||||
$q($url)->then(function (Psr\Http\Message\ResponseInterface $response) use ($url) {
|
||||
echo $url . ': ' . $response->getBody()->getSize() . ' bytes' . PHP_EOL;
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Usage
|
||||
|
||||
### Queue
|
||||
|
||||
The `Queue` is responsible for managing your operations and ensuring not too
|
||||
many operations are executed at once. It's a very simple and lightweight
|
||||
in-memory implementation of the
|
||||
[leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket#As_a_queue) algorithm.
|
||||
|
||||
This means that you control how many operations can be executed concurrently.
|
||||
If you add a job to the queue and it still below the limit, it will be executed
|
||||
immediately. If you keep adding new jobs to the queue and its concurrency limit
|
||||
is reached, it will not start a new operation and instead queue this for future
|
||||
execution. Once one of the pending operations complete, it will pick the next
|
||||
job from the queue and execute this operation.
|
||||
|
||||
The `new Queue(int $concurrency, ?int $limit, callable(mixed):PromiseInterface<T> $handler)` call
|
||||
can be used to create a new queue instance.
|
||||
You can create any number of queues, for example when you want to apply
|
||||
different limits to different kinds of operations.
|
||||
|
||||
The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
on your particular use case. It's common to limit concurrency to a rather
|
||||
small value, as doing more than a dozen of things at once may easily
|
||||
overwhelm the receiving side.
|
||||
|
||||
The `$limit` parameter sets a new hard limit on how many jobs may be
|
||||
outstanding (kept in memory) at once. Depending on your particular use
|
||||
case, it's usually safe to keep a few hundreds or thousands of jobs in
|
||||
memory. If you do not want to apply an upper limit, you can pass a `null`
|
||||
value which is semantically more meaningful than passing a big number.
|
||||
|
||||
```php
|
||||
// handle up to 10 jobs concurrently, but keep no more than 1000 in memory
|
||||
$q = new Queue(10, 1000, $handler);
|
||||
```
|
||||
|
||||
```php
|
||||
// handle up to 10 jobs concurrently, do not limit queue size
|
||||
$q = new Queue(10, null, $handler);
|
||||
```
|
||||
|
||||
```php
|
||||
// handle up to 10 jobs concurrently, reject all further jobs
|
||||
$q = new Queue(10, 10, $handler);
|
||||
```
|
||||
|
||||
The `$handler` parameter must be a valid callable that accepts your job
|
||||
parameters, invokes the appropriate operation and returns a Promise as a
|
||||
placeholder for its future result.
|
||||
|
||||
```php
|
||||
// using a Closure as handler is usually recommended
|
||||
$q = new Queue(10, null, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
```
|
||||
|
||||
```php
|
||||
// accepts any callable, so PHP's array notation is also supported
|
||||
$q = new Queue(10, null, array($browser, 'get'));
|
||||
```
|
||||
|
||||
#### Promises
|
||||
|
||||
This library works under the assumption that you want to concurrently handle
|
||||
async operations that use a [Promise](https://github.com/reactphp/promise)-based API.
|
||||
|
||||
The demonstration purposes, the examples in this documentation use
|
||||
[ReactPHP's async HTTP client](https://github.com/reactphp/http#client-usage), but you
|
||||
may use any Promise-based API with this project. Its API can be used like this:
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
$promise = $browser->get($url);
|
||||
```
|
||||
|
||||
If you wrap this in a `Queue` instance as given above, this code will look
|
||||
like this:
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
$q = new Queue(10, null, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
|
||||
$promise = $q($url);
|
||||
```
|
||||
|
||||
The `$q` instance is invokable, so that invoking `$q(...$args)` will
|
||||
actually be forwarded as `$browser->get(...$args)` as given in the
|
||||
`$handler` argument when concurrency is still below limits.
|
||||
|
||||
Each operation is expected to be async (non-blocking), so you may actually
|
||||
invoke multiple operations concurrently (send multiple requests in parallel).
|
||||
The `$handler` is responsible for responding to each request with a resolution
|
||||
value, the order is not guaranteed.
|
||||
These operations use a [Promise](https://github.com/reactphp/promise)-based
|
||||
interface that makes it easy to react to when an operation is completed (i.e.
|
||||
either successfully fulfilled or rejected with an error):
|
||||
|
||||
```php
|
||||
$promise->then(
|
||||
function ($result) {
|
||||
var_dump('Result received', $result);
|
||||
},
|
||||
function (Exception $error) {
|
||||
var_dump('There was an error', $error->getMessage());
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Each operation may take some time to complete, but due to its async nature you
|
||||
can actually start any number of (queued) operations. Once the concurrency limit
|
||||
is reached, this invocation will simply be queued and this will return a pending
|
||||
promise which will start the actual operation once another operation is
|
||||
completed. This means that this is handled entirely transparently and you do not
|
||||
need to worry about this concurrency limit yourself.
|
||||
|
||||
If this looks strange to you, you can also use the more traditional
|
||||
[blocking API](#blocking).
|
||||
|
||||
#### Cancellation
|
||||
|
||||
The returned Promise is implemented in such a way that it can be cancelled
|
||||
when it is still pending.
|
||||
Cancelling a pending operation will invoke its cancellation handler which is
|
||||
responsible for rejecting its value with an Exception and cleaning up any
|
||||
underlying resources.
|
||||
|
||||
```php
|
||||
$promise = $q($url);
|
||||
|
||||
Loop::addTimer(2.0, function () use ($promise) {
|
||||
$promise->cancel();
|
||||
});
|
||||
```
|
||||
|
||||
Similarly, cancelling an operation that is queued and has not yet been started
|
||||
will be rejected without ever starting the operation.
|
||||
|
||||
#### Timeout
|
||||
|
||||
By default, this library does not limit how long a single operation can take,
|
||||
so that the resulting promise may stay pending for a long time.
|
||||
Many use cases involve some kind of "timeout" logic so that an operation is
|
||||
cancelled after a certain threshold is reached.
|
||||
|
||||
You can simply use [cancellation](#cancellation) as in the previous chapter or
|
||||
you may want to look into using [react/promise-timer](https://github.com/reactphp/promise-timer)
|
||||
which helps taking care of this through a simple API.
|
||||
|
||||
The resulting code with timeouts applied look something like this:
|
||||
|
||||
```php
|
||||
use React\Promise\Timer;
|
||||
|
||||
$q = new Queue(10, null, function ($uri) use ($browser) {
|
||||
return Timer\timeout($browser->get($uri), 2.0);
|
||||
});
|
||||
|
||||
$promise = $q($uri);
|
||||
```
|
||||
|
||||
The resulting promise can be consumed as usual and the above code will ensure
|
||||
that execution of this operation can not take longer than the given timeout
|
||||
(i.e. after it is actually started).
|
||||
In particular, note how this differs from applying a timeout to the resulting
|
||||
promise. The following code will ensure that the total time for queuing and
|
||||
executing this operation can not take longer than the given timeout:
|
||||
|
||||
```php
|
||||
// usually not recommended
|
||||
$promise = Timer\timeout($q($url), 2.0);
|
||||
```
|
||||
|
||||
Please refer to [react/promise-timer](https://github.com/reactphp/promise-timer)
|
||||
for more details.
|
||||
|
||||
#### all()
|
||||
|
||||
The static `all(int $concurrency, array<TKey,TIn> $jobs, callable(TIn):PromiseInterface<TOut> $handler): PromiseInterface<array<TKey,TOut>>` method can be used to
|
||||
concurrently process all given jobs through the given `$handler`.
|
||||
|
||||
This is a convenience method which uses the `Queue` internally to
|
||||
schedule all jobs while limiting concurrency to ensure no more than
|
||||
`$concurrency` jobs ever run at once. It will return a promise which
|
||||
resolves with the results of all jobs on success.
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
$promise = Queue::all(3, $urls, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
|
||||
$promise->then(function (array $responses) {
|
||||
echo 'All ' . count($responses) . ' successful!' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
If either of the jobs fail, it will reject the resulting promise and will
|
||||
try to cancel all outstanding jobs. Similarly, calling `cancel()` on the
|
||||
resulting promise will try to cancel all outstanding jobs. See
|
||||
[promises](#promises) and [cancellation](#cancellation) for details.
|
||||
|
||||
The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
on your particular use case. It's common to limit concurrency to a rather
|
||||
small value, as doing more than a dozen of things at once may easily
|
||||
overwhelm the receiving side. Using a `1` value will ensure that all jobs
|
||||
are processed one after another, effectively creating a "waterfall" of
|
||||
jobs. Using a value less than 1 will reject with an
|
||||
`InvalidArgumentException` without processing any jobs.
|
||||
|
||||
```php
|
||||
// handle up to 10 jobs concurrently
|
||||
$promise = Queue::all(10, $jobs, $handler);
|
||||
```
|
||||
|
||||
```php
|
||||
// handle each job after another without concurrency (waterfall)
|
||||
$promise = Queue::all(1, $jobs, $handler);
|
||||
```
|
||||
|
||||
The `$jobs` parameter must be an array with all jobs to process. Each
|
||||
value in this array will be passed to the `$handler` to start one job.
|
||||
The array keys will be preserved in the resulting array, while the array
|
||||
values will be replaced with the job results as returned by the
|
||||
`$handler`. If this array is empty, this method will resolve with an
|
||||
empty array without processing any jobs.
|
||||
|
||||
The `$handler` parameter must be a valid callable that accepts your job
|
||||
parameters, invokes the appropriate operation and returns a Promise as a
|
||||
placeholder for its future result. If the given argument is not a valid
|
||||
callable, this method will reject with an `InvalidArgumentException`
|
||||
without processing any jobs.
|
||||
|
||||
```php
|
||||
// using a Closure as handler is usually recommended
|
||||
$promise = Queue::all(10, $jobs, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
```
|
||||
|
||||
```php
|
||||
// accepts any callable, so PHP's array notation is also supported
|
||||
$promise = Queue::all(10, $jobs, array($browser, 'get'));
|
||||
```
|
||||
|
||||
> Keep in mind that returning an array of response messages means that
|
||||
the whole response body has to be kept in memory.
|
||||
|
||||
#### any()
|
||||
|
||||
The static `any(int $concurrency, array<TKey,TIn> $jobs, callable(TIn):Promise<TOut> $handler): PromiseInterface<TOut>` method can be used to
|
||||
concurrently process the given jobs through the given `$handler` and
|
||||
resolve with first resolution value.
|
||||
|
||||
This is a convenience method which uses the `Queue` internally to
|
||||
schedule all jobs while limiting concurrency to ensure no more than
|
||||
`$concurrency` jobs ever run at once. It will return a promise which
|
||||
resolves with the result of the first job on success and will then try
|
||||
to `cancel()` all outstanding jobs.
|
||||
|
||||
```php
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
$promise = Queue::any(3, $urls, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
|
||||
$promise->then(function (ResponseInterface $response) {
|
||||
echo 'First response: ' . $response->getBody() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
If all of the jobs fail, it will reject the resulting promise. Similarly,
|
||||
calling `cancel()` on the resulting promise will try to cancel all
|
||||
outstanding jobs. See [promises](#promises) and
|
||||
[cancellation](#cancellation) for details.
|
||||
|
||||
The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
on your particular use case. It's common to limit concurrency to a rather
|
||||
small value, as doing more than a dozen of things at once may easily
|
||||
overwhelm the receiving side. Using a `1` value will ensure that all jobs
|
||||
are processed one after another, effectively creating a "waterfall" of
|
||||
jobs. Using a value less than 1 will reject with an
|
||||
`InvalidArgumentException` without processing any jobs.
|
||||
|
||||
```php
|
||||
// handle up to 10 jobs concurrently
|
||||
$promise = Queue::any(10, $jobs, $handler);
|
||||
```
|
||||
|
||||
```php
|
||||
// handle each job after another without concurrency (waterfall)
|
||||
$promise = Queue::any(1, $jobs, $handler);
|
||||
```
|
||||
|
||||
The `$jobs` parameter must be an array with all jobs to process. Each
|
||||
value in this array will be passed to the `$handler` to start one job.
|
||||
The array keys have no effect, the promise will simply resolve with the
|
||||
job results of the first successful job as returned by the `$handler`.
|
||||
If this array is empty, this method will reject without processing any
|
||||
jobs.
|
||||
|
||||
The `$handler` parameter must be a valid callable that accepts your job
|
||||
parameters, invokes the appropriate operation and returns a Promise as a
|
||||
placeholder for its future result. If the given argument is not a valid
|
||||
callable, this method will reject with an `InvalidArgumentExceptionn`
|
||||
without processing any jobs.
|
||||
|
||||
```php
|
||||
// using a Closure as handler is usually recommended
|
||||
$promise = Queue::any(10, $jobs, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
```
|
||||
|
||||
```php
|
||||
// accepts any callable, so PHP's array notation is also supported
|
||||
$promise = Queue::any(10, $jobs, array($browser, 'get'));
|
||||
```
|
||||
|
||||
#### Blocking
|
||||
|
||||
As stated above, this library provides you a powerful, async API by default.
|
||||
|
||||
You can also integrate this into your traditional, blocking environment by using
|
||||
[reactphp/async](https://github.com/reactphp/async). This allows you to simply
|
||||
await async HTTP requests like this:
|
||||
|
||||
```php
|
||||
use function React\Async\await;
|
||||
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
$promise = Queue::all(3, $urls, function ($url) use ($browser) {
|
||||
return $browser->get($url);
|
||||
});
|
||||
|
||||
try {
|
||||
$responses = await($promise);
|
||||
// responses successfully received
|
||||
} catch (Exception $e) {
|
||||
// an error occured while performing the requests
|
||||
}
|
||||
```
|
||||
|
||||
Similarly, you can also wrap this in a function to provide a simple API and hide
|
||||
all the async details from the outside:
|
||||
|
||||
```php
|
||||
use function React\Async\await;
|
||||
|
||||
/**
|
||||
* Concurrently downloads all the given URIs
|
||||
*
|
||||
* @param string[] $uris list of URIs to download
|
||||
* @return ResponseInterface[] map with a response object for each URI
|
||||
* @throws Exception if any of the URIs can not be downloaded
|
||||
*/
|
||||
function download(array $uris)
|
||||
{
|
||||
$browser = new React\Http\Browser();
|
||||
|
||||
$promise = Queue::all(3, $uris, function ($uri) use ($browser) {
|
||||
return $browser->get($uri);
|
||||
});
|
||||
|
||||
return await($promise);
|
||||
}
|
||||
```
|
||||
|
||||
This is made possible thanks to fibers available in PHP 8.1+ and our
|
||||
compatibility API that also works on all supported PHP versions.
|
||||
Please refer to [reactphp/async](https://github.com/reactphp/async#readme) for more details.
|
||||
|
||||
> Keep in mind that returning an array of response messages means that the whole
|
||||
response body has to be kept in memory.
|
||||
|
||||
## 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 clue/mq-react:^1.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+.
|
||||
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 is set up to always ensure 100% code coverage across all
|
||||
supported environments. If you have the Xdebug extension installed, you can also
|
||||
generate a code coverage report locally like this:
|
||||
|
||||
```bash
|
||||
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is released under the permissive [MIT license](LICENSE).
|
||||
|
||||
I'd like to thank [Bergfreunde GmbH](https://www.bergfreunde.de/), a German
|
||||
online retailer for Outdoor Gear & Clothing, for sponsoring the first release! 🎉
|
||||
Thanks to sponsors like this, who understand the importance of open source
|
||||
development, I can justify spending time and focus on open source development
|
||||
instead of traditional paid work.
|
||||
|
||||
> Did you know that I offer custom development services and issuing invoices for
|
||||
sponsorships of releases and for contributions? Contact me (@clue) for details.
|
33
vendor/clue/mq-react/composer.json
vendored
33
vendor/clue/mq-react/composer.json
vendored
@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "clue/mq-react",
|
||||
"description": "Mini Queue, the lightweight in-memory message queue to concurrently do many (but not too many) things at once, built on top of ReactPHP",
|
||||
"keywords": ["Message Queue", "Mini Queue", "job", "message", "worker", "queue", "rate limit", "throttle", "concurrency", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-mq",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": "^3 || ^2.2.1 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4 || ^3 || ^2",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/http": "^1.8"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Clue\\React\\Mq\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Clue\\Tests\\React\\Mq\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
465
vendor/clue/mq-react/src/Queue.php
vendored
465
vendor/clue/mq-react/src/Queue.php
vendored
@ -1,465 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Mq;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* The `Queue` is responsible for managing your operations and ensuring not too
|
||||
* many operations are executed at once. It's a very simple and lightweight
|
||||
* in-memory implementation of the
|
||||
* [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket#As_a_queue) algorithm.
|
||||
*
|
||||
* This means that you control how many operations can be executed concurrently.
|
||||
* If you add a job to the queue and it still below the limit, it will be executed
|
||||
* immediately. If you keep adding new jobs to the queue and its concurrency limit
|
||||
* is reached, it will not start a new operation and instead queue this for future
|
||||
* execution. Once one of the pending operations complete, it will pick the next
|
||||
* job from the queue and execute this operation.
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
class Queue implements \Countable
|
||||
{
|
||||
private $concurrency;
|
||||
private $limit;
|
||||
private $handler;
|
||||
|
||||
/** @var int<0,max> */
|
||||
private $pending = 0;
|
||||
|
||||
/** @var array<int,\Closure():void> */
|
||||
private $queue = array();
|
||||
|
||||
/**
|
||||
* Concurrently process all given jobs through the given `$handler`.
|
||||
*
|
||||
* This is a convenience method which uses the `Queue` internally to
|
||||
* schedule all jobs while limiting concurrency to ensure no more than
|
||||
* `$concurrency` jobs ever run at once. It will return a promise which
|
||||
* resolves with the results of all jobs on success.
|
||||
*
|
||||
* ```php
|
||||
* $browser = new React\Http\Browser();
|
||||
*
|
||||
* $promise = Queue::all(3, $urls, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
*
|
||||
* $promise->then(function (array $responses) {
|
||||
* echo 'All ' . count($responses) . ' successful!' . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If either of the jobs fail, it will reject the resulting promise and will
|
||||
* try to cancel all outstanding jobs. Similarly, calling `cancel()` on the
|
||||
* resulting promise will try to cancel all outstanding jobs. See
|
||||
* [promises](#promises) and [cancellation](#cancellation) for details.
|
||||
*
|
||||
* The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
* of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
* on your particular use case. It's common to limit concurrency to a rather
|
||||
* small value, as doing more than a dozen of things at once may easily
|
||||
* overwhelm the receiving side. Using a `1` value will ensure that all jobs
|
||||
* are processed one after another, effectively creating a "waterfall" of
|
||||
* jobs. Using a value less than 1 will reject with an
|
||||
* `InvalidArgumentException` without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently
|
||||
* $promise = Queue::all(10, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle each job after another without concurrency (waterfall)
|
||||
* $promise = Queue::all(1, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* The `$jobs` parameter must be an array with all jobs to process. Each
|
||||
* value in this array will be passed to the `$handler` to start one job.
|
||||
* The array keys will be preserved in the resulting array, while the array
|
||||
* values will be replaced with the job results as returned by the
|
||||
* `$handler`. If this array is empty, this method will resolve with an
|
||||
* empty array without processing any jobs.
|
||||
*
|
||||
* The `$handler` parameter must be a valid callable that accepts your job
|
||||
* parameters, invokes the appropriate operation and returns a Promise as a
|
||||
* placeholder for its future result. If the given argument is not a valid
|
||||
* callable, this method will reject with an `InvalidArgumentException`
|
||||
* without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // using a Closure as handler is usually recommended
|
||||
* $promise = Queue::all(10, $jobs, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // accepts any callable, so PHP's array notation is also supported
|
||||
* $promise = Queue::all(10, $jobs, array($browser, 'get'));
|
||||
* ```
|
||||
*
|
||||
* > Keep in mind that returning an array of response messages means that
|
||||
* the whole response body has to be kept in memory.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TIn
|
||||
* @template TOut
|
||||
* @param int $concurrency concurrency soft limit
|
||||
* @param array<TKey,TIn> $jobs
|
||||
* @param callable(TIn):PromiseInterface<TOut> $handler
|
||||
* @return PromiseInterface<array<TKey,TOut>> Returns a Promise which resolves with an array of all resolution values
|
||||
* or rejects when any of the operations reject.
|
||||
*/
|
||||
public static function all($concurrency, array $jobs, $handler)
|
||||
{
|
||||
try {
|
||||
// limit number of concurrent operations
|
||||
$q = new self($concurrency, null, $handler);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// reject if $concurrency or $handler is invalid
|
||||
return Promise\reject($e);
|
||||
}
|
||||
|
||||
// try invoking all operations and automatically queue excessive ones
|
||||
$promises = array_map($q, $jobs);
|
||||
|
||||
return new Promise\Promise(function ($resolve, $reject) use ($promises) {
|
||||
Promise\all($promises)->then($resolve, function ($e) use ($promises, $reject) {
|
||||
// cancel all pending promises if a single promise fails
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// reject with original rejection message
|
||||
$reject($e);
|
||||
});
|
||||
}, function () use ($promises) {
|
||||
// cancel all pending promises on cancellation
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Concurrently process the given jobs through the given `$handler` and
|
||||
* resolve with first resolution value.
|
||||
*
|
||||
* This is a convenience method which uses the `Queue` internally to
|
||||
* schedule all jobs while limiting concurrency to ensure no more than
|
||||
* `$concurrency` jobs ever run at once. It will return a promise which
|
||||
* resolves with the result of the first job on success and will then try
|
||||
* to `cancel()` all outstanding jobs.
|
||||
*
|
||||
* ```php
|
||||
* $browser = new React\Http\Browser();
|
||||
*
|
||||
* $promise = Queue::any(3, $urls, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
*
|
||||
* $promise->then(function (ResponseInterface $response) {
|
||||
* echo 'First response: ' . $response->getBody() . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If all of the jobs fail, it will reject the resulting promise. Similarly,
|
||||
* calling `cancel()` on the resulting promise will try to cancel all
|
||||
* outstanding jobs. See [promises](#promises) and
|
||||
* [cancellation](#cancellation) for details.
|
||||
*
|
||||
* The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
* of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
* on your particular use case. It's common to limit concurrency to a rather
|
||||
* small value, as doing more than a dozen of things at once may easily
|
||||
* overwhelm the receiving side. Using a `1` value will ensure that all jobs
|
||||
* are processed one after another, effectively creating a "waterfall" of
|
||||
* jobs. Using a value less than 1 will reject with an
|
||||
* `InvalidArgumentException` without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently
|
||||
* $promise = Queue::any(10, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle each job after another without concurrency (waterfall)
|
||||
* $promise = Queue::any(1, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* The `$jobs` parameter must be an array with all jobs to process. Each
|
||||
* value in this array will be passed to the `$handler` to start one job.
|
||||
* The array keys have no effect, the promise will simply resolve with the
|
||||
* job results of the first successful job as returned by the `$handler`.
|
||||
* If this array is empty, this method will reject without processing any
|
||||
* jobs.
|
||||
*
|
||||
* The `$handler` parameter must be a valid callable that accepts your job
|
||||
* parameters, invokes the appropriate operation and returns a Promise as a
|
||||
* placeholder for its future result. If the given argument is not a valid
|
||||
* callable, this method will reject with an `InvalidArgumentExceptionn`
|
||||
* without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // using a Closure as handler is usually recommended
|
||||
* $promise = Queue::any(10, $jobs, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // accepts any callable, so PHP's array notation is also supported
|
||||
* $promise = Queue::any(10, $jobs, array($browser, 'get'));
|
||||
* ```
|
||||
*
|
||||
* @template TKey
|
||||
* @template TIn
|
||||
* @template TOut
|
||||
* @param int $concurrency concurrency soft limit
|
||||
* @param array<TKey,TIn> $jobs
|
||||
* @param callable(TIn):PromiseInterface<TOut> $handler
|
||||
* @return PromiseInterface<TOut> Returns a Promise which resolves with a single resolution value
|
||||
* or rejects when all of the operations reject.
|
||||
*/
|
||||
public static function any($concurrency, array $jobs, $handler)
|
||||
{
|
||||
// explicitly reject with empty jobs (https://github.com/reactphp/promise/pull/34)
|
||||
if (!$jobs) {
|
||||
return Promise\reject(new \UnderflowException('No jobs given'));
|
||||
}
|
||||
|
||||
try {
|
||||
// limit number of concurrent operations
|
||||
$q = new self($concurrency, null, $handler);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// reject if $concurrency or $handler is invalid
|
||||
return Promise\reject($e);
|
||||
}
|
||||
|
||||
// try invoking all operations and automatically queue excessive ones
|
||||
$promises = array_map($q, $jobs);
|
||||
|
||||
return new Promise\Promise(function ($resolve, $reject) use ($promises) {
|
||||
Promise\any($promises)->then(function ($result) use ($promises, $resolve) {
|
||||
// cancel all pending promises if a single result is ready
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// resolve with original resolution value
|
||||
$resolve($result);
|
||||
}, $reject);
|
||||
}, function () use ($promises) {
|
||||
// cancel all pending promises on cancellation
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new queue object.
|
||||
*
|
||||
* You can create any number of queues, for example when you want to apply
|
||||
* different limits to different kind of operations.
|
||||
*
|
||||
* The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
* of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
* on your particular use case. It's common to limit concurrency to a rather
|
||||
* small value, as doing more than a dozen of things at once may easily
|
||||
* overwhelm the receiving side.
|
||||
*
|
||||
* The `$limit` parameter sets a new hard limit on how many jobs may be
|
||||
* outstanding (kept in memory) at once. Depending on your particular use
|
||||
* case, it's usually safe to keep a few hundreds or thousands of jobs in
|
||||
* memory. If you do not want to apply an upper limit, you can pass a `null`
|
||||
* value which is semantically more meaningful than passing a big number.
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, but keep no more than 1000 in memory
|
||||
* $q = new Queue(10, 1000, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, do not limit queue size
|
||||
* $q = new Queue(10, null, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, reject all further jobs
|
||||
* $q = new Queue(10, 10, $handler);
|
||||
* ```
|
||||
*
|
||||
* The `$handler` parameter must be a valid callable that accepts your job
|
||||
* parameters, invokes the appropriate operation and returns a Promise as a
|
||||
* placeholder for its future result.
|
||||
*
|
||||
* ```php
|
||||
* // using a Closure as handler is usually recommended
|
||||
* $q = new Queue(10, null, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // PHP's array callable as handler is also supported
|
||||
* $q = new Queue(10, null, array($browser, 'get'));
|
||||
* ```
|
||||
*
|
||||
* @param int $concurrency concurrency soft limit
|
||||
* @param int|null $limit queue hard limit or NULL=unlimited
|
||||
* @param callable(mixed):PromiseInterface<T> $handler
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($concurrency, $limit, $handler)
|
||||
{
|
||||
if ($concurrency < 1 || ($limit !== null && ($limit < 1 || $concurrency > $limit))) {
|
||||
throw new \InvalidArgumentException('Invalid limit given');
|
||||
}
|
||||
if (!is_callable($handler)) {
|
||||
throw new \InvalidArgumentException('Invalid handler given');
|
||||
}
|
||||
|
||||
$this->concurrency = $concurrency;
|
||||
$this->limit = $limit;
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Queue instance is invokable, so that invoking `$q(...$args)` will
|
||||
* actually be forwarded as `$handler(...$args)` as given in the
|
||||
* `$handler` argument when concurrency is still below limits.
|
||||
*
|
||||
* Each operation may take some time to complete, but due to its async nature you
|
||||
* can actually start any number of (queued) operations. Once the concurrency limit
|
||||
* is reached, this invocation will simply be queued and this will return a pending
|
||||
* promise which will start the actual operation once another operation is
|
||||
* completed. This means that this is handled entirely transparently and you do not
|
||||
* need to worry about this concurrency limit yourself.
|
||||
*
|
||||
* @return PromiseInterface<T>
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
// happy path: simply invoke handler if we're below concurrency limit
|
||||
if ($this->pending < $this->concurrency) {
|
||||
++$this->pending;
|
||||
|
||||
// invoke handler and await its resolution before invoking next queued job
|
||||
return $this->await(
|
||||
call_user_func_array($this->handler, func_get_args())
|
||||
);
|
||||
}
|
||||
|
||||
// we're currently above concurrency limit, make sure we do not exceed maximum queue limit
|
||||
if ($this->limit !== null && $this->count() >= $this->limit) {
|
||||
return Promise\reject(new \OverflowException('Maximum queue limit of ' . $this->limit . ' exceeded'));
|
||||
}
|
||||
|
||||
// if we reach this point, then this job will need to be queued
|
||||
// get next queue position
|
||||
$queue =& $this->queue;
|
||||
$queue[] = null;
|
||||
end($queue);
|
||||
$id = key($queue);
|
||||
assert(is_int($id));
|
||||
|
||||
/** @var ?PromiseInterface<T> $pending */
|
||||
$pending = null;
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use (&$queue, $id, &$pending) {
|
||||
// forward cancellation to pending operation if it is currently executing
|
||||
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
|
||||
$pending->cancel();
|
||||
}
|
||||
$pending = null;
|
||||
|
||||
if (isset($queue[$id])) {
|
||||
// queued promise cancelled before its handler is invoked
|
||||
// remove from queue and reject explicitly
|
||||
unset($queue[$id]);
|
||||
$reject(new \RuntimeException('Cancelled queued job before processing started'));
|
||||
}
|
||||
});
|
||||
|
||||
// queue job to process if number of pending jobs is below concurrency limit again
|
||||
$handler = $this->handler; // PHP 5.4+
|
||||
$args = func_get_args();
|
||||
$that = $this; // PHP 5.4+
|
||||
$queue[$id] = function () use ($handler, $args, $deferred, &$pending, $that) {
|
||||
$pending = \call_user_func_array($handler, $args);
|
||||
|
||||
$that->await($pending)->then(
|
||||
function ($result) use ($deferred, &$pending) {
|
||||
$pending = null;
|
||||
$deferred->resolve($result);
|
||||
},
|
||||
function ($e) use ($deferred, &$pending) {
|
||||
$pending = null;
|
||||
$deferred->reject($e);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count()
|
||||
{
|
||||
return $this->pending + count($this->queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param PromiseInterface<T> $promise
|
||||
*/
|
||||
public function await(PromiseInterface $promise)
|
||||
{
|
||||
$that = $this; // PHP 5.4+
|
||||
|
||||
return $promise->then(function ($result) use ($that) {
|
||||
$that->processQueue();
|
||||
|
||||
return $result;
|
||||
}, function ($error) use ($that) {
|
||||
$that->processQueue();
|
||||
|
||||
return Promise\reject($error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function processQueue()
|
||||
{
|
||||
// skip if we're still above concurrency limit or there's no queued job waiting
|
||||
if (--$this->pending >= $this->concurrency || !$this->queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$next = reset($this->queue);
|
||||
assert($next instanceof \Closure);
|
||||
unset($this->queue[key($this->queue)]);
|
||||
|
||||
// once number of pending jobs is below concurrency limit again:
|
||||
// await this situation, invoke handler and await its resolution before invoking next queued job
|
||||
++$this->pending;
|
||||
|
||||
// invoke handler and await its resolution before invoking next queued job
|
||||
$next();
|
||||
}
|
||||
}
|
2
vendor/clue/redis-protocol/.gitignore
vendored
2
vendor/clue/redis-protocol/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/vendor
|
||||
/composer.lock
|
8
vendor/clue/redis-protocol/.travis.yml
vendored
8
vendor/clue/redis-protocol/.travis.yml
vendored
@ -1,8 +0,0 @@
|
||||
language: php
|
||||
php:
|
||||
- 5.4
|
||||
- 5.3
|
||||
before_script:
|
||||
- composer install --dev --prefer-source --no-interaction
|
||||
script:
|
||||
- phpunit --coverage-text
|
50
vendor/clue/redis-protocol/CHANGELOG.md
vendored
50
vendor/clue/redis-protocol/CHANGELOG.md
vendored
@ -1,50 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 0.3.1 (2017-06-06)
|
||||
|
||||
* Fix: Fix server-side parsing of legacy inline protocol when multiple requests are processed at once
|
||||
(#12 by @kelunik and #13 by @clue)
|
||||
|
||||
## 0.3.0 (2014-01-27)
|
||||
|
||||
* Feature: Add dedicated and faster `RequestParser` that also support the old
|
||||
inline request protocol.
|
||||
* Feature: Message serialization can now be handled directly by the Serializer
|
||||
again without having to construct the appropriate model first.
|
||||
* BC break: The `Factory` now has two distinct methods to create parsers:
|
||||
* `createResponseParser()` for a client-side library
|
||||
* `createRequestParser()` for a server-side library / testing framework
|
||||
* BC break: Simplified parser API, now `pushIncoming()` returns an array of all
|
||||
parsed message models.
|
||||
* BC break: The signature for getting a serialized message from a model was
|
||||
changed and now requires a Serializer passed:
|
||||
```php
|
||||
ModelInterface::getMessageSerialized($serializer)
|
||||
```
|
||||
* Many, many performance improvements
|
||||
|
||||
## 0.2.0 (2014-01-21)
|
||||
|
||||
* Re-organize the whole API into dedicated
|
||||
* `Parser` (protocol reader) and
|
||||
* `Serializer` (protocol writer) sub-namespaces. (#4)
|
||||
|
||||
* Use of the factory has now been unified:
|
||||
|
||||
```php
|
||||
$factory = new Clue\Redis\Protocol\Factory();
|
||||
$parser = $factory->createParser();
|
||||
$serializer = $factory->createSerializer();
|
||||
```
|
||||
|
||||
* Add a dedicated `Model` for each type of reply. Among others, this now allows
|
||||
you to distinguish a single line `StatusReply` from a binary-safe `BulkReply`. (#2)
|
||||
|
||||
* Fix parsing binary values and do not trip over trailing/leading whitespace. (#4)
|
||||
|
||||
* Improve parser and serializer performance by up to 20%. (#4)
|
||||
|
||||
## 0.1.0 (2013-09-10)
|
||||
|
||||
* First tagged release
|
||||
|
139
vendor/clue/redis-protocol/README.md
vendored
139
vendor/clue/redis-protocol/README.md
vendored
@ -1,139 +0,0 @@
|
||||
# clue/redis-protocol [](https://travis-ci.org/clue/php-redis-protocol)
|
||||
|
||||
A streaming redis protocol parser and serializer written in PHP
|
||||
|
||||
This parser and serializer implementation allows you to parse redis protocol
|
||||
messages into native PHP values and vice-versa. This is usually needed by a
|
||||
redis client implementation which also handles the connection socket.
|
||||
|
||||
To re-iterate: This is *not* a redis client implementation. This is a protocol
|
||||
implementation that is usually used by a redis client implementation. If you're
|
||||
looking for an easy way to build your own client implementation, then this is
|
||||
for you. If you merely want to connect to a redis server and issue some
|
||||
commands, you're probably better off using one of the existing client
|
||||
implementations.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [Factory](#factory)
|
||||
* [Parser](#parser)
|
||||
* [Model](#model)
|
||||
* [Serializer](#serializer)
|
||||
* [Install](#install)
|
||||
* [License](#license)
|
||||
|
||||
## Quickstart example
|
||||
|
||||
```php
|
||||
use Clue\Redis\Protocol;
|
||||
|
||||
$factory = new Protocol\Factory();
|
||||
$parser = $factory->createResponseParser();
|
||||
$serializer = $factory->createSerializer();
|
||||
|
||||
$fp = fsockopen('tcp://localhost', 6379);
|
||||
fwrite($fp, $serializer->getRequestMessage('SET', array('name', 'value')));
|
||||
fwrite($fp, $serializer->getRequestMessage('GET', array('name')));
|
||||
|
||||
// the commands are pipelined, so this may parse multiple responses
|
||||
$models = $parser->pushIncoming(fread($fp, 4096));
|
||||
|
||||
$reply1 = array_shift($models);
|
||||
$reply2 = array_shift($models);
|
||||
|
||||
var_dump($reply1->getValueNative()); // string(2) "OK"
|
||||
var_dump($reply2->getValueNative()); // string(5) "value"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Factory
|
||||
|
||||
The factory helps with instantiating the *right* parser and serializer.
|
||||
Eventually the *best* available implementation will be chosen depending on your
|
||||
installed extensions. You're also free to instantiate them directly, but this
|
||||
will lock you down on a given implementation (which could be okay depending on
|
||||
your use-case).
|
||||
|
||||
### Parser
|
||||
|
||||
The library includes a streaming redis protocol parser. As such, it can safely
|
||||
parse redis protocol messages and work with an incomplete data stream. For this,
|
||||
each included parser implements a single method
|
||||
`ParserInterface::pushIncoming($chunk)`.
|
||||
|
||||
* The `ResponseParser` is what most redis client implementation would want to
|
||||
use in order to parse incoming response messages from a redis server instance.
|
||||
* The `RequestParser` can be used to test messages coming from a redis client or
|
||||
even to implement a redis server.
|
||||
* The `MessageBuffer` decorates either of the available parsers and merely
|
||||
offers some helper methods in order to work with single messages:
|
||||
* `hasIncomingModel()` to check if there's a complete message in the pipeline
|
||||
* `popIncomingModel()` to extract a complete message from the incoming queue.
|
||||
|
||||
### Model
|
||||
|
||||
Each message (response as well as request) is represented by a model
|
||||
implementing the `ModelInterface` that has two methods:
|
||||
|
||||
* `getValueNative()` returns the wrapped value.
|
||||
* `getMessageSerialized($serializer)` returns the serialized protocol messages
|
||||
that will be sent over the wire.
|
||||
|
||||
These models are very lightweight and add little overhead. They help keeping the
|
||||
code organized and also provide a means to distinguish a single line
|
||||
`StatusReply` from a binary-safe `BulkReply`.
|
||||
|
||||
The parser always returns models. Models can also be instantiated directly:
|
||||
|
||||
```php
|
||||
$model = new Model\IntegerReply(123);
|
||||
var_dump($model->getValueNative()); // int(123)
|
||||
var_dump($model->getMessageSerialized($serializer)); // string(6) ":123\r\n"
|
||||
```
|
||||
|
||||
### Serializer
|
||||
|
||||
The serializer is responsible for creating serialized messages and the
|
||||
corresponing message models to be sent across the wire.
|
||||
|
||||
```php
|
||||
$message = $serializer->getRequestMessage('ping');
|
||||
var_dump($message); // string(14) "$1\r\n*4\r\nping\r\n"
|
||||
|
||||
$message = $serializer->getRequestMessage('set', array('key', 'value'));
|
||||
var_dump($message); // string(33) "$3\r\n*3\r\nset\r\n*3\r\nkey\r\n*5\r\nvalue\r\n"
|
||||
|
||||
$model = $serializer->createRequestModel('get', array('key'));
|
||||
var_dump($model->getCommand()); // string(3) "get"
|
||||
var_dump($model->getArgs()); // array(1) { string(3) "key" }
|
||||
var_dump($model->getValueNative()); // array(2) { string(3) "GET", string(3) "key" }
|
||||
|
||||
$model = $serializer->createReplyModel(array('mixed', 12, array('value')));
|
||||
assert($model implement Model\MultiBulkReply);
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
It's very unlikely you'll want to use this protocol parser standalone.
|
||||
It should be added as a dependency to your redis client implementation instead.
|
||||
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 clue/redis-protocol:^0.3.1
|
||||
```
|
||||
|
||||
More details and upgrade guides can be found in the [CHANGELOG](CHANGELOG.md).
|
||||
|
||||
## License
|
||||
|
||||
Its parser and serializer originally used to be based on
|
||||
[jpd/redisent](https://github.com/jdp/redisent), which is released under the ISC
|
||||
license, copyright (c) 2009-2012 Justin Poliey <justin@getglue.com>.
|
||||
|
||||
Other than that, this library is MIT licensed.
|
19
vendor/clue/redis-protocol/composer.json
vendored
19
vendor/clue/redis-protocol/composer.json
vendored
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "clue/redis-protocol",
|
||||
"description": "A streaming redis wire protocol parser and serializer implementation in PHP",
|
||||
"keywords": ["streaming", "redis", "protocol", "parser", "serializer"],
|
||||
"homepage": "https://github.com/clue/php-redis-protocol",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Clue\\Redis\\Protocol": "src" }
|
||||
}
|
||||
}
|
22
vendor/clue/redis-protocol/example/client.php
vendored
22
vendor/clue/redis-protocol/example/client.php
vendored
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Clue\Redis\Protocol;
|
||||
|
||||
$factory = new Protocol\Factory();
|
||||
$parser = $factory->createResponseParser();
|
||||
$serializer = $factory->createSerializer();
|
||||
|
||||
$fp = fsockopen('tcp://localhost', 6379);
|
||||
fwrite($fp, $serializer->getRequestMessage('SET', array('name', 'value')));
|
||||
fwrite($fp, $serializer->getRequestMessage('GET', array('name')));
|
||||
|
||||
// the commands are pipelined, so this may parse multiple responses
|
||||
$models = $parser->pushIncoming(fread($fp, 4096));
|
||||
|
||||
$reply1 = array_shift($models);
|
||||
$reply2 = array_shift($models);
|
||||
|
||||
var_dump($reply1->getValueNative()); // string(2) "OK"
|
||||
var_dump($reply2->getValueNative()); // string(5) "value"
|
31
vendor/clue/redis-protocol/example/perf.php
vendored
31
vendor/clue/redis-protocol/example/perf.php
vendored
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\ProtocolBuffer;
|
||||
use Clue\Redis\Protocol\Factory;
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$factory = new Factory();
|
||||
$parser = $factory->createResponseParser();
|
||||
$serializer = $factory->createSerializer();
|
||||
|
||||
$n = isset($argv[1]) ? (int)$argv[1] : 10000; // number of dummy messages to parse
|
||||
$cs = 4096; // pretend we can only read 7 bytes at once. more like 4096/8192 usually
|
||||
|
||||
echo 'benchmarking ' . $n . ' messages (chunksize of ' . $cs .' bytes)' . PHP_EOL;
|
||||
|
||||
$time = microtime(true);
|
||||
|
||||
$stream = '';
|
||||
for ($i = 0; $i < $n; ++$i) {
|
||||
$stream .= $serializer->getRequestMessage('set', array('var' . $i, 'value' . $i));
|
||||
}
|
||||
|
||||
echo round(microtime(true) - $time, 3) . 's for serialization' . PHP_EOL;
|
||||
$time = microtime(true);
|
||||
|
||||
for ($i = 0, $l = strlen($stream); $i < $l; $i += $cs) {
|
||||
$parser->pushIncoming(substr($stream, $i, $cs));
|
||||
}
|
||||
|
||||
echo round(microtime(true) - $time, 3) . 's for parsing' . PHP_EOL;
|
19
vendor/clue/redis-protocol/phpunit.xml.dist
vendored
19
vendor/clue/redis-protocol/phpunit.xml.dist
vendored
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Redis Protocol Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Parser\ResponseParser;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
use Clue\Redis\Protocol\Serializer\RecursiveSerializer;
|
||||
use Clue\Redis\Protocol\Parser\RequestParser;
|
||||
|
||||
/**
|
||||
* Provides factory methods used to instantiate the best available protocol implementation
|
||||
*/
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* instantiate the best available protocol response parser implementation
|
||||
*
|
||||
* This is the parser every redis client implementation should use in order
|
||||
* to parse incoming response messages from a redis server.
|
||||
*
|
||||
* @return ParserInterface
|
||||
*/
|
||||
public function createResponseParser()
|
||||
{
|
||||
return new ResponseParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* instantiate the best available protocol request parser implementation
|
||||
*
|
||||
* This is most useful for a redis server implementation which needs to
|
||||
* process client requests.
|
||||
*
|
||||
* @return ParserInterface
|
||||
*/
|
||||
public function createRequestParser()
|
||||
{
|
||||
return new RequestParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* instantiate the best available protocol serializer implementation
|
||||
*
|
||||
* @return SerializerInterface
|
||||
*/
|
||||
public function createSerializer()
|
||||
{
|
||||
return new RecursiveSerializer();
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class BulkReply implements ModelInterface
|
||||
{
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* create bulk reply (string reply)
|
||||
*
|
||||
* @param string|null $data
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
if ($value !== null) {
|
||||
$value = (string)$value;
|
||||
}
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getBulkMessage($this->value);
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Exception;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
* @link http://redis.io/topics/protocol#status-reply
|
||||
*/
|
||||
class ErrorReply extends Exception implements ModelInterface
|
||||
{
|
||||
/**
|
||||
* create error status reply (single line error message)
|
||||
*
|
||||
* @param string|ErrorReplyException $message
|
||||
* @return string
|
||||
*/
|
||||
public function __construct($message, $code = 0, $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->getMessage();
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getErrorMessage($this->getMessage());
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class IntegerReply implements ModelInterface
|
||||
{
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* create integer reply
|
||||
*
|
||||
* @param int $data
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = (int)$value;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getIntegerMessage($this->value);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
interface ModelInterface
|
||||
{
|
||||
/**
|
||||
* Returns value of this model as a native representation for PHP
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValueNative();
|
||||
|
||||
/**
|
||||
* Returns the serialized representation of this protocol message
|
||||
*
|
||||
* @param SerializerInterface $serializer;
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageSerialized(SerializerInterface $serializer);
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class MultiBulkReply implements ModelInterface
|
||||
{
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* create multi bulk reply (an array of other replies, usually bulk replies)
|
||||
*
|
||||
* @param array|null $data
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $data = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
if ($this->data === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
foreach ($this->data as $one) {
|
||||
if ($one instanceof ModelInterface) {
|
||||
$ret []= $one->getValueNative();
|
||||
} else {
|
||||
$ret []= $one;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getMultiBulkMessage($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this model represents a valid unified request protocol message
|
||||
*
|
||||
* The new unified protocol was introduced in Redis 1.2, but it became the
|
||||
* standard way for talking with the Redis server in Redis 2.0. The unified
|
||||
* request protocol is what Redis already uses in replies in order to send
|
||||
* list of items to clients, and is called a Multi Bulk Reply.
|
||||
*
|
||||
* @return boolean
|
||||
* @link http://redis.io/topics/protocol
|
||||
*/
|
||||
public function isRequest()
|
||||
{
|
||||
if (!$this->data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->data as $one) {
|
||||
if (!($one instanceof BulkReply) && !is_string($one)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRequestModel()
|
||||
{
|
||||
if (!$this->data) {
|
||||
throw new UnexpectedValueException('Null-multi-bulk message can not be represented as a request, must contain string/bulk values');
|
||||
}
|
||||
|
||||
$command = null;
|
||||
$args = array();
|
||||
|
||||
foreach ($this->data as $one) {
|
||||
if ($one instanceof BulkReply) {
|
||||
$one = $one->getValueNative();
|
||||
} elseif (!is_string($one)) {
|
||||
throw new UnexpectedValueException('Message can not be represented as a request, must only contain string/bulk values');
|
||||
}
|
||||
|
||||
if ($command === null) {
|
||||
$command = $one;
|
||||
} else {
|
||||
$args []= $one;
|
||||
}
|
||||
}
|
||||
|
||||
return new Request($command, $args);
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class Request implements ModelInterface
|
||||
{
|
||||
private $command;
|
||||
private $args;
|
||||
|
||||
public function __construct($command, array $args = array())
|
||||
{
|
||||
$this->command = $command;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function getArgs()
|
||||
{
|
||||
return $this->args;
|
||||
}
|
||||
|
||||
public function getReplyModel()
|
||||
{
|
||||
$models = array(new BulkReply($this->command));
|
||||
foreach ($this->args as $arg) {
|
||||
$models []= new BulkReply($arg);
|
||||
}
|
||||
|
||||
return new MultiBulkReply($models);
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
$ret = $this->args;
|
||||
array_unshift($ret, $this->command);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getRequestMessage($this->command, $this->args);
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
/**
|
||||
*
|
||||
* @link http://redis.io/topics/protocol#status-reply
|
||||
*/
|
||||
class StatusReply implements ModelInterface
|
||||
{
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* create status reply (single line message)
|
||||
*
|
||||
* @param string|Status $message
|
||||
* @return string
|
||||
*/
|
||||
public function __construct($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getStatusMessage($this->message);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use UnderflowException;
|
||||
|
||||
class MessageBuffer implements ParserInterface
|
||||
{
|
||||
private $parser;
|
||||
private $incomingQueue = array();
|
||||
|
||||
public function __construct(ParserInterface $parser)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
public function popIncomingModel()
|
||||
{
|
||||
if (!$this->incomingQueue) {
|
||||
throw new UnderflowException('Incoming message queue is empty');
|
||||
}
|
||||
return array_shift($this->incomingQueue);
|
||||
}
|
||||
|
||||
public function hasIncomingModel()
|
||||
{
|
||||
return ($this->incomingQueue) ? true : false;
|
||||
}
|
||||
|
||||
public function pushIncoming($data)
|
||||
{
|
||||
$ret = $this->parser->pushIncoming($data);
|
||||
|
||||
foreach ($ret as $one) {
|
||||
$this->incomingQueue []= $one;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ParserException extends UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
|
||||
interface ParserInterface
|
||||
{
|
||||
/**
|
||||
* push a chunk of the redis protocol message into the buffer and parse
|
||||
*
|
||||
* You can push any number of bytes of a redis protocol message into the
|
||||
* parser and it will try to parse messages from its data stream. So you can
|
||||
* pass data directly from your socket stream and the parser will return the
|
||||
* right amount of message model objects for you.
|
||||
*
|
||||
* If you pass an incomplete message, expect it to return an empty array. If
|
||||
* your incomplete message is split to across multiple chunks, the parsed
|
||||
* message model will be returned once the parser has sufficient data.
|
||||
*
|
||||
* @param string $dataChunk
|
||||
* @return ModelInterface[] 0+ message models
|
||||
* @throws ParserException if the message can not be parsed
|
||||
* @see self::popIncomingModel()
|
||||
*/
|
||||
public function pushIncoming($dataChunk);
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
use Clue\Redis\Protocol\Model\Request;
|
||||
|
||||
class RequestParser implements ParserInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
private $incomingBuffer = '';
|
||||
private $incomingOffset = 0;
|
||||
|
||||
public function pushIncoming($dataChunk)
|
||||
{
|
||||
$this->incomingBuffer .= $dataChunk;
|
||||
|
||||
$parsed = array();
|
||||
|
||||
do {
|
||||
$saved = $this->incomingOffset;
|
||||
$message = $this->readRequest();
|
||||
if ($message === null) {
|
||||
// restore previous position for next parsing attempt
|
||||
$this->incomingOffset = $saved;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($message !== false) {
|
||||
$parsed []= $message;
|
||||
}
|
||||
} while($this->incomingBuffer !== '');
|
||||
|
||||
if ($this->incomingOffset !== 0) {
|
||||
$this->incomingBuffer = (string)substr($this->incomingBuffer, $this->incomingOffset);
|
||||
$this->incomingOffset = 0;
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to parse request from incoming buffer
|
||||
*
|
||||
* @throws ParserException if the incoming buffer is invalid
|
||||
* @return Request|null
|
||||
*/
|
||||
private function readRequest()
|
||||
{
|
||||
$crlf = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
if ($crlf === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// line starts with a multi-bulk header "*"
|
||||
if (isset($this->incomingBuffer[$this->incomingOffset]) && $this->incomingBuffer[$this->incomingOffset] === '*') {
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset + 1, $crlf - $this->incomingOffset + 1);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
$count = (int)$line;
|
||||
|
||||
if ($count <= 0) {
|
||||
return false;
|
||||
}
|
||||
$command = null;
|
||||
$args = array();
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$sub = $this->readBulk();
|
||||
if ($sub === null) {
|
||||
return null;
|
||||
}
|
||||
if ($command === null) {
|
||||
$command = $sub;
|
||||
} else {
|
||||
$args []= $sub;
|
||||
}
|
||||
}
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
// parse an old inline request instead
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset, $crlf - $this->incomingOffset);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
|
||||
$args = preg_split('/ +/', trim($line, ' '));
|
||||
$command = array_shift($args);
|
||||
|
||||
if ($command === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
private function readBulk()
|
||||
{
|
||||
$crlf = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
if ($crlf === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// line has to start with a bulk header "$"
|
||||
if (!isset($this->incomingBuffer[$this->incomingOffset]) || $this->incomingBuffer[$this->incomingOffset] !== '$') {
|
||||
throw new ParserException('ERR Protocol error: expected \'$\', got \'' . substr($this->incomingBuffer, $this->incomingOffset, 1) . '\'');
|
||||
}
|
||||
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset + 1, $crlf - $this->incomingOffset + 1);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
$size = (int)$line;
|
||||
|
||||
if ($size < 0) {
|
||||
throw new ParserException('ERR Protocol error: invalid bulk length');
|
||||
}
|
||||
|
||||
if (!isset($this->incomingBuffer[$this->incomingOffset + $size + 1])) {
|
||||
// check enough bytes + crlf are buffered
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = substr($this->incomingBuffer, $this->incomingOffset, $size);
|
||||
$this->incomingOffset += $size + 2;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
use Clue\Redis\Protocol\Model\IntegerReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Model\StatusReply;
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
|
||||
/**
|
||||
* Simple recursive redis wire protocol parser
|
||||
*
|
||||
* Heavily influenced by blocking parser implementation from jpd/redisent.
|
||||
*
|
||||
* @link https://github.com/jdp/redisent
|
||||
* @link http://redis.io/topics/protocol
|
||||
*/
|
||||
class ResponseParser implements ParserInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
private $incomingBuffer = '';
|
||||
private $incomingOffset = 0;
|
||||
|
||||
public function pushIncoming($dataChunk)
|
||||
{
|
||||
$this->incomingBuffer .= $dataChunk;
|
||||
|
||||
return $this->tryParsingIncomingMessages();
|
||||
}
|
||||
|
||||
private function tryParsingIncomingMessages()
|
||||
{
|
||||
$messages = array();
|
||||
|
||||
do {
|
||||
$message = $this->readResponse();
|
||||
if ($message === null) {
|
||||
// restore previous position for next parsing attempt
|
||||
$this->incomingOffset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
$messages []= $message;
|
||||
|
||||
$this->incomingBuffer = (string)substr($this->incomingBuffer, $this->incomingOffset);
|
||||
$this->incomingOffset = 0;
|
||||
} while($this->incomingBuffer !== '');
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function readLine()
|
||||
{
|
||||
$pos = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
|
||||
if ($pos === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = (string)substr($this->incomingBuffer, $this->incomingOffset, $pos - $this->incomingOffset);
|
||||
$this->incomingOffset = $pos + 2;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function readLength($len)
|
||||
{
|
||||
$ret = substr($this->incomingBuffer, $this->incomingOffset, $len);
|
||||
if (strlen($ret) !== $len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->incomingOffset += $len;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to parse response from incoming buffer
|
||||
*
|
||||
* ripped from jdp/redisent, with some minor modifications to read from
|
||||
* the incoming buffer instead of issuing a blocking fread on a stream
|
||||
*
|
||||
* @throws ParserException if the incoming buffer is invalid
|
||||
* @return ModelInterface|null
|
||||
* @link https://github.com/jdp/redisent
|
||||
*/
|
||||
private function readResponse()
|
||||
{
|
||||
/* Parse the response based on the reply identifier */
|
||||
$reply = $this->readLine();
|
||||
if ($reply === null) {
|
||||
return null;
|
||||
}
|
||||
switch (substr($reply, 0, 1)) {
|
||||
/* Error reply */
|
||||
case '-':
|
||||
$response = new ErrorReply(substr($reply, 1));
|
||||
break;
|
||||
/* Inline reply */
|
||||
case '+':
|
||||
$response = new StatusReply(substr($reply, 1));
|
||||
break;
|
||||
/* Bulk reply */
|
||||
case '$':
|
||||
$size = (int)substr($reply, 1);
|
||||
if ($size === -1) {
|
||||
return new BulkReply(null);
|
||||
}
|
||||
$data = $this->readLength($size);
|
||||
if ($data === null) {
|
||||
return null;
|
||||
}
|
||||
if ($this->readLength(2) === null) { /* discard crlf */
|
||||
return null;
|
||||
}
|
||||
$response = new BulkReply($data);
|
||||
break;
|
||||
/* Multi-bulk reply */
|
||||
case '*':
|
||||
$count = (int)substr($reply, 1);
|
||||
if ($count === -1) {
|
||||
return new MultiBulkReply(null);
|
||||
}
|
||||
$response = array();
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$sub = $this->readResponse();
|
||||
if ($sub === null) {
|
||||
return null;
|
||||
}
|
||||
$response []= $sub;
|
||||
}
|
||||
$response = new MultiBulkReply($response);
|
||||
break;
|
||||
/* Integer reply */
|
||||
case ':':
|
||||
$response = new IntegerReply(substr($reply, 1));
|
||||
break;
|
||||
default:
|
||||
throw new ParserException('Invalid message can not be parsed: "' . $reply . '"');
|
||||
break;
|
||||
}
|
||||
/* Party on */
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Serializer;
|
||||
|
||||
use Clue\Redis\Protocol\Model\StatusReply;
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\IntegerReply;
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\Request;
|
||||
|
||||
class RecursiveSerializer implements SerializerInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
public function getRequestMessage($command, array $args = array())
|
||||
{
|
||||
$data = '*' . (count($args) + 1) . "\r\n$" . strlen($command) . "\r\n" . $command . "\r\n";
|
||||
foreach ($args as $arg) {
|
||||
$data .= '$' . strlen($arg) . "\r\n" . $arg . "\r\n";
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function createRequestModel($command, array $args = array())
|
||||
{
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
public function getReplyMessage($data)
|
||||
{
|
||||
if (is_string($data) || $data === null) {
|
||||
return $this->getBulkMessage($data);
|
||||
} else if (is_int($data) || is_float($data) || is_bool($data)) {
|
||||
return $this->getIntegerMessage($data);
|
||||
} else if ($data instanceof Exception) {
|
||||
return $this->getErrorMessage($data->getMessage());
|
||||
} else if (is_array($data)) {
|
||||
return $this->getMultiBulkMessage($data);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid data type passed for serialization');
|
||||
}
|
||||
}
|
||||
|
||||
public function createReplyModel($data)
|
||||
{
|
||||
if (is_string($data) || $data === null) {
|
||||
return new BulkReply($data);
|
||||
} else if (is_int($data) || is_float($data) || is_bool($data)) {
|
||||
return new IntegerReply($data);
|
||||
} else if ($data instanceof Exception) {
|
||||
return new ErrorReply($data->getMessage());
|
||||
} else if (is_array($data)) {
|
||||
$models = array();
|
||||
foreach ($data as $one) {
|
||||
$models []= $this->createReplyModel($one);
|
||||
}
|
||||
return new MultiBulkReply($models);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid data type passed for serialization');
|
||||
}
|
||||
}
|
||||
|
||||
public function getBulkMessage($data)
|
||||
{
|
||||
if ($data === null) {
|
||||
/* null bulk reply */
|
||||
return '$-1' . self::CRLF;
|
||||
}
|
||||
/* bulk reply */
|
||||
return '$' . strlen($data) . self::CRLF . $data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getErrorMessage($data)
|
||||
{
|
||||
/* error status reply */
|
||||
return '-' . $data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getIntegerMessage($data)
|
||||
{
|
||||
return ':' . (int)$data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getMultiBulkMessage($data)
|
||||
{
|
||||
if ($data === null) {
|
||||
/* null multi bulk reply */
|
||||
return '*-1' . self::CRLF;
|
||||
}
|
||||
/* multi bulk reply */
|
||||
$ret = '*' . count($data) . self::CRLF;
|
||||
foreach ($data as $one) {
|
||||
if ($one instanceof ModelInterface) {
|
||||
$ret .= $one->getMessageSerialized($this);
|
||||
} else {
|
||||
$ret .= $this->getReplyMessage($one);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getStatusMessage($data)
|
||||
{
|
||||
/* status reply */
|
||||
return '+' . $data . self::CRLF;
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Serializer;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ErrorReplyException;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
|
||||
interface SerializerInterface
|
||||
{
|
||||
/**
|
||||
* create a serialized unified request protocol message
|
||||
*
|
||||
* This is the *one* method most redis client libraries will likely want to
|
||||
* use in order to send a serialized message (a request) over the* wire to
|
||||
* your redis server instance.
|
||||
*
|
||||
* This method should be used in favor of constructing a request model and
|
||||
* then serializing it. While its effect might be equivalent, this method
|
||||
* is likely to (i.e. it /could/) provide a faster implementation.
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $args
|
||||
* @return string
|
||||
* @see self::createRequestMessage()
|
||||
*/
|
||||
public function getRequestMessage($command, array $args = array());
|
||||
|
||||
/**
|
||||
* create a unified request protocol message model
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $args
|
||||
* @return MultiBulkReply
|
||||
*/
|
||||
public function createRequestModel($command, array $args = array());
|
||||
|
||||
/**
|
||||
* create a serialized unified protocol reply message
|
||||
*
|
||||
* This is most useful for a redis server implementation which needs to
|
||||
* process client requests and send resulting reply messages.
|
||||
*
|
||||
* This method does its best to guess to right reply type and then returns
|
||||
* a serialized version of the message. It follows the "redis to lua
|
||||
* conversion table" (see link) which means most basic types can be mapped
|
||||
* as is.
|
||||
*
|
||||
* This method should be used in favor of constructing a reply model and
|
||||
* then serializing it. While its effect might be equivalent, this method
|
||||
* is likely to (i.e. it /could/) provide a faster implementation.
|
||||
*
|
||||
* Note however, you may still want to explicitly create a nested reply
|
||||
* model hierarchy if you need more control over the serialized message. For
|
||||
* instance, a null value will always be returned as a Null-Bulk-Reply, so
|
||||
* there's no way to express a Null-Multi-Bulk-Reply, unless you construct
|
||||
* it explicitly.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
* @see self::createReplyModel()
|
||||
* @link http://redis.io/commands/eval
|
||||
*/
|
||||
public function getReplyMessage($data);
|
||||
|
||||
/**
|
||||
* create response message by determining datatype from given argument
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return ModelInterface
|
||||
*/
|
||||
public function createReplyModel($data);
|
||||
|
||||
public function getBulkMessage($data);
|
||||
|
||||
public function getErrorMessage($data);
|
||||
|
||||
public function getIntegerMessage($data);
|
||||
|
||||
public function getMultiBulkMessage($data);
|
||||
|
||||
public function getStatusMessage($data);
|
||||
}
|
34
vendor/clue/redis-protocol/tests/FactoryTest.php
vendored
34
vendor/clue/redis-protocol/tests/FactoryTest.php
vendored
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Factory;
|
||||
|
||||
class FactoryTest extends TestCase
|
||||
{
|
||||
private $factory;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->factory = new Factory();
|
||||
}
|
||||
|
||||
public function testCreateResponseParser()
|
||||
{
|
||||
$parser = $this->factory->createResponseParser();
|
||||
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $parser);
|
||||
}
|
||||
|
||||
public function testCreateRequestParser()
|
||||
{
|
||||
$parser = $this->factory->createRequestParser();
|
||||
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $parser);
|
||||
}
|
||||
|
||||
public function testCreateSerializer()
|
||||
{
|
||||
$serializer = $this->factory->createSerializer();
|
||||
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Serializer\SerializerInterface', $serializer);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\RecursiveSerializer;
|
||||
|
||||
abstract class AbstractModelTest extends TestCase
|
||||
{
|
||||
protected $serializer;
|
||||
|
||||
abstract protected function createModel($value);
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->serializer = new RecursiveSerializer();
|
||||
}
|
||||
|
||||
public function testConstructor()
|
||||
{
|
||||
$model = $this->createModel(null);
|
||||
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\ModelInterface', $model);
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
|
||||
class BulkReplyTest extends AbstractModelTest
|
||||
{
|
||||
protected function createModel($value)
|
||||
{
|
||||
return new BulkReply($value);
|
||||
}
|
||||
|
||||
public function testStringReply()
|
||||
{
|
||||
$model = $this->createModel('test');
|
||||
|
||||
$this->assertEquals('test', $model->getValueNative());
|
||||
$this->assertEquals("$4\r\ntest\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
|
||||
public function testEmptyStringReply()
|
||||
{
|
||||
$model = $this->createModel('');
|
||||
|
||||
$this->assertEquals('', $model->getValueNative());
|
||||
$this->assertEquals("$0\r\n\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
|
||||
public function testIntegerCast()
|
||||
{
|
||||
$model = $this->createModel(123);
|
||||
|
||||
$this->assertEquals('123', $model->getValueNative());
|
||||
$this->assertEquals("$3\r\n123\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
|
||||
public function testNullBulkReply()
|
||||
{
|
||||
$model = $this->createModel(null);
|
||||
|
||||
$this->assertEquals(null, $model->getValueNative());
|
||||
$this->assertEquals("$-1\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
|
||||
class ErrorReplyTest extends AbstractModelTest
|
||||
{
|
||||
protected function createModel($value)
|
||||
{
|
||||
return new ErrorReply($value);
|
||||
}
|
||||
|
||||
public function testError()
|
||||
{
|
||||
$model = $this->createModel('ERR error');
|
||||
|
||||
$this->assertEquals('ERR error', $model->getValueNative());
|
||||
$this->assertEquals("-ERR error\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Model\IntegerReply;
|
||||
|
||||
class IntegerReplyTest extends AbstractModelTest
|
||||
{
|
||||
protected function createModel($value)
|
||||
{
|
||||
return new IntegerReply($value);
|
||||
}
|
||||
|
||||
public function testIntegerReply()
|
||||
{
|
||||
$model = $this->createModel(0);
|
||||
$this->assertEquals(0, $model->getValueNative());
|
||||
$this->assertEquals(":0\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
|
||||
public function testFloatCasted()
|
||||
{
|
||||
$model = $this->createModel(-12.99);
|
||||
$this->assertEquals(-12, $model->getValueNative());
|
||||
$this->assertEquals(":-12\r\n", $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$model = $this->createModel(14.99);
|
||||
$this->assertEquals(14, $model->getValueNative());
|
||||
$this->assertEquals(":14\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
|
||||
public function testBooleanCasted()
|
||||
{
|
||||
$model = $this->createModel(true);
|
||||
$this->assertEquals(1, $model->getValueNative());
|
||||
$this->assertEquals(":1\r\n", $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$model = $this->createModel(false);
|
||||
$this->assertEquals(0, $model->getValueNative());
|
||||
$this->assertEquals(":0\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\IntegerReply;
|
||||
|
||||
class MultiBulkReplyTest extends AbstractModelTest
|
||||
{
|
||||
protected function createModel($value)
|
||||
{
|
||||
return new MultiBulkReply($value);
|
||||
}
|
||||
|
||||
public function testEmptyArray()
|
||||
{
|
||||
$model = $this->createModel(array());
|
||||
|
||||
$this->assertEquals(array(), $model->getValueNative());
|
||||
$this->assertEquals("*0\r\n", $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$this->assertFalse($model->isRequest());
|
||||
}
|
||||
|
||||
public function testNullMultiBulkReply()
|
||||
{
|
||||
$model = $this->createModel(null);
|
||||
|
||||
$this->assertEquals(null, $model->getValueNative());
|
||||
$this->assertEquals("*-1\r\n", $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$this->assertFalse($model->isRequest());
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MultiBulkReply $model
|
||||
* @depends testNullMultiBulkReply
|
||||
* @expectedException UnexpectedValueException
|
||||
*/
|
||||
public function testNullMultiBulkReplyIsNotARequest(MultiBulkReply $model)
|
||||
{
|
||||
$model->getRequestModel();
|
||||
}
|
||||
|
||||
public function testSingleBulkEnclosed()
|
||||
{
|
||||
$model = $this->createModel(array(new BulkReply('test')));
|
||||
|
||||
$this->assertEquals(array('test'), $model->getValueNative());
|
||||
$this->assertEquals("*1\r\n$4\r\ntest\r\n", $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$this->assertTrue($model->isRequest());
|
||||
|
||||
// this can be represented by a request
|
||||
$request = $model->getRequestModel();
|
||||
$this->assertEquals($model->getValueNative(), $request->getValueNative());
|
||||
|
||||
// representing the request as a reply should return our original instance
|
||||
$reply = $request->getReplyModel();
|
||||
$this->assertEquals($model, $reply);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testSingleBulkEnclosed
|
||||
*/
|
||||
public function testStringEnclosedEqualsSingleBulk(MultiBulkReply $expected)
|
||||
{
|
||||
$model = $this->createModel(array('test'));
|
||||
|
||||
$this->assertEquals($expected->getValueNative(), $model->getValueNative());
|
||||
$this->assertEquals($expected->getMessageSerialized($this->serializer), $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$this->assertTrue($model->isRequest());
|
||||
}
|
||||
|
||||
public function testMixedReply()
|
||||
{
|
||||
$model = $this->createModel(array(new BulkReply('test'), new IntegerReply(123)));
|
||||
|
||||
$this->assertEquals(array('test', 123), $model->getValueNative());
|
||||
$this->assertEquals("*2\r\n$4\r\ntest\r\n:123\r\n", $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$this->assertFalse($model->isRequest());
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MultiBulkReply $model
|
||||
* @depends testMixedReply
|
||||
* @expectedException UnexpectedValueException
|
||||
*/
|
||||
public function testMixedReplyIsNotARequest(MultiBulkReply $model)
|
||||
{
|
||||
$model->getRequestModel();
|
||||
}
|
||||
|
||||
public function testMultiStrings()
|
||||
{
|
||||
$model = $this->createModel(array('SET', 'a', 'b'));
|
||||
|
||||
$this->assertEquals(array('SET', 'a', 'b'), $model->getValueNative());
|
||||
|
||||
$this->assertTrue($model->isRequest());
|
||||
|
||||
$request = $model->getRequestModel();
|
||||
|
||||
// this can be represented by a request
|
||||
$request = $model->getRequestModel();
|
||||
$this->assertEquals($model->getValueNative(), $request->getValueNative());
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Model\Request;
|
||||
|
||||
class RequestTest extends AbstractModelTest
|
||||
{
|
||||
protected function createModel($value)
|
||||
{
|
||||
return new Request('QUIT');
|
||||
}
|
||||
|
||||
public function testPing()
|
||||
{
|
||||
$model = new Request('PING');
|
||||
|
||||
$this->assertEquals('PING', $model->getCommand());
|
||||
$this->assertEquals(array(), $model->getArgs());
|
||||
$this->assertEquals(array('PING'), $model->getValueNative());
|
||||
$this->assertEquals("*1\r\n$4\r\nPING\r\n", $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$reply = $model->getReplyModel();
|
||||
$this->assertEquals($model->getValueNative(), $reply->getValueNative());
|
||||
}
|
||||
|
||||
public function testGet()
|
||||
{
|
||||
$model = new Request('GET', array('a'));
|
||||
|
||||
$this->assertEquals('GET', $model->getCommand());
|
||||
$this->assertEquals(array('a'), $model->getArgs());
|
||||
$this->assertEquals(array('GET', 'a'), $model->getValueNative());
|
||||
$this->assertEquals("*2\r\n$3\r\nGET\r\n$1\r\na\r\n", $model->getMessageSerialized($this->serializer));
|
||||
|
||||
$reply = $model->getReplyModel();
|
||||
$this->assertEquals($model->getValueNative(), $reply->getValueNative());
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Model\StatusReply;
|
||||
|
||||
class StatusReplyTest extends AbstractModelTest
|
||||
{
|
||||
protected function createModel($value)
|
||||
{
|
||||
return new StatusReply($value);
|
||||
}
|
||||
|
||||
public function testStatusOk()
|
||||
{
|
||||
$model = $this->createModel('OK');
|
||||
|
||||
$this->assertEquals('OK', $model->getValueNative());
|
||||
$this->assertEquals("+OK\r\n", $model->getMessageSerialized($this->serializer));
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Parser\MessageBuffer;
|
||||
|
||||
abstract class AbstractParserTest extends TestCase
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var ParserInterface
|
||||
*/
|
||||
protected $parser;
|
||||
|
||||
abstract protected function createParser();
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->parser = $this->createParser();
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Parser\ParserInterface', $this->parser);
|
||||
}
|
||||
|
||||
public function testParsingMessageOne()
|
||||
{
|
||||
// getRequestMessage('test')
|
||||
$message = $expected = "*1\r\n$4\r\ntest\r\n";
|
||||
|
||||
$models = $this->parser->pushIncoming($message);
|
||||
$this->assertCount(1, $models);
|
||||
|
||||
$model = reset($models);
|
||||
$this->assertEquals(array('test'), $model->getValueNative());
|
||||
}
|
||||
|
||||
public function testParsingMessageTwoPartial()
|
||||
{
|
||||
// getRequestMessage('test', array('second'))
|
||||
$message = "*2\r\n$4\r\ntest\r\n$6\r\nsecond\r\n";
|
||||
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 0, 1)));
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 1, 1)));
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 2, 1)));
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming(substr($message, 3, 10)));
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming(substr($message, 13)));
|
||||
|
||||
$model = reset($models);
|
||||
|
||||
$this->assertEquals(array('test', 'second'), $model->getValueNative());
|
||||
}
|
||||
|
||||
public function testMessageBuffer()
|
||||
{
|
||||
$buffer = new MessageBuffer($this->parser);
|
||||
|
||||
$this->assertFalse($buffer->hasIncomingModel());
|
||||
|
||||
$data = "*1\r\n$4\r\ntest\r\n";
|
||||
$this->assertCount(1, $models = $buffer->pushIncoming($data));
|
||||
$this->assertTrue($buffer->hasIncomingModel());
|
||||
|
||||
$expected = reset($models);
|
||||
$this->assertSame($expected, $buffer->popIncomingModel());
|
||||
$this->assertFalse($buffer->hasIncomingModel());
|
||||
|
||||
$this->setExpectedException('UnderflowException');
|
||||
$buffer->popIncomingModel();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user