mirror of
https://github.com/Icinga/icinga-php-thirdparty.git
synced 2025-07-08 14:24:31 +02:00
Version v0.12.0
This commit is contained in:
parent
44cd0d4612
commit
b94143b51d
10881
asset/js/jquery/jquery.js
vendored
Normal file
10881
asset/js/jquery/jquery.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
asset/js/jquery/jquery.min.js
vendored
Normal file
2
asset/js/jquery/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3978
composer.lock
generated
Normal file
3978
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
vendor/autoload.php
vendored
Normal file
25
vendor/autoload.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?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 ComposerAutoloaderInit0796bbd0facf8653dba47b66720dea37::getLoader();
|
119
vendor/bin/lessc
vendored
Executable file
119
vendor/bin/lessc
vendored
Executable file
@ -0,0 +1,119 @@
|
||||
#!/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
Normal file
415
vendor/brick/math/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,415 @@
|
||||
# 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
Normal file
20
vendor/brick/math/LICENSE
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
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
Normal file
17
vendor/brick/math/SECURITY.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# 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
Normal file
35
vendor/brick/math/composer.json
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"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
Normal file
895
vendor/brick/math/src/BigDecimal.php
vendored
Normal file
@ -0,0 +1,895 @@
|
||||
<?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
Normal file
1184
vendor/brick/math/src/BigInteger.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
572
vendor/brick/math/src/BigNumber.php
vendored
Normal file
572
vendor/brick/math/src/BigNumber.php
vendored
Normal file
@ -0,0 +1,572 @@
|
||||
<?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
Normal file
523
vendor/brick/math/src/BigRational.php
vendored
Normal file
@ -0,0 +1,523 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
41
vendor/brick/math/src/Exception/DivisionByZeroException.php
vendored
Normal file
41
vendor/brick/math/src/Exception/DivisionByZeroException.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?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.');
|
||||
}
|
||||
}
|
27
vendor/brick/math/src/Exception/IntegerOverflowException.php
vendored
Normal file
27
vendor/brick/math/src/Exception/IntegerOverflowException.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
14
vendor/brick/math/src/Exception/MathException.php
vendored
Normal file
14
vendor/brick/math/src/Exception/MathException.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?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
|
||||
{
|
||||
}
|
12
vendor/brick/math/src/Exception/NegativeNumberException.php
vendored
Normal file
12
vendor/brick/math/src/Exception/NegativeNumberException.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?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
|
||||
{
|
||||
}
|
35
vendor/brick/math/src/Exception/NumberFormatException.php
vendored
Normal file
35
vendor/brick/math/src/Exception/NumberFormatException.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
21
vendor/brick/math/src/Exception/RoundingNecessaryException.php
vendored
Normal file
21
vendor/brick/math/src/Exception/RoundingNecessaryException.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?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
Normal file
756
vendor/brick/math/src/Internal/Calculator.php
vendored
Normal file
@ -0,0 +1,756 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
116
vendor/brick/math/src/Internal/Calculator/BcMathCalculator.php
vendored
Normal file
116
vendor/brick/math/src/Internal/Calculator/BcMathCalculator.php
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
156
vendor/brick/math/src/Internal/Calculator/GmpCalculator.php
vendored
Normal file
156
vendor/brick/math/src/Internal/Calculator/GmpCalculator.php
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
634
vendor/brick/math/src/Internal/Calculator/NativeCalculator.php
vendored
Normal file
634
vendor/brick/math/src/Internal/Calculator/NativeCalculator.php
vendored
Normal file
@ -0,0 +1,634 @@
|
||||
<?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
Normal file
107
vendor/brick/math/src/RoundingMode.php
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
<?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
Normal file
2
vendor/clue/block-react/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
152
vendor/clue/block-react/CHANGELOG.md
vendored
Normal file
152
vendor/clue/block-react/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
# 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
Normal file
21
vendor/clue/block-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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
Normal file
335
vendor/clue/block-react/README.md
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
# 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
Normal file
29
vendor/clue/block-react/composer.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"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
Normal file
357
vendor/clue/block-react/src/functions.php
vendored
Normal file
@ -0,0 +1,357 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
8
vendor/clue/block-react/src/functions_include.php
vendored
Normal file
8
vendor/clue/block-react/src/functions_include.php
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Block;
|
||||
|
||||
if (!function_exists('Clue\\React\\Block\\sleep')) {
|
||||
require __DIR__ . '/functions.php';
|
||||
}
|
||||
|
2
vendor/clue/connection-manager-extra/.github/FUNDING.yml
vendored
Normal file
2
vendor/clue/connection-manager-extra/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
191
vendor/clue/connection-manager-extra/CHANGELOG.md
vendored
Normal file
191
vendor/clue/connection-manager-extra/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
# 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
Normal file
21
vendor/clue/connection-manager-extra/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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
Normal file
287
vendor/clue/connection-manager-extra/README.md
vendored
Normal file
@ -0,0 +1,287 @@
|
||||
# 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.
|
29
vendor/clue/connection-manager-extra/composer.json
vendored
Normal file
29
vendor/clue/connection-manager-extra/composer.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
41
vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
vendored
Normal file
41
vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?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);
|
||||
});
|
||||
}
|
||||
}
|
41
vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php
vendored
Normal file
41
vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
52
vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php
vendored
Normal file
52
vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<?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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
26
vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php
vendored
Normal file
26
vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
46
vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
vendored
Normal file
46
vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
<?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;
|
||||
});
|
||||
}
|
||||
}
|
34
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
vendored
Normal file
34
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?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;
|
||||
});
|
||||
}
|
||||
}
|
62
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
vendored
Normal file
62
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
<?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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
14
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php
vendored
Normal file
14
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
class ConnectionManagerRandom extends ConnectionManagerConsecutive
|
||||
{
|
||||
public function connect($uri)
|
||||
{
|
||||
$managers = $this->managers;
|
||||
shuffle($managers);
|
||||
|
||||
return $this->tryConnection($managers, $uri);
|
||||
}
|
||||
}
|
111
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php
vendored
Normal file
111
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
2
vendor/clue/http-proxy-react/.github/FUNDING.yml
vendored
Normal file
2
vendor/clue/http-proxy-react/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
200
vendor/clue/http-proxy-react/CHANGELOG.md
vendored
Normal file
200
vendor/clue/http-proxy-react/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
# 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
Normal file
21
vendor/clue/http-proxy-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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
Normal file
510
vendor/clue/http-proxy-react/README.md
vendored
Normal file
@ -0,0 +1,510 @@
|
||||
# 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
Normal file
31
vendor/clue/http-proxy-react/composer.json
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"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
Normal file
278
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Normal file
@ -0,0 +1,278 @@
|
||||
<?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
Normal file
2
vendor/clue/mq-react/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
96
vendor/clue/mq-react/CHANGELOG.md
vendored
Normal file
96
vendor/clue/mq-react/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
# 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
Normal file
21
vendor/clue/mq-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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
Normal file
532
vendor/clue/mq-react/README.md
vendored
Normal file
@ -0,0 +1,532 @@
|
||||
# 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
Normal file
33
vendor/clue/mq-react/composer.json
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"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
Normal file
465
vendor/clue/mq-react/src/Queue.php
vendored
Normal file
@ -0,0 +1,465 @@
|
||||
<?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
Normal file
2
vendor/clue/redis-protocol/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/vendor
|
||||
/composer.lock
|
8
vendor/clue/redis-protocol/.travis.yml
vendored
Normal file
8
vendor/clue/redis-protocol/.travis.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
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
Normal file
50
vendor/clue/redis-protocol/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
# 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
Normal file
139
vendor/clue/redis-protocol/README.md
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
# 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
Normal file
19
vendor/clue/redis-protocol/composer.json
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"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
Normal file
22
vendor/clue/redis-protocol/example/client.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?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
Normal file
31
vendor/clue/redis-protocol/example/perf.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?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
Normal file
19
vendor/clue/redis-protocol/phpunit.xml.dist
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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>
|
51
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php
vendored
Normal file
51
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
31
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php
vendored
Normal file
31
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
23
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php
vendored
Normal file
23
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?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);
|
||||
}
|
100
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php
vendored
Normal file
100
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
53
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php
vendored
Normal file
53
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
40
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php
vendored
Normal file
40
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
10
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php
vendored
Normal file
10
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ParserException extends UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
28
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php
vendored
Normal file
28
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
<?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);
|
||||
}
|
125
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php
vendored
Normal file
125
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
151
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php
vendored
Normal file
151
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
111
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php
vendored
Normal file
111
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
83
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php
vendored
Normal file
83
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
<?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
Normal file
34
vendor/clue/redis-protocol/tests/FactoryTest.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
22
vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php
vendored
Normal file
22
vendor/clue/redis-protocol/tests/Model/AbstractModelTest.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
43
vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php
vendored
Normal file
43
vendor/clue/redis-protocol/tests/Model/BulkReplyTest.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
19
vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php
vendored
Normal file
19
vendor/clue/redis-protocol/tests/Model/ErrorReplyTest.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
40
vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php
vendored
Normal file
40
vendor/clue/redis-protocol/tests/Model/IntegerReplyTest.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
115
vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php
vendored
Normal file
115
vendor/clue/redis-protocol/tests/Model/MultiBulkReplyTest.php
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
37
vendor/clue/redis-protocol/tests/Model/RequestTest.php
vendored
Normal file
37
vendor/clue/redis-protocol/tests/Model/RequestTest.php
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
19
vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php
vendored
Normal file
19
vendor/clue/redis-protocol/tests/Model/StatusReplyTest.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
67
vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php
vendored
Normal file
67
vendor/clue/redis-protocol/tests/Parser/AbstractParserTest.php
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
132
vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php
vendored
Normal file
132
vendor/clue/redis-protocol/tests/Parser/RequestParserTest.php
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Parser\RequestParser;
|
||||
|
||||
class RequestParserTest extends AbstractParserTest
|
||||
{
|
||||
protected function createParser()
|
||||
{
|
||||
return new RequestParser();
|
||||
}
|
||||
|
||||
public function testSimplePingRequest()
|
||||
{
|
||||
$message = "*1\r\n$4\r\nping\r\n";
|
||||
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$request = reset($models);
|
||||
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request);
|
||||
$this->assertEquals('ping', $request->getCommand());
|
||||
$this->assertEquals(array(), $request->getArgs());
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Request $expected
|
||||
* @depends testSimplePingRequest
|
||||
*/
|
||||
public function testInlinePingRequest($expected)
|
||||
{
|
||||
$message = "ping\r\n";
|
||||
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$request = reset($models);
|
||||
|
||||
$this->assertEquals($expected, $request);
|
||||
}
|
||||
|
||||
public function testInlineWhitespaceIsIgnored()
|
||||
{
|
||||
$message = " set name value \r\n";
|
||||
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$request = reset($models);
|
||||
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request);
|
||||
$this->assertEquals('set', $request->getCommand());
|
||||
$this->assertEquals(array('name', 'value'), $request->getArgs());
|
||||
}
|
||||
|
||||
public function testIncompleteSuccessive()
|
||||
{
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming("*1\r\n"));
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming("$4\r\n"));
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming("test"));
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming("\r\n"));
|
||||
}
|
||||
|
||||
public function testNullMultiBulkRequestIsIgnored()
|
||||
{
|
||||
$message = "*-1\r\n";
|
||||
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming($message));
|
||||
}
|
||||
|
||||
public function testEmptyMultiBulkRequestIsIgnored()
|
||||
{
|
||||
$message = "*0\r\n";
|
||||
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming($message));
|
||||
}
|
||||
|
||||
public function testEmptyInlineIsIgnored()
|
||||
{
|
||||
$message = "\r\n";
|
||||
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming($message));
|
||||
}
|
||||
|
||||
public function testInlineParsesMultipleRequestsAtOnce()
|
||||
{
|
||||
$message = "hello\r\n\world\r\ntest\r\n";
|
||||
|
||||
$this->assertCount(3, $models = $this->parser->pushIncoming($message));
|
||||
}
|
||||
|
||||
|
||||
public function testEmptyInlineAroundInlineIsIgnored()
|
||||
{
|
||||
$message = "\r\n\r\n" . "ping\r\n\r\n";
|
||||
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$request = reset($models);
|
||||
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\Request', $request);
|
||||
$this->assertEquals('ping', $request->getCommand());
|
||||
$this->assertEquals(array(), $request->getArgs());
|
||||
}
|
||||
|
||||
public function testWhitespaceInlineIsIgnored()
|
||||
{
|
||||
$message = " \r\n";
|
||||
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Clue\Redis\Protocol\Parser\ParserException
|
||||
*/
|
||||
public function testInvalidMultiBulkMustContainBulk()
|
||||
{
|
||||
$message = "*1\r\n:123\r\n";
|
||||
|
||||
$this->parser->pushIncoming($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Clue\Redis\Protocol\Parser\ParserException
|
||||
*/
|
||||
public function testInvalidBulkLength()
|
||||
{
|
||||
$message = "*1\r\n$-1\r\n";
|
||||
|
||||
$this->parser->pushIncoming($message);
|
||||
}
|
||||
}
|
130
vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php
vendored
Normal file
130
vendor/clue/redis-protocol/tests/Parser/ResponseParserTest.php
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ResponseParser;
|
||||
|
||||
class RecursiveParserTest extends AbstractParserTest
|
||||
{
|
||||
protected function createParser()
|
||||
{
|
||||
return new ResponseParser();
|
||||
}
|
||||
|
||||
public function testPartialIncompleteBulkReply()
|
||||
{
|
||||
$this->assertEquals(array(), $this->parser->pushIncoming("$20\r\nincompl"));
|
||||
}
|
||||
|
||||
public function testParsingStatusReplies()
|
||||
{
|
||||
// C: PING
|
||||
$message = "+PONG\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals('PONG', $data);
|
||||
|
||||
// C: SET key value
|
||||
$message = "+OK\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals('OK', $data);
|
||||
}
|
||||
|
||||
public function testParsingErrorReply()
|
||||
{
|
||||
$message = "-WRONGTYPE Operation against a key holding the wrong kind of value\r\n";
|
||||
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
$exception = reset($models);
|
||||
|
||||
$this->assertInstanceOf('Exception', $exception);
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\ErrorReply', $exception);
|
||||
$this->assertEquals('WRONGTYPE Operation against a key holding the wrong kind of value', $exception->getMessage());
|
||||
}
|
||||
|
||||
public function testParsingIntegerReply()
|
||||
{
|
||||
// C: INCR mykey
|
||||
$message = ":1\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals(1, $data);
|
||||
}
|
||||
|
||||
public function testParsingBulkReply()
|
||||
{
|
||||
// C: GET mykey
|
||||
$message = "$6\r\nfoobar\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals("foobar", $data);
|
||||
}
|
||||
|
||||
public function testParsingNullBulkReply()
|
||||
{
|
||||
// C: GET nonexistingkey
|
||||
$message = "$-1\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals(null, $data);
|
||||
}
|
||||
|
||||
public function testParsingEmptyMultiBulkReply()
|
||||
{
|
||||
// C: LRANGE nokey 0 1
|
||||
$message = "*0\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals(array(), $data);
|
||||
}
|
||||
|
||||
public function testParsingNullMultiBulkReply()
|
||||
{
|
||||
// C: BLPOP key 1
|
||||
$message = "*-1\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals(null, $data);
|
||||
}
|
||||
|
||||
public function testParsingMultiBulkReplyWithMixedElements()
|
||||
{
|
||||
$message = "*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$6\r\nfoobar\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals(array(1, 2, 3, 4, 'foobar'), $data);
|
||||
}
|
||||
|
||||
public function testParsingMultiBulkReplyWithIncompletePush()
|
||||
{
|
||||
$this->assertCount(0, $this->parser->pushIncoming("*5\r\n:1\r\n:2\r"));
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming("\n:3\r\n:4\r\n$6\r\nfoobar\r\n"));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals(array(1, 2, 3, 4, 'foobar'), $data);
|
||||
}
|
||||
|
||||
public function testParsingMultiBulkReplyWithNullElement()
|
||||
{
|
||||
$message = "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n";
|
||||
$this->assertCount(1, $models = $this->parser->pushIncoming($message));
|
||||
|
||||
$data = reset($models)->getValueNative();
|
||||
$this->assertEquals(array('foo', null, 'bar'), $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Clue\Redis\Protocol\Parser\ParserException
|
||||
*/
|
||||
public function testParseError()
|
||||
{
|
||||
$this->parser->pushIncoming("invalid string\r\n");
|
||||
}
|
||||
}
|
141
vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php
vendored
Normal file
141
vendor/clue/redis-protocol/tests/Serializer/AbstractSerializerTest.php
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
use Clue\Redis\Protocol\Model\Status;
|
||||
use Clue\Redis\Protocol\Model\ErrorReplyException;
|
||||
//use Exception;
|
||||
|
||||
abstract class AbstractSerializerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @return SerializerInterface
|
||||
*/
|
||||
abstract protected function createSerializer();
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$this->serializer = $this->createSerializer();
|
||||
}
|
||||
|
||||
public function testIntegerReply()
|
||||
{
|
||||
$model = $this->serializer->createReplyModel(0);
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
|
||||
$this->assertEquals(0, $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(0));
|
||||
}
|
||||
|
||||
public function testFloatCastIntegerReply()
|
||||
{
|
||||
$model = $this->serializer->createReplyModel(-12.99);
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
|
||||
$this->assertEquals(-12, $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(-12.99));
|
||||
|
||||
$model = $this->serializer->createReplyModel(14.99);
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
|
||||
$this->assertEquals(14, $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(14.99));
|
||||
}
|
||||
|
||||
public function testBooleanCastIntegerReply()
|
||||
{
|
||||
$model = $this->serializer->createReplyModel(true);
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
|
||||
$this->assertEquals(1, $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(true));
|
||||
|
||||
$model = $this->serializer->createReplyModel(false);
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\IntegerReply', $model);
|
||||
$this->assertEquals(0, $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(false));
|
||||
}
|
||||
|
||||
public function testStringReply()
|
||||
{
|
||||
$model = $this->serializer->createReplyModel('test');
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\BulkReply', $model);
|
||||
$this->assertEquals('test', $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage('test'));
|
||||
}
|
||||
|
||||
public function testNullCastNullBulkReply()
|
||||
{
|
||||
$model = $this->serializer->createReplyModel(null);
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\BulkReply', $model);
|
||||
$this->assertEquals(null, $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(null));
|
||||
}
|
||||
|
||||
public function testEmptyArrayMultiBulkReply()
|
||||
{
|
||||
$model = $this->serializer->createReplyModel(array());
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\MultiBulkReply', $model);
|
||||
$this->assertEquals(array(), $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(array()));
|
||||
}
|
||||
|
||||
public function testArrayMultiBulkReply()
|
||||
{
|
||||
$model = $this->serializer->createReplyModel(array('test', 123));
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\MultiBulkReply', $model);
|
||||
$this->assertEquals(array('test', 123), $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(array('test', 123)));
|
||||
}
|
||||
|
||||
public function testErrorReply()
|
||||
{
|
||||
$model = $this->serializer->createReplyModel(new Exception('ERR failure'));
|
||||
$this->assertInstanceOf('Clue\Redis\Protocol\Model\ErrorReply', $model);
|
||||
$this->assertEquals('ERR failure', $model->getValueNative());
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $this->serializer->getReplyMessage(new Exception('ERR failure')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidArgument()
|
||||
{
|
||||
$this->serializer->createReplyModel((object)array());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidReplyData()
|
||||
{
|
||||
$this->serializer->getReplyMessage((object)array());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param array $data
|
||||
* @dataProvider provideRequestMessage
|
||||
*/
|
||||
public function testRequestMessage($command, $args)
|
||||
{
|
||||
// the model is already unit-tested, so just compare against its message
|
||||
$model = $this->serializer->createRequestModel($command, $args);
|
||||
|
||||
$message = $this->serializer->getRequestMessage($command, $args);
|
||||
|
||||
$this->assertEquals($model->getMessageSerialized($this->serializer), $message);
|
||||
}
|
||||
|
||||
public function provideRequestMessage()
|
||||
{
|
||||
return array(
|
||||
array('PING', array()),
|
||||
array('GET', array('a')),
|
||||
array('SET', array('a', 'b')),
|
||||
array('SET', array('empty', ''))
|
||||
);
|
||||
}
|
||||
|
||||
// public function testBenchCreateRequest()
|
||||
// {
|
||||
// for ($i = 0; $i < 100000; ++$i) {
|
||||
// $this->serializer->createReplyModel(array('a', 'b', 'c'));
|
||||
// }
|
||||
// }
|
||||
}
|
11
vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php
vendored
Normal file
11
vendor/clue/redis-protocol/tests/Serializer/RecursiveSerializerTest.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\RecursiveSerializer;
|
||||
|
||||
class RecursiveSerializerTest extends AbstractSerializerTest
|
||||
{
|
||||
protected function createSerializer()
|
||||
{
|
||||
return new RecursiveSerializer();
|
||||
}
|
||||
}
|
7
vendor/clue/redis-protocol/tests/bootstrap.php
vendored
Normal file
7
vendor/clue/redis-protocol/tests/bootstrap.php
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
(include_once __DIR__ . '/../vendor/autoload.php') OR die(PHP_EOL . 'ERROR: composer autoloader not found, run "composer install" or see README for instructions' . PHP_EOL);
|
||||
|
||||
class TestCase extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
}
|
2
vendor/clue/redis-react/.github/FUNDING.yml
vendored
Normal file
2
vendor/clue/redis-react/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
268
vendor/clue/redis-react/CHANGELOG.md
vendored
Normal file
268
vendor/clue/redis-react/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
# Changelog
|
||||
|
||||
## 2.6.0 (2022-05-09)
|
||||
|
||||
* Feature: Support PHP 8.1 release.
|
||||
(#119 by @clue)
|
||||
|
||||
* Improve documentation and CI configuration.
|
||||
(#123 and #125 by @SimonFrings)
|
||||
|
||||
## 2.5.0 (2021-08-31)
|
||||
|
||||
* Feature: Simplify usage by supporting new [default loop](https://reactphp.org/event-loop/#loop) and new Socket API.
|
||||
(#114 and #115 by @SimonFrings)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$factory = new Clue\React\Redis\Factory($loop);
|
||||
|
||||
// new (using default loop)
|
||||
$factory = new Clue\React\Redis\Factory();
|
||||
```
|
||||
|
||||
* Feature: Improve error reporting, include Redis URI and socket error codes in all connection errors.
|
||||
(#116 by @clue)
|
||||
|
||||
* Documentation improvements and updated examples.
|
||||
(#117 by @clue, #112 by @Nyholm and #113 by @PaulRotmann)
|
||||
|
||||
* Improve test suite and use GitHub actions for continuous integration (CI).
|
||||
(#111 by @SimonFrings)
|
||||
|
||||
## 2.4.0 (2020-09-25)
|
||||
|
||||
* Fix: Fix dangling timer when lazy connection closes with pending commands.
|
||||
(#105 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.
|
||||
(#96 and #97 by @clue and #99, #101 and #104 by @SimonFrings)
|
||||
|
||||
## 2.3.0 (2019-03-11)
|
||||
|
||||
* Feature: Add new `createLazyClient()` method to connect only on demand and
|
||||
implement "idle" timeout to close underlying connection when unused.
|
||||
(#87 and #88 by @clue and #82 by @WyriHaximus)
|
||||
|
||||
```php
|
||||
$client = $factory->createLazyClient('redis://localhost:6379');
|
||||
|
||||
$client->incr('hello');
|
||||
$client->end();
|
||||
```
|
||||
|
||||
* Feature: Support cancellation of pending connection attempts.
|
||||
(#85 by @clue)
|
||||
|
||||
```php
|
||||
$promise = $factory->createClient($redisUri);
|
||||
|
||||
$loop->addTimer(3.0, function () use ($promise) {
|
||||
$promise->cancel();
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Support connection timeouts.
|
||||
(#86 by @clue)
|
||||
|
||||
```php
|
||||
$factory->createClient('localhost?timeout=0.5');
|
||||
```
|
||||
|
||||
* Feature: Improve Exception messages for connection issues.
|
||||
(#89 by @clue)
|
||||
|
||||
```php
|
||||
$factory->createClient('redis://localhost:6379')->then(
|
||||
function (Client $client) {
|
||||
// client connected (and authenticated)
|
||||
},
|
||||
function (Exception $e) {
|
||||
// an error occurred while trying to connect (or authenticate) client
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
if ($e->getPrevious()) {
|
||||
echo $e->getPrevious()->getMessage() . PHP_EOL;
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
* Improve test suite structure and add forward compatibility with PHPUnit 7 and PHPUnit 6
|
||||
and test against PHP 7.1, 7.2, and 7.3 on TravisCI.
|
||||
(#83 by @WyriHaximus and #84 by @clue)
|
||||
|
||||
* Improve documentation and update project homepage.
|
||||
(#81 and #90 by @clue)
|
||||
|
||||
## 2.2.0 (2018-01-24)
|
||||
|
||||
* Feature: Support communication over Unix domain sockets (UDS)
|
||||
(#70 by @clue)
|
||||
|
||||
```php
|
||||
// new: now supports redis over Unix domain sockets (UDS)
|
||||
$factory->createClient('redis+unix:///tmp/redis.sock');
|
||||
```
|
||||
|
||||
## 2.1.0 (2017-09-25)
|
||||
|
||||
* Feature: Update Socket dependency to support hosts file on all platforms
|
||||
(#66 by @clue)
|
||||
|
||||
This means that connecting to hosts such as `localhost` (and for example
|
||||
those used for Docker containers) will now work as expected across all
|
||||
platforms with no changes required:
|
||||
|
||||
```php
|
||||
$factory->createClient('localhost');
|
||||
```
|
||||
|
||||
## 2.0.0 (2017-09-20)
|
||||
|
||||
A major compatibility release to update this package to support all latest
|
||||
ReactPHP components!
|
||||
|
||||
This update involves a minor BC break due to dropped support for legacy
|
||||
versions. We've tried hard to avoid BC breaks where possible and minimize impact
|
||||
otherwise. We expect that most consumers of this package will actually not be
|
||||
affected by any BC breaks, see below for more details.
|
||||
|
||||
* BC break: Remove all deprecated APIs, default to `redis://` URI scheme
|
||||
and drop legacy SocketClient in favor of new Socket component.
|
||||
(#61 by @clue)
|
||||
|
||||
> All of this affects the `Factory` only, which is mostly considered
|
||||
"advanced usage". If you're affected by this BC break, then it's
|
||||
recommended to first update to the intermediary v1.2.0 release, which
|
||||
allows you to use the `redis://` URI scheme and a standard
|
||||
`ConnectorInterface` and then update to this version without causing a
|
||||
BC break.
|
||||
|
||||
* BC break: Remove uneeded `data` event and support for advanced `MONITOR`
|
||||
command for performance and consistency reasons and
|
||||
remove underdocumented `isBusy()` method.
|
||||
(#62, #63 and #64 by @clue)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Socket v1.0 and v0.8 and EventLoop v1.0 and Evenement v3
|
||||
(#65 by @clue)
|
||||
|
||||
## 1.2.0 (2017-09-19)
|
||||
|
||||
* Feature: Support `redis[s]://` URI scheme and deprecate legacy URIs
|
||||
(#60 by @clue)
|
||||
|
||||
```php
|
||||
$factory->createClient('redis://:secret@localhost:6379/4');
|
||||
$factory->createClient('redis://localhost:6379?password=secret&db=4');
|
||||
```
|
||||
|
||||
* Feature: Factory accepts Connector from Socket and deprecate legacy SocketClient
|
||||
(#59 by @clue)
|
||||
|
||||
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($loop, array(
|
||||
'dns' => '127.0.0.1',
|
||||
'tcp' => array(
|
||||
'bindto' => '192.168.10.1:0'
|
||||
),
|
||||
'tls' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false
|
||||
)
|
||||
));
|
||||
|
||||
$factory = new Factory($loop, $connector);
|
||||
```
|
||||
|
||||
## 1.1.0 (2017-09-18)
|
||||
|
||||
* Feature: Update SocketClient dependency to latest version
|
||||
(#58 by @clue)
|
||||
|
||||
* Improve test suite by adding PHPUnit to require-dev,
|
||||
fix HHVM build for now again and ignore future HHVM build errors,
|
||||
lock Travis distro so new defaults will not break the build and
|
||||
skip functional integration tests by default
|
||||
(#52, #53, #56 and #57 by @clue)
|
||||
|
||||
## 1.0.0 (2016-05-20)
|
||||
|
||||
* First stable release, now following SemVer
|
||||
|
||||
* BC break: Consistent public API, mark internal APIs as such
|
||||
(#38 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$client->on('data', function (MessageInterface $message, Client $client) {
|
||||
// process an incoming message (raw message object)
|
||||
});
|
||||
|
||||
// new
|
||||
$client->on('data', function (MessageInterface $message) use ($client) {
|
||||
// process an incoming message (raw message object)
|
||||
});
|
||||
```
|
||||
|
||||
> Contains no other changes, so it's actually fully compatible with the v0.5.2 release.
|
||||
|
||||
## 0.5.2 (2016-05-20)
|
||||
|
||||
* Fix: Do not send empty SELECT statement when no database has been given
|
||||
(#35, #36 by @clue)
|
||||
|
||||
* Improve documentation, update dependencies and add first class support for PHP 7
|
||||
|
||||
## 0.5.1 (2015-01-12)
|
||||
|
||||
* Fix: Fix compatibility with react/promise v2.0 for monitor and PubSub commands.
|
||||
(#28)
|
||||
|
||||
## 0.5.0 (2014-11-12)
|
||||
|
||||
* Feature: Support PubSub commands (P)(UN)SUBSCRIBE and watching for "message",
|
||||
"subscribe" and "unsubscribe" events
|
||||
(#24)
|
||||
|
||||
* Feature: Support MONITOR command and watching for "monitor" events
|
||||
(#23)
|
||||
|
||||
* Improve documentation, update locked dependencies and add first class support for HHVM
|
||||
(#25, #26 and others)
|
||||
|
||||
## 0.4.0 (2014-08-25)
|
||||
|
||||
* BC break: The `Client` class has been renamed to `StreamingClient`.
|
||||
Added new `Client` interface.
|
||||
(#18 and #19)
|
||||
|
||||
* BC break: Rename `message` event to `data`.
|
||||
(#21)
|
||||
|
||||
* BC break: The `Factory` now accepts a `LoopInterface` as first argument.
|
||||
(#22)
|
||||
|
||||
* Fix: The `close` event will be emitted once when invoking the `Client::close()`
|
||||
method or when the underlying stream closes.
|
||||
(#20)
|
||||
|
||||
* Refactored code, improved testability, extended test suite and better code coverage.
|
||||
(#11, #18 and #20)
|
||||
|
||||
> Note: This is an intermediary release to ease upgrading to the imminent v0.5 release.
|
||||
|
||||
## 0.3.0 (2014-05-31)
|
||||
|
||||
* First tagged release
|
||||
|
||||
> Note: Starts at v0.3 because previous versions were not tagged. Leaving some
|
||||
> room in case they're going to be needed in the future.
|
||||
|
||||
## 0.0.0 (2013-07-05)
|
||||
|
||||
* Initial concept
|
21
vendor/clue/redis-react/LICENSE
vendored
Normal file
21
vendor/clue/redis-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
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.
|
660
vendor/clue/redis-react/README.md
vendored
Normal file
660
vendor/clue/redis-react/README.md
vendored
Normal file
@ -0,0 +1,660 @@
|
||||
# clue/reactphp-redis
|
||||
|
||||
[](https://github.com/clue/reactphp-redis/actions)
|
||||
[](https://packagist.org/packages/clue/redis-react)
|
||||
|
||||
Async [Redis](https://redis.io/) client implementation, built on top of [ReactPHP](https://reactphp.org/).
|
||||
|
||||
[Redis](https://redis.io/) is an open source, advanced, in-memory key-value database.
|
||||
It offers a set of simple, atomic operations in order to work with its primitive data types.
|
||||
Its lightweight design and fast operation makes it an ideal candidate for modern application stacks.
|
||||
This library provides you a simple API to work with your Redis database from within PHP.
|
||||
It enables you to set and query its data or use its PubSub topics to react to incoming events.
|
||||
|
||||
* **Async execution of Commands** -
|
||||
Send any number of commands to Redis in parallel (automatic pipeline) and
|
||||
process their responses as soon as results come in.
|
||||
The Promise-based design provides a *sane* interface to working with async responses.
|
||||
* **Event-driven core** -
|
||||
Register your event handler callbacks to react to incoming events, such as an incoming PubSub message event.
|
||||
* **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.
|
||||
Future or custom commands and events require no changes to be supported.
|
||||
* **Good test coverage** -
|
||||
Comes with an automated tests suite and is regularly tested against versions as old as Redis v2.6 and newer.
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
* [Support us](#support-us)
|
||||
* [Quickstart example](#quickstart-example)
|
||||
* [Usage](#usage)
|
||||
* [Commands](#commands)
|
||||
* [Promises](#promises)
|
||||
* [PubSub](#pubsub)
|
||||
* [API](#api)
|
||||
* [Factory](#factory)
|
||||
* [createClient()](#createclient)
|
||||
* [createLazyClient()](#createlazyclient)
|
||||
* [Client](#client)
|
||||
* [__call()](#__call)
|
||||
* [end()](#end)
|
||||
* [close()](#close)
|
||||
* [error event](#error-event)
|
||||
* [close event](#close-event)
|
||||
* [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 connect to your
|
||||
local Redis server and send some requests:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$factory = new Clue\React\Redis\Factory();
|
||||
$redis = $factory->createLazyClient('localhost:6379');
|
||||
|
||||
$redis->set('greeting', 'Hello world');
|
||||
$redis->append('greeting', '!');
|
||||
|
||||
$redis->get('greeting')->then(function ($greeting) {
|
||||
// Hello world!
|
||||
echo $greeting . PHP_EOL;
|
||||
});
|
||||
|
||||
$redis->incr('invocation')->then(function ($n) {
|
||||
echo 'This is invocation #' . $n . PHP_EOL;
|
||||
});
|
||||
|
||||
// end connection once all pending requests have been resolved
|
||||
$redis->end();
|
||||
```
|
||||
|
||||
See also the [examples](examples).
|
||||
|
||||
## Usage
|
||||
|
||||
### Commands
|
||||
|
||||
Most importantly, this project provides a [`Client`](#client) instance that
|
||||
can be used to invoke all [Redis commands](https://redis.io/commands) (such as `GET`, `SET`, etc.).
|
||||
|
||||
```php
|
||||
$redis->get($key);
|
||||
$redis->set($key, $value);
|
||||
$redis->exists($key);
|
||||
$redis->expire($key, $seconds);
|
||||
$redis->mget($key1, $key2, $key3);
|
||||
|
||||
$redis->multi();
|
||||
$redis->exec();
|
||||
|
||||
$redis->publish($channel, $payload);
|
||||
$redis->subscribe($channel);
|
||||
|
||||
$redis->ping();
|
||||
$redis->select($database);
|
||||
|
||||
// many more…
|
||||
```
|
||||
|
||||
Each method call matches the respective [Redis command](https://redis.io/commands).
|
||||
For example, the `$redis->get()` method will invoke the [`GET` command](https://redis.io/commands/get).
|
||||
|
||||
All [Redis commands](https://redis.io/commands) are automatically available as
|
||||
public methods via the magic [`__call()` method](#__call).
|
||||
Listing all available commands is out of scope here, please refer to the
|
||||
[Redis command reference](https://redis.io/commands).
|
||||
|
||||
Any arguments passed to the method call will be forwarded as command arguments.
|
||||
For example, the `$redis->set('name', 'Alice')` call will perform the equivalent of a
|
||||
`SET name Alice` command. It's safe to pass integer arguments where applicable (for
|
||||
example `$redis->expire($key, 60)`), but internally Redis requires all arguments to
|
||||
always be coerced to string values.
|
||||
|
||||
Each of these commands supports async operation and returns a [Promise](#promises)
|
||||
that eventually *fulfills* with its *results* on success or *rejects* with an
|
||||
`Exception` on error. See also the following section about [promises](#promises)
|
||||
for more details.
|
||||
|
||||
### Promises
|
||||
|
||||
Sending commands is async (non-blocking), so you can actually send multiple
|
||||
commands in parallel.
|
||||
Redis will respond to each command request with a response message, pending
|
||||
commands will be pipelined automatically.
|
||||
|
||||
Sending commands uses a [Promise](https://github.com/reactphp/promise)-based
|
||||
interface that makes it easy to react to when a command is completed
|
||||
(i.e. either successfully fulfilled or rejected with an error):
|
||||
|
||||
```php
|
||||
$redis->get($key)->then(function (?string $value) {
|
||||
var_dump($value);
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
### PubSub
|
||||
|
||||
This library is commonly used to efficiently transport messages using Redis'
|
||||
[Pub/Sub](https://redis.io/topics/pubsub) (Publish/Subscribe) channels. For
|
||||
instance, this can be used to distribute single messages to a larger number
|
||||
of subscribers (think horizontal scaling for chat-like applications) or as an
|
||||
efficient message transport in distributed systems (microservice architecture).
|
||||
|
||||
The [`PUBLISH` command](https://redis.io/commands/publish) can be used to
|
||||
send a message to all clients currently subscribed to a given channel:
|
||||
|
||||
```php
|
||||
$channel = 'user';
|
||||
$message = json_encode(array('id' => 10));
|
||||
$redis->publish($channel, $message);
|
||||
```
|
||||
|
||||
The [`SUBSCRIBE` command](https://redis.io/commands/subscribe) can be used to
|
||||
subscribe to a channel and then receive incoming PubSub `message` events:
|
||||
|
||||
```php
|
||||
$channel = 'user';
|
||||
$redis->subscribe($channel);
|
||||
|
||||
$redis->on('message', function ($channel, $payload) {
|
||||
// pubsub message received on given $channel
|
||||
var_dump($channel, json_decode($payload));
|
||||
});
|
||||
```
|
||||
|
||||
Likewise, you can use the same client connection to subscribe to multiple
|
||||
channels by simply executing this command multiple times:
|
||||
|
||||
```php
|
||||
$redis->subscribe('user.register');
|
||||
$redis->subscribe('user.join');
|
||||
$redis->subscribe('user.leave');
|
||||
```
|
||||
|
||||
Similarly, the [`PSUBSCRIBE` command](https://redis.io/commands/psubscribe) can
|
||||
be used to subscribe to all channels matching a given pattern and then receive
|
||||
all incoming PubSub messages with the `pmessage` event:
|
||||
|
||||
|
||||
```php
|
||||
$pattern = 'user.*';
|
||||
$redis->psubscribe($pattern);
|
||||
|
||||
$redis->on('pmessage', function ($pattern, $channel, $payload) {
|
||||
// pubsub message received matching given $pattern
|
||||
var_dump($channel, json_decode($payload));
|
||||
});
|
||||
```
|
||||
|
||||
Once you're in a subscribed state, Redis no longer allows executing any other
|
||||
commands on the same client connection. This is commonly worked around by simply
|
||||
creating a second client connection and dedicating one client connection solely
|
||||
for PubSub subscriptions and the other for all other commands.
|
||||
|
||||
The [`UNSUBSCRIBE` command](https://redis.io/commands/unsubscribe) and
|
||||
[`PUNSUBSCRIBE` command](https://redis.io/commands/punsubscribe) can be used to
|
||||
unsubscribe from active subscriptions if you're no longer interested in
|
||||
receiving any further events for the given channel and pattern subscriptions
|
||||
respectively:
|
||||
|
||||
```php
|
||||
$redis->subscribe('user');
|
||||
|
||||
Loop::addTimer(60.0, function () use ($redis) {
|
||||
$redis->unsubscribe('user');
|
||||
});
|
||||
```
|
||||
|
||||
Likewise, once you've unsubscribed the last channel and pattern, the client
|
||||
connection is no longer in a subscribed state and you can issue any other
|
||||
command over this client connection again.
|
||||
|
||||
Each of the above methods follows normal request-response semantics and return
|
||||
a [`Promise`](#promises) to await successful subscriptions. Note that while
|
||||
Redis allows a variable number of arguments for each of these commands, this
|
||||
library is currently limited to single arguments for each of these methods in
|
||||
order to match exactly one response to each command request. As an alternative,
|
||||
the methods can simply be invoked multiple times with one argument each.
|
||||
|
||||
Additionally, can listen for the following PubSub events to get notifications
|
||||
about subscribed/unsubscribed channels and patterns:
|
||||
|
||||
```php
|
||||
$redis->on('subscribe', function ($channel, $total) {
|
||||
// subscribed to given $channel
|
||||
});
|
||||
$redis->on('psubscribe', function ($pattern, $total) {
|
||||
// subscribed to matching given $pattern
|
||||
});
|
||||
$redis->on('unsubscribe', function ($channel, $total) {
|
||||
// unsubscribed from given $channel
|
||||
});
|
||||
$redis->on('punsubscribe', function ($pattern, $total) {
|
||||
// unsubscribed from matching given $pattern
|
||||
});
|
||||
```
|
||||
|
||||
When using the [`createLazyClient()`](#createlazyclient) method, the `unsubscribe`
|
||||
and `punsubscribe` events will be invoked automatically when the underlying
|
||||
connection is lost. This gives you control over re-subscribing to the channels
|
||||
and patterns as appropriate.
|
||||
|
||||
## API
|
||||
|
||||
### Factory
|
||||
|
||||
The `Factory` is responsible for creating your [`Client`](#client) instance.
|
||||
|
||||
```php
|
||||
$factory = new Clue\React\Redis\Factory();
|
||||
```
|
||||
|
||||
This class takes an optional `LoopInterface|null $loop` parameter that can be used to
|
||||
pass the event loop instance to use for this object. You can use a `null` value
|
||||
here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
|
||||
This value SHOULD NOT be given unless you're sure you want to explicitly use a
|
||||
given event loop instance.
|
||||
|
||||
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
|
||||
)
|
||||
));
|
||||
|
||||
$factory = new Clue\React\Redis\Factory(null, $connector);
|
||||
```
|
||||
|
||||
#### createClient()
|
||||
|
||||
The `createClient(string $uri): PromiseInterface<Client,Exception>` method can be used to
|
||||
create a new [`Client`](#client).
|
||||
|
||||
It helps with establishing a plain TCP/IP or secure TLS connection to Redis
|
||||
and optionally authenticating (AUTH) and selecting the right database (SELECT).
|
||||
|
||||
```php
|
||||
$factory->createClient('localhost:6379')->then(
|
||||
function (Client $redis) {
|
||||
// client connected (and authenticated)
|
||||
},
|
||||
function (Exception $e) {
|
||||
// an error occurred while trying to connect (or authenticate) client
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
The method returns a [Promise](https://github.com/reactphp/promise) that
|
||||
will resolve with a [`Client`](#client)
|
||||
instance on success or will reject with an `Exception` if the URL is
|
||||
invalid or the connection or authentication fails.
|
||||
|
||||
The returned Promise is implemented in such a way that it can be
|
||||
cancelled when it is still pending. Cancelling a pending promise will
|
||||
reject its value with an Exception and will cancel the underlying TCP/IP
|
||||
connection attempt and/or Redis authentication.
|
||||
|
||||
```php
|
||||
$promise = $factory->createClient($uri);
|
||||
|
||||
Loop::addTimer(3.0, function () use ($promise) {
|
||||
$promise->cancel();
|
||||
});
|
||||
```
|
||||
|
||||
The `$redisUri` can be given in the
|
||||
[standard](https://www.iana.org/assignments/uri-schemes/prov/redis) form
|
||||
`[redis[s]://][:auth@]host[:port][/db]`.
|
||||
You can omit the URI scheme and port if you're connecting to the default port 6379:
|
||||
|
||||
```php
|
||||
// both are equivalent due to defaults being applied
|
||||
$factory->createClient('localhost');
|
||||
$factory->createClient('redis://localhost:6379');
|
||||
```
|
||||
|
||||
Redis supports password-based authentication (`AUTH` command). Note that Redis'
|
||||
authentication mechanism does not employ a username, so you can pass the
|
||||
password `h@llo` URL-encoded (percent-encoded) as part of the URI like this:
|
||||
|
||||
```php
|
||||
// all forms are equivalent
|
||||
$factory->createClient('redis://:h%40llo@localhost');
|
||||
$factory->createClient('redis://ignored:h%40llo@localhost');
|
||||
$factory->createClient('redis://localhost?password=h%40llo');
|
||||
```
|
||||
|
||||
You can optionally include a path that will be used to select (SELECT command) the right database:
|
||||
|
||||
```php
|
||||
// both forms are equivalent
|
||||
$factory->createClient('redis://localhost/2');
|
||||
$factory->createClient('redis://localhost?db=2');
|
||||
```
|
||||
|
||||
You can use the [standard](https://www.iana.org/assignments/uri-schemes/prov/rediss)
|
||||
`rediss://` URI scheme if you're using a secure TLS proxy in front of Redis:
|
||||
|
||||
```php
|
||||
$factory->createClient('rediss://redis.example.com:6340');
|
||||
```
|
||||
|
||||
You can use the `redis+unix://` URI scheme if your Redis instance is listening
|
||||
on a Unix domain socket (UDS) path:
|
||||
|
||||
```php
|
||||
$factory->createClient('redis+unix:///tmp/redis.sock');
|
||||
|
||||
// the URI MAY contain `password` and `db` query parameters as seen above
|
||||
$factory->createClient('redis+unix:///tmp/redis.sock?password=secret&db=2');
|
||||
|
||||
// the URI MAY contain authentication details as userinfo as seen above
|
||||
// should be used with care, also note that database can not be passed as path
|
||||
$factory->createClient('redis+unix://:secret@/tmp/redis.sock');
|
||||
```
|
||||
|
||||
This method respects PHP's `default_socket_timeout` setting (default 60s)
|
||||
as a timeout for establishing the connection and waiting for successful
|
||||
authentication. You can explicitly pass a custom timeout value in seconds
|
||||
(or use a negative number to not apply a timeout) like this:
|
||||
|
||||
```php
|
||||
$factory->createClient('localhost?timeout=0.5');
|
||||
```
|
||||
|
||||
#### createLazyClient()
|
||||
|
||||
The `createLazyClient(string $uri): Client` method can be used to
|
||||
create a new [`Client`](#client).
|
||||
|
||||
It helps with establishing a plain TCP/IP or secure TLS connection to Redis
|
||||
and optionally authenticating (AUTH) and selecting the right database (SELECT).
|
||||
|
||||
```php
|
||||
$redis = $factory->createLazyClient('localhost:6379');
|
||||
|
||||
$redis->incr('hello');
|
||||
$redis->end();
|
||||
```
|
||||
|
||||
This method immediately returns a "virtual" connection implementing the
|
||||
[`Client`](#client) that can be used to interface with your Redis database.
|
||||
Internally, it lazily creates the underlying database connection only on
|
||||
demand once the first request is invoked on this instance and will queue
|
||||
all outstanding requests until the underlying connection is ready.
|
||||
Additionally, it will only keep this underlying connection in an "idle" state
|
||||
for 60s by default and will automatically close the underlying connection when
|
||||
it is no longer needed.
|
||||
|
||||
From a consumer side this means that you can start sending commands to the
|
||||
database right away while the underlying connection may still be
|
||||
outstanding. Because creating this underlying connection may take some
|
||||
time, it will enqueue all oustanding commands and will ensure that all
|
||||
commands will be executed in correct order once the connection is ready.
|
||||
In other words, this "virtual" connection behaves just like a "real"
|
||||
connection as described in the `Client` interface and frees you from having
|
||||
to deal with its async resolution.
|
||||
|
||||
If the underlying database connection fails, it will reject all
|
||||
outstanding commands and will return to the initial "idle" state. This
|
||||
means that you can keep sending additional commands at a later time which
|
||||
will again try to open a new underlying connection. Note that this may
|
||||
require special care if you're using transactions (`MULTI`/`EXEC`) that are kept
|
||||
open for longer than the idle period.
|
||||
|
||||
While using PubSub channels (see `SUBSCRIBE` and `PSUBSCRIBE` commands), this client
|
||||
will never reach an "idle" state and will keep pending forever (or until the
|
||||
underlying database connection is lost). Additionally, if the underlying
|
||||
database connection drops, it will automatically send the appropriate `unsubscribe`
|
||||
and `punsubscribe` events for all currently active channel and pattern subscriptions.
|
||||
This allows you to react to these events and restore your subscriptions by
|
||||
creating a new underlying connection repeating the above commands again.
|
||||
|
||||
Note that creating the underlying connection will be deferred until the
|
||||
first request is invoked. Accordingly, any eventual connection issues
|
||||
will be detected once this instance is first used. You can use the
|
||||
`end()` method to ensure that the "virtual" connection will be soft-closed
|
||||
and no further commands can be enqueued. Similarly, calling `end()` on
|
||||
this instance when not currently connected will succeed immediately and
|
||||
will not have to wait for an actual underlying connection.
|
||||
|
||||
Depending on your particular use case, you may prefer this method or the
|
||||
underlying `createClient()` which resolves with a promise. For many
|
||||
simple use cases it may be easier to create a lazy connection.
|
||||
|
||||
The `$redisUri` can be given in the
|
||||
[standard](https://www.iana.org/assignments/uri-schemes/prov/redis) form
|
||||
`[redis[s]://][:auth@]host[:port][/db]`.
|
||||
You can omit the URI scheme and port if you're connecting to the default port 6379:
|
||||
|
||||
```php
|
||||
// both are equivalent due to defaults being applied
|
||||
$factory->createLazyClient('localhost');
|
||||
$factory->createLazyClient('redis://localhost:6379');
|
||||
```
|
||||
|
||||
Redis supports password-based authentication (`AUTH` command). Note that Redis'
|
||||
authentication mechanism does not employ a username, so you can pass the
|
||||
password `h@llo` URL-encoded (percent-encoded) as part of the URI like this:
|
||||
|
||||
```php
|
||||
// all forms are equivalent
|
||||
$factory->createLazyClient('redis://:h%40llo@localhost');
|
||||
$factory->createLazyClient('redis://ignored:h%40llo@localhost');
|
||||
$factory->createLazyClient('redis://localhost?password=h%40llo');
|
||||
```
|
||||
|
||||
You can optionally include a path that will be used to select (SELECT command) the right database:
|
||||
|
||||
```php
|
||||
// both forms are equivalent
|
||||
$factory->createLazyClient('redis://localhost/2');
|
||||
$factory->createLazyClient('redis://localhost?db=2');
|
||||
```
|
||||
|
||||
You can use the [standard](https://www.iana.org/assignments/uri-schemes/prov/rediss)
|
||||
`rediss://` URI scheme if you're using a secure TLS proxy in front of Redis:
|
||||
|
||||
```php
|
||||
$factory->createLazyClient('rediss://redis.example.com:6340');
|
||||
```
|
||||
|
||||
You can use the `redis+unix://` URI scheme if your Redis instance is listening
|
||||
on a Unix domain socket (UDS) path:
|
||||
|
||||
```php
|
||||
$factory->createLazyClient('redis+unix:///tmp/redis.sock');
|
||||
|
||||
// the URI MAY contain `password` and `db` query parameters as seen above
|
||||
$factory->createLazyClient('redis+unix:///tmp/redis.sock?password=secret&db=2');
|
||||
|
||||
// the URI MAY contain authentication details as userinfo as seen above
|
||||
// should be used with care, also note that database can not be passed as path
|
||||
$factory->createLazyClient('redis+unix://:secret@/tmp/redis.sock');
|
||||
```
|
||||
|
||||
This method respects PHP's `default_socket_timeout` setting (default 60s)
|
||||
as a timeout for establishing the underlying connection and waiting for
|
||||
successful authentication. You can explicitly pass a custom timeout value
|
||||
in seconds (or use a negative number to not apply a timeout) like this:
|
||||
|
||||
```php
|
||||
$factory->createLazyClient('localhost?timeout=0.5');
|
||||
```
|
||||
|
||||
By default, this method will keep "idle" connections open for 60s and will
|
||||
then end the underlying connection. The next request after an "idle"
|
||||
connection ended will automatically create a new underlying connection.
|
||||
This ensure you always get a "fresh" connection and as such should not be
|
||||
confused with a "keepalive" or "heartbeat" mechanism, as this will not
|
||||
actively try to probe the connection. You can explicitly pass a custom
|
||||
idle timeout value in seconds (or use a negative number to not apply a
|
||||
timeout) like this:
|
||||
|
||||
```php
|
||||
$factory->createLazyClient('localhost?idle=0.1');
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
The `Client` is responsible for exchanging messages with Redis
|
||||
and keeps track of pending commands.
|
||||
|
||||
Besides defining a few methods, this interface also implements the
|
||||
`EventEmitterInterface` which allows you to react to certain events as documented below.
|
||||
|
||||
#### __call()
|
||||
|
||||
The `__call(string $name, string[] $args): PromiseInterface<mixed,Exception>` method can be used to
|
||||
invoke the given command.
|
||||
|
||||
This is a magic method that will be invoked when calling any Redis command on this instance.
|
||||
Each method call matches the respective [Redis command](https://redis.io/commands).
|
||||
For example, the `$redis->get()` method will invoke the [`GET` command](https://redis.io/commands/get).
|
||||
|
||||
```php
|
||||
$redis->get($key)->then(function (?string $value) {
|
||||
var_dump($value);
|
||||
}, function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
All [Redis commands](https://redis.io/commands) are automatically available as
|
||||
public methods via this magic `__call()` method.
|
||||
Listing all available commands is out of scope here, please refer to the
|
||||
[Redis command reference](https://redis.io/commands).
|
||||
|
||||
Any arguments passed to the method call will be forwarded as command arguments.
|
||||
For example, the `$redis->set('name', 'Alice')` call will perform the equivalent of a
|
||||
`SET name Alice` command. It's safe to pass integer arguments where applicable (for
|
||||
example `$redis->expire($key, 60)`), but internally Redis requires all arguments to
|
||||
always be coerced to string values.
|
||||
|
||||
Each of these commands supports async operation and returns a [Promise](#promises)
|
||||
that eventually *fulfills* with its *results* on success or *rejects* with an
|
||||
`Exception` on error. See also [promises](#promises) for more details.
|
||||
|
||||
#### end()
|
||||
|
||||
The `end():void` method can be used to
|
||||
soft-close the Redis connection once all pending commands are completed.
|
||||
|
||||
#### close()
|
||||
|
||||
The `close():void` method can be used to
|
||||
force-close the Redis connection and reject all pending commands.
|
||||
|
||||
#### error event
|
||||
|
||||
The `error` event will be emitted once a fatal error occurs, such as
|
||||
when the client connection is lost or is invalid.
|
||||
The event receives a single `Exception` argument for the error instance.
|
||||
|
||||
```php
|
||||
$redis->on('error', function (Exception $e) {
|
||||
echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
This event will only be triggered for fatal errors and will be followed
|
||||
by closing the client connection. It is not to be confused with "soft"
|
||||
errors caused by invalid commands.
|
||||
|
||||
#### close event
|
||||
|
||||
The `close` event will be emitted once the client connection closes (terminates).
|
||||
|
||||
```php
|
||||
$redis->on('close', function () {
|
||||
echo 'Connection closed' . PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
See also the [`close()`](#close) method.
|
||||
|
||||
## 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/redis-react:^2.6
|
||||
```
|
||||
|
||||
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
This project aims to run on any platform and thus does not require any PHP
|
||||
extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
|
||||
HHVM.
|
||||
It's *highly recommended to use the latest supported PHP version* for this project.
|
||||
|
||||
## 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 both unit tests and functional integration tests.
|
||||
The functional tests require access to a running Redis server instance
|
||||
and will be skipped by default.
|
||||
|
||||
If you don't have access to a running Redis server, you can also use a temporary `Redis` Docker image:
|
||||
|
||||
```bash
|
||||
$ docker run --net=host redis
|
||||
```
|
||||
|
||||
To now run the functional tests, you need to supply *your* login
|
||||
details in an environment variable like this:
|
||||
|
||||
```bash
|
||||
$ REDIS_URI=localhost:6379 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.
|
32
vendor/clue/redis-react/composer.json
vendored
Normal file
32
vendor/clue/redis-react/composer.json
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "clue/redis-react",
|
||||
"description": "Async Redis client implementation, built on top of ReactPHP.",
|
||||
"keywords": ["Redis", "database", "client", "async", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-redis",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"clue/redis-protocol": "0.3.*",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^2.0 || ^1.1",
|
||||
"react/promise-timer": "^1.8",
|
||||
"react/socket": "^1.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.1",
|
||||
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Redis\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Redis\\": "tests/" }
|
||||
}
|
||||
}
|
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