Version v0.14.0-dev

This commit is contained in:
github-actions[bot] 2025-09-20 16:03:35 +00:00
parent f51759aeef
commit dda4f31dcc
184 changed files with 2723 additions and 607 deletions

View File

@ -1 +1 @@
v0.13.1
v0.14.0-dev

5
vendor/autoload.php vendored
View File

@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
throw new RuntimeException($err);
}
require_once __DIR__ . '/composer/autoload_real.php';

View File

@ -2,6 +2,23 @@
All notable changes to this project will be documented in this file.
## [0.14.0](https://github.com/brick/math/releases/tag/0.14.0) - 2025-08-29
✨ **New features**
- New methods: `BigInteger::clamp()` and `BigDecimal::clamp()` (#96 by @JesterIruka)
✨ **Improvements**
- All pure methods in `BigNumber` classes are now marked as `@pure` for better static analysis
💥 **Breaking changes**
- Minimum PHP version is now 8.2
- `BigNumber` classes are now `readonly`
- `BigNumber` is now marked as sealed: it must not be extended outside of this package
- Exception classes are now `final`
## [0.13.1](https://github.com/brick/math/releases/tag/0.13.1) - 2025-03-29
✨ **Improvements**

View File

@ -19,12 +19,12 @@
],
"license": "MIT",
"require": {
"php": "^8.1"
"php": "^8.2"
},
"require-dev": {
"phpunit/phpunit": "^10.1",
"phpunit/phpunit": "^11.5",
"php-coveralls/php-coveralls": "^2.2",
"vimeo/psalm": "6.8.8"
"phpstan/phpstan": "2.1.22"
},
"autoload": {
"psr-4": {

14
vendor/brick/math/phpstan.neon vendored Normal file
View File

@ -0,0 +1,14 @@
parameters:
level: 10
checkUninitializedProperties: true
paths:
- src
ignoreErrors:
- '~Impure call to function array_shift\(\) in pure method~'
- '~Possibly impure call to function assert\(\) in pure method~'
- '~Possibly impure call to function hex2bin\(\) in pure method~'
- '~Possibly impure call to function preg_match\(\) in pure method~'
- '~Impure static variable in pure method Brick\\Math\\Big(Integer|Decimal|Rational)::(zero|one|ten)\(\)~'
-
message: '~Parameter #\d \$\S+ of function bc\S+ expects numeric-string, string given~'
path: src/Internal/Calculator/BcMathCalculator.php

View File

@ -1,70 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.8.8@1361cd33008feb3ae2b4a93f1860e14e538ec8c2">
<file src="src/BigInteger.php">
<FalsableReturnStatement>
<code><![CDATA[\hex2bin($hex)]]></code>
</FalsableReturnStatement>
<InvalidFalsableReturnType>
<code><![CDATA[string]]></code>
</InvalidFalsableReturnType>
</file>
<file src="src/Exception/DivisionByZeroException.php">
<ClassMustBeFinal>
<code><![CDATA[DivisionByZeroException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Exception/IntegerOverflowException.php">
<ClassMustBeFinal>
<code><![CDATA[IntegerOverflowException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Exception/NegativeNumberException.php">
<ClassMustBeFinal>
<code><![CDATA[NegativeNumberException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Exception/NumberFormatException.php">
<ClassMustBeFinal>
<code><![CDATA[NumberFormatException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Exception/RoundingNecessaryException.php">
<ClassMustBeFinal>
<code><![CDATA[RoundingNecessaryException]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Internal/Calculator/BcMathCalculator.php">
<ClassMustBeFinal>
<code><![CDATA[BcMathCalculator]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Internal/Calculator/GmpCalculator.php">
<ClassMustBeFinal>
<code><![CDATA[GmpCalculator]]></code>
</ClassMustBeFinal>
</file>
<file src="src/Internal/Calculator/NativeCalculator.php">
<ClassMustBeFinal>
<code><![CDATA[NativeCalculator]]></code>
</ClassMustBeFinal>
<InvalidOperand>
<code><![CDATA[$a * $b]]></code>
<code><![CDATA[$a * 1]]></code>
<code><![CDATA[$a + $b]]></code>
<code><![CDATA[$b * 1]]></code>
<code><![CDATA[$b * 1]]></code>
<code><![CDATA[$blockA * $blockB + $carry]]></code>
<code><![CDATA[$blockA + $blockB]]></code>
<code><![CDATA[$blockA + $blockB + $carry]]></code>
<code><![CDATA[$blockA - $blockB]]></code>
<code><![CDATA[$blockA - $blockB - $carry]]></code>
<code><![CDATA[$carry]]></code>
<code><![CDATA[$mul % $complement]]></code>
<code><![CDATA[$mul - $value]]></code>
<code><![CDATA[$nb - 1]]></code>
<code><![CDATA[$sum += $complement]]></code>
<code><![CDATA[($mul - $value) / $complement]]></code>
<code><![CDATA[($nb - 1) * 10]]></code>
</InvalidOperand>
</file>
</files>

View File

@ -8,14 +8,13 @@ use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NegativeNumberException;
use Brick\Math\Internal\Calculator;
use Brick\Math\Internal\CalculatorRegistry;
use Override;
/**
* Immutable, arbitrary-precision signed decimal numbers.
*
* @psalm-immutable
*/
final class BigDecimal extends BigNumber
final readonly class BigDecimal extends BigNumber
{
/**
* The unscaled value of this decimal number.
@ -24,20 +23,22 @@ final class BigDecimal extends BigNumber
* No leading zero must be present.
* No leading minus sign must be present if the value is 0.
*/
private readonly string $value;
private string $value;
/**
* The scale (number of digits after the decimal point) of this decimal number.
*
* This must be zero or more.
*/
private readonly int $scale;
private int $scale;
/**
* Protected constructor. Use a factory method to obtain an instance.
*
* @param string $value The unscaled value, validated.
* @param int $scale The scale, validated.
*
* @pure
*/
protected function __construct(string $value, int $scale = 0)
{
@ -45,9 +46,6 @@ final class BigDecimal extends BigNumber
$this->scale = $scale;
}
/**
* @psalm-pure
*/
#[Override]
protected static function from(BigNumber $number): static
{
@ -63,7 +61,7 @@ final class BigDecimal extends BigNumber
* @param int $scale The scale of the number. If negative, the scale will be set to zero
* and the unscaled value will be adjusted accordingly.
*
* @psalm-pure
* @pure
*/
public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0) : BigDecimal
{
@ -82,14 +80,11 @@ final class BigDecimal extends BigNumber
/**
* Returns a BigDecimal representing zero, with a scale of zero.
*
* @psalm-pure
* @pure
*/
public static function zero() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $zero
*/
/** @var BigDecimal|null $zero */
static $zero;
if ($zero === null) {
@ -102,14 +97,11 @@ final class BigDecimal extends BigNumber
/**
* Returns a BigDecimal representing one, with a scale of zero.
*
* @psalm-pure
* @pure
*/
public static function one() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $one
*/
/** @var BigDecimal|null $one */
static $one;
if ($one === null) {
@ -122,14 +114,11 @@ final class BigDecimal extends BigNumber
/**
* Returns a BigDecimal representing ten, with a scale of zero.
*
* @psalm-pure
* @pure
*/
public static function ten() : BigDecimal
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigDecimal|null $ten
*/
/** @var BigDecimal|null $ten */
static $ten;
if ($ten === null) {
@ -147,6 +136,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigDecimal.
*
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
*
* @pure
*/
public function plus(BigNumber|int|float|string $that) : BigDecimal
{
@ -162,7 +153,7 @@ final class BigDecimal extends BigNumber
[$a, $b] = $this->scaleValues($this, $that);
$value = Calculator::get()->add($a, $b);
$value = CalculatorRegistry::get()->add($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($value, $scale);
@ -176,6 +167,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigDecimal.
*
* @throws MathException If the number is not valid, or is not convertible to a BigDecimal.
*
* @pure
*/
public function minus(BigNumber|int|float|string $that) : BigDecimal
{
@ -187,7 +180,7 @@ final class BigDecimal extends BigNumber
[$a, $b] = $this->scaleValues($this, $that);
$value = Calculator::get()->sub($a, $b);
$value = CalculatorRegistry::get()->sub($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
return new BigDecimal($value, $scale);
@ -201,6 +194,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
*
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
*
* @pure
*/
public function multipliedBy(BigNumber|int|float|string $that) : BigDecimal
{
@ -214,7 +209,7 @@ final class BigDecimal extends BigNumber
return $that;
}
$value = Calculator::get()->mul($this->value, $that->value);
$value = CalculatorRegistry::get()->mul($this->value, $that->value);
$scale = $this->scale + $that->scale;
return new BigDecimal($value, $scale);
@ -229,6 +224,8 @@ final class BigDecimal extends BigNumber
*
* @throws \InvalidArgumentException If the scale or rounding mode is invalid.
* @throws MathException If the number is invalid, is zero, or rounding was necessary.
*
* @pure
*/
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal
{
@ -251,7 +248,7 @@ final class BigDecimal extends BigNumber
$p = $this->valueWithMinScale($that->scale + $scale);
$q = $that->valueWithMinScale($this->scale - $scale);
$result = Calculator::get()->divRound($p, $q, $roundingMode);
$result = CalculatorRegistry::get()->divRound($p, $q, $roundingMode);
return new BigDecimal($result, $scale);
}
@ -265,6 +262,8 @@ final class BigDecimal extends BigNumber
*
* @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.
*
* @pure
*/
public function exactlyDividedBy(BigNumber|int|float|string $that) : BigDecimal
{
@ -279,7 +278,7 @@ final class BigDecimal extends BigNumber
$d = \rtrim($b, '0');
$scale = \strlen($b) - \strlen($d);
$calculator = Calculator::get();
$calculator = CalculatorRegistry::get();
foreach ([5, 2] as $prime) {
for (;;) {
@ -297,12 +296,36 @@ final class BigDecimal extends BigNumber
return $this->dividedBy($that, $scale)->stripTrailingZeros();
}
/**
* Limits (clamps) this number between the given minimum and maximum values.
*
* If the number is lower than $min, returns a copy of $min.
* If the number is greater than $max, returns a copy of $max.
* Otherwise, returns this number unchanged.
*
* @param BigNumber|int|float|string $min The minimum. Must be convertible to a BigDecimal.
* @param BigNumber|int|float|string $max The maximum. Must be convertible to a BigDecimal.
*
* @throws MathException If min/max are not convertible to a BigDecimal.
*/
public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max) : BigDecimal
{
if ($this->isLessThan($min)) {
return BigDecimal::of($min);
} elseif ($this->isGreaterThan($max)) {
return BigDecimal::of($max);
}
return $this;
}
/**
* Returns this number exponentiated to the given value.
*
* The result has a scale of `$this->scale * $exponent`.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*
* @pure
*/
public function power(int $exponent) : BigDecimal
{
@ -322,7 +345,7 @@ final class BigDecimal extends BigNumber
));
}
return new BigDecimal(Calculator::get()->pow($this->value, $exponent), $this->scale * $exponent);
return new BigDecimal(CalculatorRegistry::get()->pow($this->value, $exponent), $this->scale * $exponent);
}
/**
@ -333,6 +356,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*
* @pure
*/
public function quotient(BigNumber|int|float|string $that) : BigDecimal
{
@ -345,7 +370,7 @@ final class BigDecimal extends BigNumber
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
$quotient = Calculator::get()->divQ($p, $q);
$quotient = CalculatorRegistry::get()->divQ($p, $q);
return new BigDecimal($quotient, 0);
}
@ -358,6 +383,8 @@ final class BigDecimal extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*
* @pure
*/
public function remainder(BigNumber|int|float|string $that) : BigDecimal
{
@ -370,7 +397,7 @@ final class BigDecimal extends BigNumber
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
$remainder = Calculator::get()->divR($p, $q);
$remainder = CalculatorRegistry::get()->divR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
@ -384,11 +411,11 @@ final class BigDecimal extends BigNumber
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @return BigDecimal[] An array containing the quotient and the remainder.
*
* @psalm-return array{BigDecimal, BigDecimal}
* @return array{BigDecimal, BigDecimal} An array containing the quotient and the remainder.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
*
* @pure
*/
public function quotientAndRemainder(BigNumber|int|float|string $that) : array
{
@ -401,7 +428,7 @@ final class BigDecimal extends BigNumber
$p = $this->valueWithMinScale($that->scale);
$q = $that->valueWithMinScale($this->scale);
[$quotient, $remainder] = Calculator::get()->divQR($p, $q);
[$quotient, $remainder] = CalculatorRegistry::get()->divQR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
@ -416,6 +443,8 @@ final class BigDecimal extends BigNumber
*
* @throws \InvalidArgumentException If the scale is negative.
* @throws NegativeNumberException If this number is negative.
*
* @pure
*/
public function sqrt(int $scale) : BigDecimal
{
@ -447,13 +476,15 @@ final class BigDecimal extends BigNumber
$value = \substr($value, 0, $addDigits);
}
$value = Calculator::get()->sqrt($value);
$value = CalculatorRegistry::get()->sqrt($value);
return new BigDecimal($value, $scale);
}
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
*
* @pure
*/
public function withPointMovedLeft(int $n) : BigDecimal
{
@ -470,6 +501,8 @@ final class BigDecimal extends BigNumber
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
*
* @pure
*/
public function withPointMovedRight(int $n) : BigDecimal
{
@ -496,6 +529,8 @@ final class BigDecimal extends BigNumber
/**
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
*
* @pure
*/
public function stripTrailingZeros() : BigDecimal
{
@ -527,6 +562,8 @@ final class BigDecimal extends BigNumber
/**
* Returns the absolute value of this number.
*
* @pure
*/
public function abs() : BigDecimal
{
@ -535,10 +572,12 @@ final class BigDecimal extends BigNumber
/**
* Returns the negated value of this number.
*
* @pure
*/
public function negated() : BigDecimal
{
return new BigDecimal(Calculator::get()->neg($this->value), $this->scale);
return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
}
#[Override]
@ -553,7 +592,7 @@ final class BigDecimal extends BigNumber
if ($that instanceof BigDecimal) {
[$a, $b] = $this->scaleValues($this, $that);
return Calculator::get()->cmp($a, $b);
return CalculatorRegistry::get()->cmp($a, $b);
}
return - $that->compareTo($this);
@ -565,11 +604,17 @@ final class BigDecimal extends BigNumber
return ($this->value === '0') ? 0 : (($this->value[0] === '-') ? -1 : 1);
}
/**
* @pure
*/
public function getUnscaledValue() : BigInteger
{
return self::newBigInteger($this->value);
}
/**
* @pure
*/
public function getScale() : int
{
return $this->scale;
@ -588,6 +633,8 @@ final class BigDecimal extends BigNumber
* 123.456 => 6
* 0.00123 => 3
* 0.0012300 => 5
*
* @pure
*/
public function getPrecision(): int
{
@ -606,6 +653,8 @@ final class BigDecimal extends BigNumber
* Returns a string representing the integral part of this decimal number.
*
* Example: `-123.456` => `-123`.
*
* @pure
*/
public function getIntegralPart() : string
{
@ -624,6 +673,8 @@ final class BigDecimal extends BigNumber
* If the scale is zero, an empty string is returned.
*
* Examples: `-123.456` => '456', `123` => ''.
*
* @pure
*/
public function getFractionalPart() : string
{
@ -638,6 +689,8 @@ final class BigDecimal extends BigNumber
/**
* Returns whether this decimal number has a non-zero fractional part.
*
* @pure
*/
public function hasNonZeroFractionalPart() : bool
{
@ -702,7 +755,7 @@ final class BigDecimal extends BigNumber
$value = $this->getUnscaledValueWithLeadingZeros();
/** @var numeric-string */
/** @phpstan-ignore return.type */
return \substr($value, 0, -$this->scale) . '.' . \substr($value, -$this->scale);
}
@ -722,7 +775,6 @@ final class BigDecimal extends BigNumber
* 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
*
@ -730,10 +782,12 @@ final class BigDecimal extends BigNumber
*/
public function __unserialize(array $data): void
{
/** @phpstan-ignore isset.initializedProperty */
if (isset($this->value)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
/** @phpstan-ignore deadCode.unreachable */
$this->value = $data['value'];
$this->scale = $data['scale'];
}
@ -742,6 +796,8 @@ final class BigDecimal extends BigNumber
* Puts the internal values of the given decimal numbers on the same scale.
*
* @return array{string, string} The scaled integer values of $x and $y.
*
* @pure
*/
private function scaleValues(BigDecimal $x, BigDecimal $y) : array
{
@ -757,6 +813,9 @@ final class BigDecimal extends BigNumber
return [$a, $b];
}
/**
* @pure
*/
private function valueWithMinScale(int $scale) : string
{
$value = $this->value;
@ -770,6 +829,8 @@ final class BigDecimal extends BigNumber
/**
* Adds leading zeros if necessary to the unscaled value to represent the full decimal number.
*
* @pure
*/
private function getUnscaledValueWithLeadingZeros() : string
{

View File

@ -10,6 +10,7 @@ use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NegativeNumberException;
use Brick\Math\Exception\NumberFormatException;
use Brick\Math\Internal\Calculator;
use Brick\Math\Internal\CalculatorRegistry;
use Override;
/**
@ -17,10 +18,8 @@ use Override;
*
* All methods accepting a number as a parameter accept either a BigInteger instance,
* an integer, or a string representing an arbitrary size integer.
*
* @psalm-immutable
*/
final class BigInteger extends BigNumber
final readonly class BigInteger extends BigNumber
{
/**
* The value, as a string of digits with optional leading minus sign.
@ -28,21 +27,20 @@ final class BigInteger extends BigNumber
* No leading zeros must be present.
* No leading minus sign must be present if the number is zero.
*/
private readonly string $value;
private string $value;
/**
* Protected constructor. Use a factory method to obtain an instance.
*
* @param string $value A string of digits, with optional leading minus sign.
*
* @pure
*/
protected function __construct(string $value)
{
$this->value = $value;
}
/**
* @psalm-pure
*/
#[Override]
protected static function from(BigNumber $number): static
{
@ -66,7 +64,7 @@ final class BigInteger extends BigNumber
* @throws NumberFormatException If the number is empty, or contains invalid chars for the given base.
* @throws \InvalidArgumentException If the base is out of range.
*
* @psalm-pure
* @pure
*/
public static function fromBase(string $number, int $base) : BigInteger
{
@ -115,7 +113,7 @@ final class BigInteger extends BigNumber
return new BigInteger($sign . $number);
}
$result = Calculator::get()->fromBase($number, $base);
$result = CalculatorRegistry::get()->fromBase($number, $base);
return new BigInteger($sign . $result);
}
@ -131,7 +129,7 @@ final class BigInteger extends BigNumber
* @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet.
* @throws \InvalidArgumentException If the alphabet does not contain at least 2 chars.
*
* @psalm-pure
* @pure
*/
public static function fromArbitraryBase(string $number, string $alphabet) : BigInteger
{
@ -151,7 +149,7 @@ final class BigInteger extends BigNumber
throw NumberFormatException::charNotInAlphabet($matches[0]);
}
$number = Calculator::get()->fromArbitraryBase($number, $alphabet, $base);
$number = CalculatorRegistry::get()->fromArbitraryBase($number, $alphabet, $base);
return new BigInteger($number);
}
@ -172,6 +170,8 @@ final class BigInteger extends BigNumber
* sign bit.
*
* @throws NumberFormatException If the string is empty.
*
* @pure
*/
public static function fromBytes(string $value, bool $signed = true) : BigInteger
{
@ -203,12 +203,10 @@ final class BigInteger extends BigNumber
*
* Using the default random bytes generator, this method is suitable for cryptographic use.
*
* @psalm-param (callable(int): string)|null $randomBytesGenerator
*
* @param int $numBits The number of bits.
* @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer, and returns a
* string of random bytes of the given length. Defaults to the
* `random_bytes()` function.
* @param int $numBits The number of bits.
* @param (callable(int): string)|null $randomBytesGenerator A function that accepts a number of bytes, and returns
* a string of random bytes of the given length. Defaults
* to the `random_bytes()` function.
*
* @throws \InvalidArgumentException If $numBits is negative.
*/
@ -243,13 +241,11 @@ final class BigInteger extends BigNumber
*
* Using the default random bytes generator, this method is suitable for cryptographic use.
*
* @psalm-param (callable(int): string)|null $randomBytesGenerator
*
* @param BigNumber|int|float|string $min The lower bound. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $max The upper bound. Must be convertible to a BigInteger.
* @param callable|null $randomBytesGenerator A function that accepts a number of bytes as an integer,
* and returns a string of random bytes of the given length.
* Defaults to the `random_bytes()` function.
* @param BigNumber|int|float|string $min The lower bound. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $max The upper bound. Must be convertible to a BigInteger.
* @param (callable(int): string)|null $randomBytesGenerator A function that accepts a number of bytes, and returns
* a string of random bytes of the given length. Defaults
* to the `random_bytes()` function.
*
* @throws MathException If one of the parameters cannot be converted to a BigInteger,
* or `$min` is greater than `$max`.
@ -284,14 +280,11 @@ final class BigInteger extends BigNumber
/**
* Returns a BigInteger representing zero.
*
* @psalm-pure
* @pure
*/
public static function zero() : BigInteger
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigInteger|null $zero
*/
/** @var BigInteger|null $zero */
static $zero;
if ($zero === null) {
@ -304,14 +297,11 @@ final class BigInteger extends BigNumber
/**
* Returns a BigInteger representing one.
*
* @psalm-pure
* @pure
*/
public static function one() : BigInteger
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigInteger|null $one
*/
/** @var BigInteger|null $one */
static $one;
if ($one === null) {
@ -324,14 +314,11 @@ final class BigInteger extends BigNumber
/**
* Returns a BigInteger representing ten.
*
* @psalm-pure
* @pure
*/
public static function ten() : BigInteger
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigInteger|null $ten
*/
/** @var BigInteger|null $ten */
static $ten;
if ($ten === null) {
@ -341,6 +328,9 @@ final class BigInteger extends BigNumber
return $ten;
}
/**
* @pure
*/
public static function gcdMultiple(BigInteger $a, BigInteger ...$n): BigInteger
{
$result = $a;
@ -362,6 +352,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The number to add. Must be convertible to a BigInteger.
*
* @throws MathException If the number is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
public function plus(BigNumber|int|float|string $that) : BigInteger
{
@ -375,7 +367,7 @@ final class BigInteger extends BigNumber
return $that;
}
$value = Calculator::get()->add($this->value, $that->value);
$value = CalculatorRegistry::get()->add($this->value, $that->value);
return new BigInteger($value);
}
@ -386,6 +378,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The number to subtract. Must be convertible to a BigInteger.
*
* @throws MathException If the number is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
public function minus(BigNumber|int|float|string $that) : BigInteger
{
@ -395,7 +389,7 @@ final class BigInteger extends BigNumber
return $this;
}
$value = Calculator::get()->sub($this->value, $that->value);
$value = CalculatorRegistry::get()->sub($this->value, $that->value);
return new BigInteger($value);
}
@ -406,6 +400,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigInteger.
*
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigInteger.
*
* @pure
*/
public function multipliedBy(BigNumber|int|float|string $that) : BigInteger
{
@ -419,7 +415,7 @@ final class BigInteger extends BigNumber
return $that;
}
$value = Calculator::get()->mul($this->value, $that->value);
$value = CalculatorRegistry::get()->mul($this->value, $that->value);
return new BigInteger($value);
}
@ -432,6 +428,8 @@ final class BigInteger extends BigNumber
*
* @throws MathException If the divisor is not a valid number, is not convertible to a BigInteger, is zero,
* or RoundingMode::UNNECESSARY is used and the remainder is not zero.
*
* @pure
*/
public function dividedBy(BigNumber|int|float|string $that, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigInteger
{
@ -445,15 +443,40 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::divisionByZero();
}
$result = Calculator::get()->divRound($this->value, $that->value, $roundingMode);
$result = CalculatorRegistry::get()->divRound($this->value, $that->value, $roundingMode);
return new BigInteger($result);
}
/**
* Limits (clamps) this number between the given minimum and maximum values.
*
* If the number is lower than $min, returns a copy of $min.
* If the number is greater than $max, returns a copy of $max.
* Otherwise, returns this number unchanged.
*
* @param BigNumber|int|float|string $min The minimum. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $max The maximum. Must be convertible to a BigInteger.
*
* @throws MathException If min/max are not convertible to a BigInteger.
*/
public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max) : BigInteger
{
if ($this->isLessThan($min)) {
return BigInteger::of($min);
} elseif ($this->isGreaterThan($max)) {
return BigInteger::of($max);
}
return $this;
}
/**
* Returns this number exponentiated to the given value.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*
* @pure
*/
public function power(int $exponent) : BigInteger
{
@ -473,7 +496,7 @@ final class BigInteger extends BigNumber
));
}
return new BigInteger(Calculator::get()->pow($this->value, $exponent));
return new BigInteger(CalculatorRegistry::get()->pow($this->value, $exponent));
}
/**
@ -482,6 +505,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
public function quotient(BigNumber|int|float|string $that) : BigInteger
{
@ -495,7 +520,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::divisionByZero();
}
$quotient = Calculator::get()->divQ($this->value, $that->value);
$quotient = CalculatorRegistry::get()->divQ($this->value, $that->value);
return new BigInteger($quotient);
}
@ -508,6 +533,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
public function remainder(BigNumber|int|float|string $that) : BigInteger
{
@ -521,7 +548,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::divisionByZero();
}
$remainder = Calculator::get()->divR($this->value, $that->value);
$remainder = CalculatorRegistry::get()->divR($this->value, $that->value);
return new BigInteger($remainder);
}
@ -531,11 +558,11 @@ final class BigInteger extends BigNumber
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @return BigInteger[] An array containing the quotient and the remainder.
*
* @psalm-return array{BigInteger, BigInteger}
* @return array{BigInteger, BigInteger} An array containing the quotient and the remainder.
*
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
public function quotientAndRemainder(BigNumber|int|float|string $that) : array
{
@ -545,7 +572,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::divisionByZero();
}
[$quotient, $remainder] = Calculator::get()->divQR($this->value, $that->value);
[$quotient, $remainder] = CalculatorRegistry::get()->divQR($this->value, $that->value);
return [
new BigInteger($quotient),
@ -564,6 +591,8 @@ final class BigInteger extends BigNumber
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
public function mod(BigNumber|int|float|string $that) : BigInteger
{
@ -573,7 +602,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::modulusMustNotBeZero();
}
$value = Calculator::get()->mod($this->value, $that->value);
$value = CalculatorRegistry::get()->mod($this->value, $that->value);
return new BigInteger($value);
}
@ -585,6 +614,8 @@ final class BigInteger extends BigNumber
* @throws NegativeNumberException If $m is negative.
* @throws MathException If this BigInteger has no multiplicative inverse mod m (that is, this BigInteger
* is not relatively prime to m).
*
* @pure
*/
public function modInverse(BigInteger $m) : BigInteger
{
@ -600,7 +631,7 @@ final class BigInteger extends BigNumber
return BigInteger::zero();
}
$value = Calculator::get()->modInverse($this->value, $m->value);
$value = CalculatorRegistry::get()->modInverse($this->value, $m->value);
if ($value === null) {
throw new MathException('Unable to compute the modInverse for the given modulus.');
@ -619,6 +650,8 @@ final class BigInteger extends BigNumber
*
* @throws NegativeNumberException If any of the operands is negative.
* @throws DivisionByZeroException If the modulus is zero.
*
* @pure
*/
public function modPow(BigNumber|int|float|string $exp, BigNumber|int|float|string $mod) : BigInteger
{
@ -633,7 +666,7 @@ final class BigInteger extends BigNumber
throw DivisionByZeroException::modulusMustNotBeZero();
}
$result = Calculator::get()->modPow($this->value, $exp->value, $mod->value);
$result = CalculatorRegistry::get()->modPow($this->value, $exp->value, $mod->value);
return new BigInteger($result);
}
@ -644,6 +677,8 @@ final class BigInteger extends BigNumber
* The GCD is always positive, unless both operands are zero, in which case it is zero.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
*
* @pure
*/
public function gcd(BigNumber|int|float|string $that) : BigInteger
{
@ -657,7 +692,7 @@ final class BigInteger extends BigNumber
return $that;
}
$value = Calculator::get()->gcd($this->value, $that->value);
$value = CalculatorRegistry::get()->gcd($this->value, $that->value);
return new BigInteger($value);
}
@ -668,6 +703,8 @@ final class BigInteger extends BigNumber
* The result is the largest x such that n.
*
* @throws NegativeNumberException If this number is negative.
*
* @pure
*/
public function sqrt() : BigInteger
{
@ -675,13 +712,15 @@ final class BigInteger extends BigNumber
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
}
$value = Calculator::get()->sqrt($this->value);
$value = CalculatorRegistry::get()->sqrt($this->value);
return new BigInteger($value);
}
/**
* Returns the absolute value of this number.
*
* @pure
*/
public function abs() : BigInteger
{
@ -690,10 +729,12 @@ final class BigInteger extends BigNumber
/**
* Returns the inverse of this number.
*
* @pure
*/
public function negated() : BigInteger
{
return new BigInteger(Calculator::get()->neg($this->value));
return new BigInteger(CalculatorRegistry::get()->neg($this->value));
}
/**
@ -702,12 +743,14 @@ final class BigInteger extends BigNumber
* This method returns a negative BigInteger if and only if both operands are negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
*
* @pure
*/
public function and(BigNumber|int|float|string $that) : BigInteger
{
$that = BigInteger::of($that);
return new BigInteger(Calculator::get()->and($this->value, $that->value));
return new BigInteger(CalculatorRegistry::get()->and($this->value, $that->value));
}
/**
@ -716,12 +759,14 @@ final class BigInteger extends BigNumber
* This method returns a negative BigInteger if and only if either of the operands is negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
*
* @pure
*/
public function or(BigNumber|int|float|string $that) : BigInteger
{
$that = BigInteger::of($that);
return new BigInteger(Calculator::get()->or($this->value, $that->value));
return new BigInteger(CalculatorRegistry::get()->or($this->value, $that->value));
}
/**
@ -730,16 +775,20 @@ final class BigInteger extends BigNumber
* This method returns a negative BigInteger if and only if exactly one of the operands is negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
*
* @pure
*/
public function xor(BigNumber|int|float|string $that) : BigInteger
{
$that = BigInteger::of($that);
return new BigInteger(Calculator::get()->xor($this->value, $that->value));
return new BigInteger(CalculatorRegistry::get()->xor($this->value, $that->value));
}
/**
* Returns the bitwise-not of this BigInteger.
*
* @pure
*/
public function not() : BigInteger
{
@ -748,6 +797,8 @@ final class BigInteger extends BigNumber
/**
* Returns the integer left shifted by a given number of bits.
*
* @pure
*/
public function shiftedLeft(int $distance) : BigInteger
{
@ -764,6 +815,8 @@ final class BigInteger extends BigNumber
/**
* Returns the integer right shifted by a given number of bits.
*
* @pure
*/
public function shiftedRight(int $distance) : BigInteger
{
@ -789,6 +842,8 @@ final class BigInteger extends BigNumber
*
* For positive BigIntegers, this is equivalent to the number of bits in the ordinary binary representation.
* Computes (ceil(log2(this < 0 ? -this : this+1))).
*
* @pure
*/
public function getBitLength() : int
{
@ -807,6 +862,8 @@ final class BigInteger extends BigNumber
* Returns the index of the rightmost (lowest-order) one bit in this BigInteger.
*
* Returns -1 if this BigInteger contains no one bits.
*
* @pure
*/
public function getLowestSetBit() : int
{
@ -826,6 +883,8 @@ final class BigInteger extends BigNumber
/**
* Returns whether this number is even.
*
* @pure
*/
public function isEven() : bool
{
@ -834,6 +893,8 @@ final class BigInteger extends BigNumber
/**
* Returns whether this number is odd.
*
* @pure
*/
public function isOdd() : bool
{
@ -848,6 +909,8 @@ final class BigInteger extends BigNumber
* @param int $n The bit to test, 0-based.
*
* @throws \InvalidArgumentException If the bit to test is negative.
*
* @pure
*/
public function testBit(int $n) : bool
{
@ -864,7 +927,7 @@ final class BigInteger extends BigNumber
$that = BigNumber::of($that);
if ($that instanceof BigInteger) {
return Calculator::get()->cmp($this->value, $that->value);
return CalculatorRegistry::get()->cmp($this->value, $that->value);
}
return - $that->compareTo($this);
@ -924,6 +987,8 @@ final class BigInteger extends BigNumber
* The output will always be lowercase for bases greater than 10.
*
* @throws \InvalidArgumentException If the base is out of range.
*
* @pure
*/
public function toBase(int $base) : string
{
@ -935,7 +1000,7 @@ final class BigInteger extends BigNumber
throw new \InvalidArgumentException(\sprintf('Base %d is out of range [2, 36]', $base));
}
return Calculator::get()->toBase($this->value, $base);
return CalculatorRegistry::get()->toBase($this->value, $base);
}
/**
@ -948,6 +1013,8 @@ final class BigInteger extends BigNumber
*
* @throws NegativeNumberException If this number is negative.
* @throws \InvalidArgumentException If the given alphabet does not contain at least 2 chars.
*
* @pure
*/
public function toArbitraryBase(string $alphabet) : string
{
@ -961,7 +1028,7 @@ final class BigInteger extends BigNumber
throw new NegativeNumberException(__FUNCTION__ . '() does not support negative numbers.');
}
return Calculator::get()->toArbitraryBase($this->value, $alphabet, $base);
return CalculatorRegistry::get()->toArbitraryBase($this->value, $alphabet, $base);
}
/**
@ -981,6 +1048,8 @@ final class BigInteger extends BigNumber
* @param bool $signed Whether to output a signed number in two's-complement representation with a leading sign bit.
*
* @throws NegativeNumberException If $signed is false, and the number is negative.
*
* @pure
*/
public function toBytes(bool $signed = true) : string
{
@ -1020,7 +1089,10 @@ final class BigInteger extends BigNumber
}
}
return \hex2bin($hex);
$result = \hex2bin($hex);
assert($result !== false);
return $result;
}
/**
@ -1049,7 +1121,6 @@ final class BigInteger extends BigNumber
* This method is only here to allow unserializing the object and cannot be accessed directly.
*
* @internal
* @psalm-suppress RedundantPropertyInitializationCheck
*
* @param array{value: string} $data
*
@ -1057,10 +1128,12 @@ final class BigInteger extends BigNumber
*/
public function __unserialize(array $data): void
{
/** @phpstan-ignore isset.initializedProperty */
if (isset($this->value)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
/** @phpstan-ignore deadCode.unreachable */
$this->value = $data['value'];
}
}

View File

@ -11,11 +11,14 @@ use Brick\Math\Exception\RoundingNecessaryException;
use Override;
/**
* Common interface for arbitrary-precision rational numbers.
* Base class for arbitrary-precision numbers.
*
* @psalm-immutable
* This class is sealed: it is part of the public API but should not be subclassed in userland.
* Protected methods may change in any version.
*
* @phpstan-sealed BigInteger|BigDecimal|BigRational
*/
abstract class BigNumber implements \JsonSerializable
abstract readonly class BigNumber implements \JsonSerializable, \Stringable
{
/**
* The regular expression used to parse integer or decimal numbers.
@ -43,7 +46,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Creates a BigNumber of the given value.
*
* The concrete return type is dependent on the given value, with the following rules:
* When of() is called on BigNumber, 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
@ -52,18 +56,20 @@ abstract class BigNumber implements \JsonSerializable
* - 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
*
* When of() is called on BigInteger, BigDecimal, or BigRational, the resulting number is converted to an instance
* of the subclass when possible; otherwise a RoundingNecessaryException exception is thrown.
*
* @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.
* @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
*
* @psalm-pure
* @pure
*/
final public static function of(BigNumber|int|float|string $value) : static
{
$value = self::_of($value);
if (static::class === BigNumber::class) {
// https://github.com/vimeo/psalm/issues/10309
assert($value instanceof static);
return $value;
@ -76,7 +82,7 @@ abstract class BigNumber implements \JsonSerializable
* @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
* @pure
*/
private static function _of(BigNumber|int|float|string $value) : BigNumber
{
@ -102,9 +108,6 @@ abstract class BigNumber implements \JsonSerializable
$numerator = $matches['numerator'];
$denominator = $matches['denominator'];
assert($numerator !== null);
assert($denominator !== null);
$numerator = self::cleanUp($sign, $numerator);
$denominator = self::cleanUp(null, $denominator);
@ -138,7 +141,7 @@ abstract class BigNumber implements \JsonSerializable
}
if ($point !== null || $exponent !== null) {
$fractional = ($fractional ?? '');
$fractional ??= '';
$exponent = ($exponent !== null) ? (int)$exponent : 0;
if ($exponent === PHP_INT_MIN || $exponent === PHP_INT_MAX) {
@ -170,15 +173,15 @@ abstract class BigNumber implements \JsonSerializable
*
* @throws RoundingNecessaryException If the value cannot be converted.
*
* @psalm-pure
* @pure
*/
abstract protected static function from(BigNumber $number): static;
/**
* Proxy method to access BigInteger's protected constructor from sibling classes.
*
* @pure
* @internal
* @psalm-pure
*/
final protected function newBigInteger(string $value) : BigInteger
{
@ -188,8 +191,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Proxy method to access BigDecimal's protected constructor from sibling classes.
*
* @pure
* @internal
* @psalm-pure
*/
final protected function newBigDecimal(string $value, int $scale = 0) : BigDecimal
{
@ -199,8 +202,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Proxy method to access BigRational's protected constructor from sibling classes.
*
* @pure
* @internal
* @psalm-pure
*/
final protected function newBigRational(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator) : BigRational
{
@ -216,7 +219,7 @@ abstract class BigNumber implements \JsonSerializable
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
* @pure
*/
final public static function min(BigNumber|int|float|string ...$values) : static
{
@ -246,7 +249,7 @@ abstract class BigNumber implements \JsonSerializable
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
* @pure
*/
final public static function max(BigNumber|int|float|string ...$values) : static
{
@ -270,41 +273,43 @@ abstract class BigNumber implements \JsonSerializable
/**
* 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.
* When called on BigNumber, sum() accepts any supported type and returns a result whose type is the widest among
* the given values (BigInteger < BigDecimal < BigRational).
*
* When called on BigInteger, BigDecimal, or BigRational, sum() requires that all values can be converted to that
* specific subclass, and returns a result of the same type.
*
* @param BigNumber|int|float|string ...$values The values to add. All values must be convertible to the class on
* which this method is called.
*
* @throws \InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
*
* @psalm-pure
* @pure
*/
final public static function sum(BigNumber|int|float|string ...$values) : static
{
/** @var static|null $sum */
$sum = null;
$first = array_shift($values);
foreach ($values as $value) {
$value = static::of($value);
$sum = $sum === null ? $value : self::add($sum, $value);
}
if ($sum === null) {
if ($first === null) {
throw new \InvalidArgumentException(__METHOD__ . '() expects at least one value.');
}
$sum = static::of($first);
foreach ($values as $value) {
$sum = self::add($sum, static::of($value));
}
assert($sum instanceof static);
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...)
*
* @psalm-pure
* @pure
*/
private static function add(BigNumber $a, BigNumber $b) : BigNumber
{
@ -324,8 +329,6 @@ abstract class BigNumber implements \JsonSerializable
return $b->plus($a);
}
/** @var BigInteger $a */
return $a->plus($b);
}
@ -333,9 +336,9 @@ abstract class BigNumber implements \JsonSerializable
* Removes optional leading zeros and applies sign.
*
* @param string|null $sign The sign, '+' or '-', optional. Null is allowed for convenience and treated as '+'.
* @param string $number The number, validated as a non-empty string of digits.
* @param string $number The number, validated as a string of digits.
*
* @psalm-pure
* @pure
*/
private static function cleanUp(string|null $sign, string $number) : string
{
@ -350,6 +353,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is equal to the given one.
*
* @pure
*/
final public function isEqualTo(BigNumber|int|float|string $that) : bool
{
@ -358,6 +363,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is strictly lower than the given one.
*
* @pure
*/
final public function isLessThan(BigNumber|int|float|string $that) : bool
{
@ -366,6 +373,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is lower than or equal to the given one.
*
* @pure
*/
final public function isLessThanOrEqualTo(BigNumber|int|float|string $that) : bool
{
@ -374,6 +383,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is strictly greater than the given one.
*
* @pure
*/
final public function isGreaterThan(BigNumber|int|float|string $that) : bool
{
@ -382,6 +393,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is greater than or equal to the given one.
*
* @pure
*/
final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that) : bool
{
@ -390,6 +403,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number equals zero.
*
* @pure
*/
final public function isZero() : bool
{
@ -398,6 +413,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is strictly negative.
*
* @pure
*/
final public function isNegative() : bool
{
@ -406,6 +423,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is negative or zero.
*
* @pure
*/
final public function isNegativeOrZero() : bool
{
@ -414,6 +433,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is strictly positive.
*
* @pure
*/
final public function isPositive() : bool
{
@ -422,6 +443,8 @@ abstract class BigNumber implements \JsonSerializable
/**
* Checks if this number is positive or zero.
*
* @pure
*/
final public function isPositiveOrZero() : bool
{
@ -431,20 +454,24 @@ abstract class BigNumber implements \JsonSerializable
/**
* Returns the sign of this number.
*
* @psalm-return -1|0|1
* Returns -1 if the number is negative, 0 if zero, 1 if positive.
*
* @return int -1 if the number is negative, 0 if zero, 1 if positive.
* @return -1|0|1
*
* @pure
*/
abstract public function getSign() : int;
/**
* Compares this number to the given one.
*
* @psalm-return -1|0|1
* Returns -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
*
* @return int -1 if `$this` is lower than, 0 if equal to, 1 if greater than `$that`.
* @return -1|0|1
*
* @throws MathException If the number is not valid.
*
* @pure
*/
abstract public function compareTo(BigNumber|int|float|string $that) : int;
@ -452,6 +479,8 @@ abstract class BigNumber implements \JsonSerializable
* Converts this number to a BigInteger.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigInteger without rounding.
*
* @pure
*/
abstract public function toBigInteger() : BigInteger;
@ -459,11 +488,15 @@ abstract class BigNumber implements \JsonSerializable
* Converts this number to a BigDecimal.
*
* @throws RoundingNecessaryException If this number cannot be converted to a BigDecimal without rounding.
*
* @pure
*/
abstract public function toBigDecimal() : BigDecimal;
/**
* Converts this number to a BigRational.
*
* @pure
*/
abstract public function toBigRational() : BigRational;
@ -475,6 +508,8 @@ abstract class BigNumber implements \JsonSerializable
*
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
* This only applies when RoundingMode::UNNECESSARY is used.
*
* @pure
*/
abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY) : BigDecimal;
@ -485,6 +520,8 @@ abstract class BigNumber implements \JsonSerializable
* Note that the acceptable range for an integer depends on the platform and differs for 32-bit and 64-bit.
*
* @throws MathException If this number cannot be exactly converted to a native integer.
*
* @pure
*/
abstract public function toInt() : int;
@ -496,6 +533,8 @@ abstract class BigNumber implements \JsonSerializable
*
* 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.
*
* @pure
*/
abstract public function toFloat() : float;
@ -504,6 +543,8 @@ abstract class BigNumber implements \JsonSerializable
*
* 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.
*
* @pure
*/
abstract public function __toString() : string;

View File

@ -14,20 +14,18 @@ use Override;
* An arbitrarily large rational number.
*
* This class is immutable.
*
* @psalm-immutable
*/
final class BigRational extends BigNumber
final readonly class BigRational extends BigNumber
{
/**
* The numerator.
*/
private readonly BigInteger $numerator;
private BigInteger $numerator;
/**
* The denominator. Always strictly positive.
*/
private readonly BigInteger $denominator;
private BigInteger $denominator;
/**
* Protected constructor. Use a factory method to obtain an instance.
@ -37,6 +35,8 @@ final class BigRational extends BigNumber
* @param bool $checkDenominator Whether to check the denominator for negative and zero.
*
* @throws DivisionByZeroException If the denominator is zero.
*
* @pure
*/
protected function __construct(BigInteger $numerator, BigInteger $denominator, bool $checkDenominator)
{
@ -55,9 +55,6 @@ final class BigRational extends BigNumber
$this->denominator = $denominator;
}
/**
* @psalm-pure
*/
#[Override]
protected static function from(BigNumber $number): static
{
@ -77,7 +74,7 @@ final class BigRational extends BigNumber
* @throws RoundingNecessaryException If an argument represents a non-integer number.
* @throws DivisionByZeroException If the denominator is zero.
*
* @psalm-pure
* @pure
*/
public static function nd(
BigNumber|int|float|string $numerator,
@ -92,14 +89,11 @@ final class BigRational extends BigNumber
/**
* Returns a BigRational representing zero.
*
* @psalm-pure
* @pure
*/
public static function zero() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $zero
*/
/** @var BigRational|null $zero */
static $zero;
if ($zero === null) {
@ -112,14 +106,11 @@ final class BigRational extends BigNumber
/**
* Returns a BigRational representing one.
*
* @psalm-pure
* @pure
*/
public static function one() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $one
*/
/** @var BigRational|null $one */
static $one;
if ($one === null) {
@ -132,14 +123,11 @@ final class BigRational extends BigNumber
/**
* Returns a BigRational representing ten.
*
* @psalm-pure
* @pure
*/
public static function ten() : BigRational
{
/**
* @psalm-suppress ImpureStaticVariable
* @var BigRational|null $ten
*/
/** @var BigRational|null $ten */
static $ten;
if ($ten === null) {
@ -149,11 +137,17 @@ final class BigRational extends BigNumber
return $ten;
}
/**
* @pure
*/
public function getNumerator() : BigInteger
{
return $this->numerator;
}
/**
* @pure
*/
public function getDenominator() : BigInteger
{
return $this->denominator;
@ -161,6 +155,8 @@ final class BigRational extends BigNumber
/**
* Returns the quotient of the division of the numerator by the denominator.
*
* @pure
*/
public function quotient() : BigInteger
{
@ -169,6 +165,8 @@ final class BigRational extends BigNumber
/**
* Returns the remainder of the division of the numerator by the denominator.
*
* @pure
*/
public function remainder() : BigInteger
{
@ -178,9 +176,9 @@ final class BigRational extends BigNumber
/**
* Returns the quotient and remainder of the division of the numerator by the denominator.
*
* @return BigInteger[]
* @return array{BigInteger, BigInteger}
*
* @psalm-return array{BigInteger, BigInteger}
* @pure
*/
public function quotientAndRemainder() : array
{
@ -193,6 +191,8 @@ final class BigRational extends BigNumber
* @param BigNumber|int|float|string $that The number to add.
*
* @throws MathException If the number is not valid.
*
* @pure
*/
public function plus(BigNumber|int|float|string $that) : BigRational
{
@ -211,6 +211,8 @@ final class BigRational extends BigNumber
* @param BigNumber|int|float|string $that The number to subtract.
*
* @throws MathException If the number is not valid.
*
* @pure
*/
public function minus(BigNumber|int|float|string $that) : BigRational
{
@ -229,6 +231,8 @@ final class BigRational extends BigNumber
* @param BigNumber|int|float|string $that The multiplier.
*
* @throws MathException If the multiplier is not a valid number.
*
* @pure
*/
public function multipliedBy(BigNumber|int|float|string $that) : BigRational
{
@ -246,6 +250,8 @@ final class BigRational extends BigNumber
* @param BigNumber|int|float|string $that The divisor.
*
* @throws MathException If the divisor is not a valid number, or is zero.
*
* @pure
*/
public function dividedBy(BigNumber|int|float|string $that) : BigRational
{
@ -261,6 +267,8 @@ final class BigRational extends BigNumber
* Returns this number exponentiated to the given value.
*
* @throws \InvalidArgumentException If the exponent is not in the range 0 to 1,000,000.
*
* @pure
*/
public function power(int $exponent) : BigRational
{
@ -287,6 +295,8 @@ final class BigRational extends BigNumber
* The reciprocal has the numerator and denominator swapped.
*
* @throws DivisionByZeroException If the numerator is zero.
*
* @pure
*/
public function reciprocal() : BigRational
{
@ -295,6 +305,8 @@ final class BigRational extends BigNumber
/**
* Returns the absolute value of this BigRational.
*
* @pure
*/
public function abs() : BigRational
{
@ -303,6 +315,8 @@ final class BigRational extends BigNumber
/**
* Returns the negated value of this BigRational.
*
* @pure
*/
public function negated() : BigRational
{
@ -311,6 +325,8 @@ final class BigRational extends BigNumber
/**
* Returns the simplified value of this BigRational.
*
* @pure
*/
public function simplified() : BigRational
{
@ -406,7 +422,6 @@ final class BigRational extends BigNumber
* 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
*
@ -414,10 +429,12 @@ final class BigRational extends BigNumber
*/
public function __unserialize(array $data): void
{
/** @phpstan-ignore isset.initializedProperty */
if (isset($this->numerator)) {
throw new \LogicException('__unserialize() is an internal function, it must not be called directly.');
}
/** @phpstan-ignore deadCode.unreachable */
$this->numerator = $data['numerator'];
$this->denominator = $data['denominator'];
}

View File

@ -7,10 +7,10 @@ namespace Brick\Math\Exception;
/**
* Exception thrown when a division by zero occurs.
*/
class DivisionByZeroException extends MathException
final class DivisionByZeroException extends MathException
{
/**
* @psalm-pure
* @pure
*/
public static function divisionByZero() : DivisionByZeroException
{
@ -18,7 +18,7 @@ class DivisionByZeroException extends MathException
}
/**
* @psalm-pure
* @pure
*/
public static function modulusMustNotBeZero() : DivisionByZeroException
{
@ -26,7 +26,7 @@ class DivisionByZeroException extends MathException
}
/**
* @psalm-pure
* @pure
*/
public static function denominatorMustNotBeZero() : DivisionByZeroException
{

View File

@ -9,10 +9,10 @@ use Brick\Math\BigInteger;
/**
* Exception thrown when an integer overflow occurs.
*/
class IntegerOverflowException extends MathException
final class IntegerOverflowException extends MathException
{
/**
* @psalm-pure
* @pure
*/
public static function toIntOverflow(BigInteger $value) : IntegerOverflowException
{

View File

@ -7,6 +7,6 @@ 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
final class NegativeNumberException extends MathException
{
}

View File

@ -7,8 +7,11 @@ namespace Brick\Math\Exception;
/**
* Exception thrown when attempting to create a number from a string with an invalid format.
*/
class NumberFormatException extends MathException
final class NumberFormatException extends MathException
{
/**
* @pure
*/
public static function invalidFormat(string $value) : self
{
return new self(\sprintf(
@ -20,7 +23,7 @@ class NumberFormatException extends MathException
/**
* @param string $char The failing character.
*
* @psalm-pure
* @pure
*/
public static function charNotInAlphabet(string $char) : self
{

View File

@ -7,10 +7,10 @@ namespace Brick\Math\Exception;
/**
* Exception thrown when a number cannot be represented at the requested scale without rounding.
*/
class RoundingNecessaryException extends MathException
final class RoundingNecessaryException extends MathException
{
/**
* @psalm-pure
* @pure
*/
public static function roundingNecessary() : RoundingNecessaryException
{

View File

@ -17,10 +17,8 @@ use Brick\Math\RoundingMode;
* All methods must return strings respecting this format, unless specified otherwise.
*
* @internal
*
* @psalm-immutable
*/
abstract class Calculator
abstract readonly class Calculator
{
/**
* The maximum exponent value allowed for the pow() method.
@ -32,63 +30,12 @@ abstract class Calculator
*/
public const ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz';
/**
* The Calculator instance in use.
*/
private static ?Calculator $instance = null;
/**
* 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.
*/
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.
*
* @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
*/
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.
*
* @return array{bool, bool, string, string} Whether $a and $b are negative, followed by their digits.
*
* @pure
*/
final protected function init(string $a, string $b) : array
{
@ -103,6 +50,8 @@ abstract class Calculator
/**
* Returns the absolute value of a number.
*
* @pure
*/
final public function abs(string $n) : string
{
@ -111,6 +60,8 @@ abstract class Calculator
/**
* Negates a number.
*
* @pure
*/
final public function neg(string $n) : string
{
@ -128,9 +79,11 @@ abstract class Calculator
/**
* Compares two numbers.
*
* @psalm-return -1|0|1
* Returns -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
*
* @return int -1 if the first number is less than, 0 if equal to, 1 if greater than the second number.
* @return -1|0|1
*
* @pure
*/
final public function cmp(string $a, string $b) : int
{
@ -160,16 +113,22 @@ abstract class Calculator
/**
* Adds two numbers.
*
* @pure
*/
abstract public function add(string $a, string $b) : string;
/**
* Subtracts two numbers.
*
* @pure
*/
abstract public function sub(string $a, string $b) : string;
/**
* Multiplies two numbers.
*
* @pure
*/
abstract public function mul(string $a, string $b) : string;
@ -180,6 +139,8 @@ abstract class Calculator
* @param string $b The divisor, must not be zero.
*
* @return string The quotient.
*
* @pure
*/
abstract public function divQ(string $a, string $b) : string;
@ -190,6 +151,8 @@ abstract class Calculator
* @param string $b The divisor, must not be zero.
*
* @return string The remainder.
*
* @pure
*/
abstract public function divR(string $a, string $b) : string;
@ -200,6 +163,8 @@ abstract class Calculator
* @param string $b The divisor, must not be zero.
*
* @return array{string, string} An array containing the quotient and remainder.
*
* @pure
*/
abstract public function divQR(string $a, string $b) : array;
@ -210,11 +175,15 @@ abstract class Calculator
* @param int $e The exponent, validated as an integer between 0 and MAX_POWER.
*
* @return string The power.
*
* @pure
*/
abstract public function pow(string $a, int $e) : string;
/**
* @param string $b The modulus; must not be zero.
*
* @pure
*/
public function mod(string $a, string $b) : string
{
@ -229,6 +198,8 @@ abstract class Calculator
* This method can be overridden by the concrete implementation if the underlying library has built-in support.
*
* @param string $m The modulus; must not be negative or zero.
*
* @pure
*/
public function modInverse(string $x, string $m) : ?string
{
@ -257,6 +228,8 @@ abstract class Calculator
* @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.
*
* @pure
*/
abstract public function modPow(string $base, string $exp, string $mod) : string;
@ -267,6 +240,8 @@ abstract class Calculator
* has built-in support for GCD calculations.
*
* @return string The GCD, always positive, or zero if both arguments are zero.
*
* @pure
*/
public function gcd(string $a, string $b) : string
{
@ -283,6 +258,8 @@ abstract class Calculator
/**
* @return array{string, string, string} GCD, X, Y
*
* @pure
*/
private function gcdExtended(string $a, string $b) : array
{
@ -303,6 +280,8 @@ abstract class Calculator
*
* The result is the largest x such that n.
* The input MUST NOT be negative.
*
* @pure
*/
abstract public function sqrt(string $n) : string;
@ -316,6 +295,8 @@ abstract class Calculator
* @param int $base The base of the number, validated from 2 to 36.
*
* @return string The converted number, following the Calculator conventions.
*
* @pure
*/
public function fromBase(string $number, int $base) : string
{
@ -332,6 +313,8 @@ abstract class Calculator
* @param int $base The base to convert to, validated from 2 to 36.
*
* @return string The converted number, lowercase.
*
* @pure
*/
public function toBase(string $number, int $base) : string
{
@ -359,6 +342,8 @@ abstract class Calculator
* @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.
*
* @pure
*/
final public function fromArbitraryBase(string $number, string $alphabet, int $base) : string
{
@ -405,6 +390,8 @@ abstract class Calculator
* @param int $base The base to convert to, validated from 2 to alphabet length.
*
* @return string The converted number in the given alphabet.
*
* @pure
*/
final public function toArbitraryBase(string $number, string $alphabet, int $base) : string
{
@ -434,10 +421,9 @@ abstract class Calculator
* @param string $b The divisor, must not be zero.
* @param RoundingMode $roundingMode The rounding mode.
*
* @throws \InvalidArgumentException If the rounding mode is invalid.
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
*
* @psalm-suppress ImpureFunctionCall
* @pure
*/
final public function divRound(string $a, string $b, RoundingMode $roundingMode) : string
{
@ -498,9 +484,6 @@ abstract class Calculator
$lastDigitIsEven = ($lastDigit % 2 === 0);
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
default:
throw new \InvalidArgumentException('Invalid rounding mode.');
}
if ($increment) {
@ -515,6 +498,8 @@ abstract class Calculator
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @pure
*/
public function and(string $a, string $b) : string
{
@ -526,6 +511,8 @@ abstract class Calculator
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @pure
*/
public function or(string $a, string $b) : string
{
@ -537,6 +524,8 @@ abstract class Calculator
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for bitwise operations.
*
* @pure
*/
public function xor(string $a, string $b) : string
{
@ -549,6 +538,8 @@ abstract class Calculator
* @param 'and'|'or'|'xor' $operator The operator to use.
* @param string $a The left operand.
* @param string $b The right operand.
*
* @pure
*/
private function bitwise(string $operator, string $a, string $b) : string
{
@ -596,6 +587,8 @@ abstract class Calculator
/**
* @param string $number A positive, binary number.
*
* @pure
*/
private function twosComplement(string $number) : string
{
@ -625,6 +618,8 @@ abstract class Calculator
* Converts a decimal number to a binary string.
*
* @param string $number The number to convert, positive or zero, only digits.
*
* @pure
*/
private function toBinary(string $number) : string
{
@ -642,6 +637,8 @@ abstract class Calculator
* Returns the positive decimal representation of a binary number.
*
* @param string $bytes The bytes representing the number.
*
* @pure
*/
private function toDecimal(string $bytes) : string
{

View File

@ -11,10 +11,8 @@ use Override;
* Calculator implementation built around the bcmath library.
*
* @internal
*
* @psalm-immutable
*/
class BcMathCalculator extends Calculator
final readonly class BcMathCalculator extends Calculator
{
#[Override]
public function add(string $a, string $b) : string

View File

@ -5,16 +5,15 @@ declare(strict_types=1);
namespace Brick\Math\Internal\Calculator;
use Brick\Math\Internal\Calculator;
use GMP;
use Override;
/**
* Calculator implementation built around the GMP library.
*
* @internal
*
* @psalm-immutable
*/
class GmpCalculator extends Calculator
final readonly class GmpCalculator extends Calculator
{
#[Override]
public function add(string $a, string $b) : string
@ -51,6 +50,10 @@ class GmpCalculator extends Calculator
{
[$q, $r] = \gmp_div_qr($a, $b);
/**
* @var GMP $q
* @var GMP $r
*/
return [
\gmp_strval($q),
\gmp_strval($r)

View File

@ -11,10 +11,8 @@ use Override;
* Calculator implementation using only native PHP code.
*
* @internal
*
* @psalm-immutable
*/
class NativeCalculator extends Calculator
final readonly class NativeCalculator extends Calculator
{
/**
* The max number of digits the platform can natively add, subtract, multiply or divide without overflow.
@ -24,9 +22,10 @@ class NativeCalculator extends Calculator
* 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)
*/
private readonly int $maxDigits;
private int $maxDigits;
/**
* @pure
* @codeCoverageIgnore
*/
public function __construct()
@ -34,7 +33,6 @@ class NativeCalculator extends Calculator
$this->maxDigits = match (PHP_INT_SIZE) {
4 => 9,
8 => 18,
default => throw new \RuntimeException('The platform is not 32-bit or 64-bit as expected.')
};
}
@ -42,8 +40,8 @@ class NativeCalculator extends Calculator
public function add(string $a, string $b) : string
{
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
* @var numeric-string $a
* @var numeric-string $b
*/
$result = $a + $b;
@ -80,8 +78,8 @@ class NativeCalculator extends Calculator
public function mul(string $a, string $b) : string
{
/**
* @psalm-var numeric-string $a
* @psalm-var numeric-string $b
* @var numeric-string $a
* @var numeric-string $b
*/
$result = $a * $b;
@ -151,11 +149,11 @@ class NativeCalculator extends Calculator
return [$this->neg($a), '0'];
}
/** @psalm-var numeric-string $a */
/** @var numeric-string $a */
$na = $a * 1; // cast to number
if (is_int($na)) {
/** @psalm-var numeric-string $b */
/** @var numeric-string $b */
$nb = $b * 1;
if (is_int($nb)) {
@ -202,7 +200,6 @@ class NativeCalculator extends Calculator
$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) {
@ -278,6 +275,8 @@ class NativeCalculator extends Calculator
/**
* Performs the addition of two non-signed large integers.
*
* @pure
*/
private function doAdd(string $a, string $b) : string
{
@ -291,14 +290,13 @@ class NativeCalculator extends Calculator
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
/** @var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
/** @var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = (string) ($blockA + $blockB + $carry);
@ -330,6 +328,8 @@ class NativeCalculator extends Calculator
/**
* Performs the subtraction of two non-signed large integers.
*
* @pure
*/
private function doSub(string $a, string $b) : string
{
@ -360,14 +360,13 @@ class NativeCalculator extends Calculator
if ($i < 0) {
$blockLength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
/** @psalm-var numeric-string $blockA */
/** @var numeric-string $blockA */
$blockA = \substr($a, $i, $blockLength);
/** @psalm-var numeric-string $blockB */
/** @var numeric-string $blockB */
$blockB = \substr($b, $i, $blockLength);
$sum = $blockA - $blockB - $carry;
@ -407,6 +406,8 @@ class NativeCalculator extends Calculator
/**
* Performs the multiplication of two non-signed large integers.
*
* @pure
*/
private function doMul(string $a, string $b) : string
{
@ -423,7 +424,6 @@ class NativeCalculator extends Calculator
if ($i < 0) {
$blockALength += $i;
/** @psalm-suppress LoopInvalidation */
$i = 0;
}
@ -437,7 +437,6 @@ class NativeCalculator extends Calculator
if ($j < 0) {
$blockBLength += $j;
/** @psalm-suppress LoopInvalidation */
$j = 0;
}
@ -480,6 +479,8 @@ class NativeCalculator extends Calculator
* Performs the division of two non-signed large integers.
*
* @return string[] The quotient and remainder.
*
* @pure
*/
private function doDiv(string $a, string $b) : array
{
@ -498,7 +499,7 @@ class NativeCalculator extends Calculator
$r = $a; // remainder
$z = $y; // focus length, always $y or $y+1
/** @psalm-var numeric-string $b */
/** @var numeric-string $b */
$nb = $b * 1; // cast to number
// performance optimization in cases where the remainder will never cause int overflow
if (is_int(($nb - 1) * 10 + 9)) {
@ -506,7 +507,7 @@ class NativeCalculator extends Calculator
for ($i = $z - 1; $i < $x; $i++) {
$n = $r * 10 + (int) $a[$i];
/** @psalm-var int $nb */
/** @var int $nb */
$q .= \intdiv($n, $nb);
$r = $n % $nb;
}
@ -553,7 +554,9 @@ class NativeCalculator extends Calculator
/**
* Compares two non-signed large numbers.
*
* @psalm-return -1|0|1
* @return -1|0|1
*
* @pure
*/
private function doCmp(string $a, string $b) : int
{
@ -575,6 +578,8 @@ class NativeCalculator extends Calculator
* The numbers must only consist of digits, without leading minus sign.
*
* @return array{string, string, int}
*
* @pure
*/
private function pad(string $a, string $b) : array
{

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Brick\Math\Internal;
use function extension_loaded;
/**
* Stores the current Calculator instance used by BigNumber classes.
*
* @internal
*/
final class CalculatorRegistry
{
/**
* The Calculator instance in use.
*/
private static ?Calculator $instance = null;
/**
* Sets the Calculator instance to use.
*
* An instance is typically set only in unit tests: autodetect is usually the best option.
*
* @param Calculator|null $calculator The calculator instance, or null to revert to autodetect.
*/
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.
*
* Note: even though this method is not technically pure, it is considered pure when used in a normal context, when
* only relying on autodetect.
*
* @pure
*/
final public static function get() : Calculator
{
/** @phpstan-ignore impure.staticPropertyAccess */
if (self::$instance === null) {
/** @phpstan-ignore impure.propertyAssign */
self::$instance = self::detect();
}
/** @phpstan-ignore impure.staticPropertyAccess */
return self::$instance;
}
/**
* Returns the fastest available Calculator implementation.
*
* @pure
* @codeCoverageIgnore
*/
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();
}
}

View File

@ -26,12 +26,23 @@ use Composer\Semver\VersionParser;
*/
class InstalledVersions
{
/**
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
* @internal
*/
private static $selfDir = null;
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool
*/
private static $installedIsLocalDir;
/**
* @var bool|null
*/
@ -309,6 +320,24 @@ class InstalledVersions
{
self::$installed = $data;
self::$installedByVendor = array();
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
// so we have to assume it does not, and that may result in duplicate data being returned when listing
// all installed packages for example
self::$installedIsLocalDir = false;
}
/**
* @return string
*/
private static function getSelfDir()
{
if (self::$selfDir === null) {
self::$selfDir = strtr(__DIR__, '\\', '/');
}
return self::$selfDir;
}
/**
@ -322,19 +351,27 @@ class InstalledVersions
}
$installed = array();
$copiedLocalDir = false;
if (self::$canGetVendors) {
$selfDir = self::getSelfDir();
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
$vendorDir = strtr($vendorDir, '\\', '/');
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
self::$installedByVendor[$vendorDir] = $required;
$installed[] = $required;
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
self::$installed = $required;
self::$installedIsLocalDir = true;
}
}
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
$copiedLocalDir = true;
}
}
}
@ -350,7 +387,7 @@ class InstalledVersions
}
}
if (self::$installed !== array()) {
if (self::$installed !== array() && !$copiedLocalDir) {
$installed[] = self::$installed;
}

View File

@ -32,12 +32,12 @@ return array(
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Predis\\' => array($vendorDir . '/predis/predis/src'),
'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'),
'Jfcherng\\Utility\\' => array($vendorDir . '/jfcherng/php-mb-string/src', $vendorDir . '/jfcherng/php-color-output/src'),
'Jfcherng\\Diff\\' => array($vendorDir . '/jfcherng/php-sequence-matcher/src', $vendorDir . '/jfcherng/php-diff/src'),
'Jfcherng\\Diff\\' => array($vendorDir . '/jfcherng/php-diff/src', $vendorDir . '/jfcherng/php-sequence-matcher/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),

View File

@ -226,8 +226,8 @@ class ComposerStaticInit5d5db0943532ec3ad2d064ba31305947
),
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-message/src',
1 => __DIR__ . '/..' . '/psr/http-factory/src',
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Http\\Client\\' =>
array (
@ -248,8 +248,8 @@ class ComposerStaticInit5d5db0943532ec3ad2d064ba31305947
),
'Jfcherng\\Diff\\' =>
array (
0 => __DIR__ . '/..' . '/jfcherng/php-sequence-matcher/src',
1 => __DIR__ . '/..' . '/jfcherng/php-diff/src',
0 => __DIR__ . '/..' . '/jfcherng/php-diff/src',
1 => __DIR__ . '/..' . '/jfcherng/php-sequence-matcher/src',
),
'GuzzleHttp\\Psr7\\' =>
array (

View File

@ -2,28 +2,28 @@
"packages": [
{
"name": "brick/math",
"version": "0.13.1",
"version_normalized": "0.13.1.0",
"version": "0.14.0",
"version_normalized": "0.14.0.0",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
"url": "https://api.github.com/repos/brick/math/zipball/113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"reference": "113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2",
"shasum": ""
},
"require": {
"php": "^8.1"
"php": "^8.2"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
"vimeo/psalm": "6.8.8"
"phpstan/phpstan": "2.1.22",
"phpunit/phpunit": "^11.5"
},
"time": "2025-03-29T13:50:30+00:00",
"time": "2025-08-29T12:40:03+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -53,7 +53,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.13.1"
"source": "https://github.com/brick/math/tree/0.14.0"
},
"funding": [
{
@ -1618,23 +1618,23 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "7.9.3",
"version_normalized": "7.9.3.0",
"version": "7.10.0",
"version_normalized": "7.10.0.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77"
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
"reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.3",
"guzzlehttp/psr7": "^2.7.0",
"guzzlehttp/promises": "^2.3",
"guzzlehttp/psr7": "^2.8",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
@ -1655,7 +1655,7 @@
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"time": "2025-03-27T13:37:11+00:00",
"time": "2025-08-23T22:36:01+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
@ -1727,7 +1727,7 @@
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.9.3"
"source": "https://github.com/guzzle/guzzle/tree/7.10.0"
},
"funding": [
{
@ -1747,17 +1747,17 @@
},
{
"name": "guzzlehttp/promises",
"version": "2.2.0",
"version_normalized": "2.2.0.0",
"version": "2.3.0",
"version_normalized": "2.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c"
"reference": "481557b130ef3790cf82b713667b43030dc9c957"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c",
"reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c",
"url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957",
"reference": "481557b130ef3790cf82b713667b43030dc9c957",
"shasum": ""
},
"require": {
@ -1765,9 +1765,9 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"time": "2025-03-27T13:27:01+00:00",
"time": "2025-08-22T14:34:08+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
@ -1813,7 +1813,7 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.2.0"
"source": "https://github.com/guzzle/promises/tree/2.3.0"
},
"funding": [
{
@ -1833,17 +1833,17 @@
},
{
"name": "guzzlehttp/psr7",
"version": "2.7.1",
"version_normalized": "2.7.1.0",
"version": "2.8.0",
"version_normalized": "2.8.0.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
"reference": "21dc724a0583619cd1652f673303492272778051"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
"reference": "21dc724a0583619cd1652f673303492272778051",
"shasum": ""
},
"require": {
@ -1859,12 +1859,12 @@
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"time": "2025-03-27T12:30:47+00:00",
"time": "2025-08-23T21:21:41+00:00",
"type": "library",
"extra": {
"bamarni-bin": {
@ -1932,7 +1932,7 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.7.1"
"source": "https://github.com/guzzle/psr7/tree/2.8.0"
},
"funding": [
{
@ -2271,17 +2271,17 @@
},
{
"name": "predis/predis",
"version": "v3.0.1",
"version_normalized": "3.0.1.0",
"version": "v3.2.0",
"version_normalized": "3.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/predis/predis.git",
"reference": "34fb0a7da0330df1bab4280fcac4afdeeccc3edf"
"reference": "c1845d96ccbceb1269387d1c294fd7e4876f6307"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/predis/predis/zipball/34fb0a7da0330df1bab4280fcac4afdeeccc3edf",
"reference": "34fb0a7da0330df1bab4280fcac4afdeeccc3edf",
"url": "https://api.github.com/repos/predis/predis/zipball/c1845d96ccbceb1269387d1c294fd7e4876f6307",
"reference": "c1845d96ccbceb1269387d1c294fd7e4876f6307",
"shasum": ""
},
"require": {
@ -2297,7 +2297,7 @@
"suggest": {
"ext-relay": "Faster connection with in-memory caching (>=0.6.2)"
},
"time": "2025-05-16T18:30:32+00:00",
"time": "2025-08-03T19:20:07+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -2325,7 +2325,7 @@
],
"support": {
"issues": "https://github.com/predis/predis/issues",
"source": "https://github.com/predis/predis/tree/v3.0.1"
"source": "https://github.com/predis/predis/tree/v3.2.0"
},
"funding": [
{
@ -2685,21 +2685,21 @@
},
{
"name": "ramsey/uuid",
"version": "4.9.0",
"version_normalized": "4.9.0.0",
"version": "4.9.1",
"version_normalized": "4.9.1.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0"
"reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0",
"reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13",
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
},
@ -2732,7 +2732,7 @@
"paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
"ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
},
"time": "2025-06-25T14:20:11+00:00",
"time": "2025-09-04T20:59:21+00:00",
"type": "library",
"extra": {
"captainhook": {
@ -2760,7 +2760,7 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.9.0"
"source": "https://github.com/ramsey/uuid/tree/4.9.1"
},
"install-path": "../ramsey/uuid"
},
@ -4155,8 +4155,8 @@
},
{
"name": "symfony/polyfill-php81",
"version": "v1.32.0",
"version_normalized": "1.32.0.0",
"version": "v1.33.0",
"version_normalized": "1.33.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
@ -4214,7 +4214,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0"
"source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0"
},
"funding": [
{
@ -4225,6 +4225,10 @@
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
@ -4392,17 +4396,17 @@
},
{
"name": "tedivm/jshrink",
"version": "v1.7.0",
"version_normalized": "1.7.0.0",
"version": "v1.8.0",
"version_normalized": "1.8.0.0",
"source": {
"type": "git",
"url": "https://github.com/tedious/JShrink.git",
"reference": "7a35f5a4651ca2ce77295eb8a3b4e133ba47e19e"
"reference": "29ee510d684c22060040f4260a527206eb8199f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tedious/JShrink/zipball/7a35f5a4651ca2ce77295eb8a3b4e133ba47e19e",
"reference": "7a35f5a4651ca2ce77295eb8a3b4e133ba47e19e",
"url": "https://api.github.com/repos/tedious/JShrink/zipball/29ee510d684c22060040f4260a527206eb8199f1",
"reference": "29ee510d684c22060040f4260a527206eb8199f1",
"shasum": ""
},
"require": {
@ -4413,7 +4417,7 @@
"php-coveralls/php-coveralls": "^2.5.0",
"phpunit/phpunit": "^9|^10"
},
"time": "2023-10-04T17:23:23+00:00",
"time": "2025-07-28T17:09:23+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -4439,7 +4443,7 @@
],
"support": {
"issues": "https://github.com/tedious/JShrink/issues",
"source": "https://github.com/tedious/JShrink/tree/v1.7.0"
"source": "https://github.com/tedious/JShrink/tree/v1.8.0"
},
"funding": [
{

View File

@ -3,7 +3,7 @@
'name' => 'icinga/icinga-php-thirdparty',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'a3a9a0f9a8449974ad52dcfbd5b1f1512e6e8bdb',
'reference' => 'f51759aeef363058b1f12456a2ee6ffbb45cb25d',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -11,9 +11,9 @@
),
'versions' => array(
'brick/math' => array(
'pretty_version' => '0.13.1',
'version' => '0.13.1.0',
'reference' => 'fc7ed316430118cc7836bf45faff18d5dfc8de04',
'pretty_version' => '0.14.0',
'version' => '0.14.0.0',
'reference' => '113a8ee2656b882d4c3164fa31aa6e12cbb7aaa2',
'type' => 'library',
'install_path' => __DIR__ . '/../brick/math',
'aliases' => array(),
@ -236,27 +236,27 @@
'dev_requirement' => false,
),
'guzzlehttp/guzzle' => array(
'pretty_version' => '7.9.3',
'version' => '7.9.3.0',
'reference' => '7b2f29fe81dc4da0ca0ea7d42107a0845946ea77',
'pretty_version' => '7.10.0',
'version' => '7.10.0.0',
'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/promises' => array(
'pretty_version' => '2.2.0',
'version' => '2.2.0.0',
'reference' => '7c69f28996b0a6920945dd20b3857e499d9ca96c',
'pretty_version' => '2.3.0',
'version' => '2.3.0.0',
'reference' => '481557b130ef3790cf82b713667b43030dc9c957',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '2.7.1',
'version' => '2.7.1.0',
'reference' => 'c2270caaabe631b3b44c85f99e5a04bbb8060d16',
'pretty_version' => '2.8.0',
'version' => '2.8.0.0',
'reference' => '21dc724a0583619cd1652f673303492272778051',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
@ -265,7 +265,7 @@
'icinga/icinga-php-thirdparty' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => 'a3a9a0f9a8449974ad52dcfbd5b1f1512e6e8bdb',
'reference' => 'f51759aeef363058b1f12456a2ee6ffbb45cb25d',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -323,9 +323,9 @@
),
),
'predis/predis' => array(
'pretty_version' => 'v3.0.1',
'version' => '3.0.1.0',
'reference' => '34fb0a7da0330df1bab4280fcac4afdeeccc3edf',
'pretty_version' => 'v3.2.0',
'version' => '3.2.0.0',
'reference' => 'c1845d96ccbceb1269387d1c294fd7e4876f6307',
'type' => 'library',
'install_path' => __DIR__ . '/../predis/predis',
'aliases' => array(),
@ -404,9 +404,9 @@
'dev_requirement' => false,
),
'ramsey/uuid' => array(
'pretty_version' => '4.9.0',
'version' => '4.9.0.0',
'reference' => '4e0e23cc785f0724a0e838279a9eb03f28b092a0',
'pretty_version' => '4.9.1',
'version' => '4.9.1.0',
'reference' => '81f941f6f729b1e3ceea61d9d014f8b6c6800440',
'type' => 'library',
'install_path' => __DIR__ . '/../ramsey/uuid',
'aliases' => array(),
@ -514,7 +514,7 @@
'rhumsaa/uuid' => array(
'dev_requirement' => false,
'replaced' => array(
0 => '4.9.0',
0 => '4.9.1',
),
),
'ringcentral/psr7' => array(
@ -581,8 +581,8 @@
'dev_requirement' => false,
),
'symfony/polyfill-php81' => array(
'pretty_version' => 'v1.32.0',
'version' => '1.32.0.0',
'pretty_version' => 'v1.33.0',
'version' => '1.33.0.0',
'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php81',
@ -608,9 +608,9 @@
'dev_requirement' => false,
),
'tedivm/jshrink' => array(
'pretty_version' => 'v1.7.0',
'version' => '1.7.0.0',
'reference' => '7a35f5a4651ca2ce77295eb8a3b4e133ba47e19e',
'pretty_version' => 'v1.8.0',
'version' => '1.8.0.0',
'reference' => '29ee510d684c22060040f4260a527206eb8199f1',
'type' => 'library',
'install_path' => __DIR__ . '/../tedivm/jshrink',
'aliases' => array(),

View File

@ -19,8 +19,7 @@ if ($issues) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
throw new \RuntimeException(
'Composer detected issues in your platform: ' . implode(' ', $issues)
);
}

View File

@ -2,6 +2,17 @@
Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
## 7.10.0 - 2025-08-23
### Added
- Support for PHP 8.5
### Changed
- Adjusted `guzzlehttp/promises` version constraint to `^2.3`
- Adjusted `guzzlehttp/psr7` version constraint to `^2.8`
## 7.9.3 - 2025-03-27

View File

@ -81,8 +81,8 @@
"require": {
"php": "^7.2.5 || ^8.0",
"ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.3",
"guzzlehttp/psr7": "^2.7.0",
"guzzlehttp/promises": "^2.3",
"guzzlehttp/psr7": "^2.8",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},

6
vendor/guzzlehttp/guzzle/package-lock.json generated vendored Normal file
View File

@ -0,0 +1,6 @@
{
"name": "guzzle",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@ -125,7 +125,9 @@ class CurlFactory implements CurlFactoryInterface
unset($easy->handle);
if (\count($this->handles) >= $this->maxHandles) {
\curl_close($resource);
if (PHP_VERSION_ID < 80000) {
\curl_close($resource);
}
} else {
// Remove all callback functions as they can hold onto references
// and are not cleaned up by curl_reset. Using curl_setopt_array
@ -729,7 +731,10 @@ class CurlFactory implements CurlFactoryInterface
public function __destruct()
{
foreach ($this->handles as $id => $handle) {
\curl_close($handle);
if (PHP_VERSION_ID < 80000) {
\curl_close($handle);
}
unset($this->handles[$id]);
}
}

View File

@ -240,7 +240,10 @@ class CurlMultiHandler
$handle = $this->handles[$id]['easy']->handle;
unset($this->delays[$id], $this->handles[$id]);
\curl_multi_remove_handle($this->_mh, $handle);
\curl_close($handle);
if (PHP_VERSION_ID < 80000) {
\curl_close($handle);
}
return true;
}

View File

@ -333,8 +333,15 @@ class StreamHandler
);
return $this->createResource(
function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) {
function () use ($uri, $contextResource, $context, $options, $request) {
$resource = @\fopen((string) $uri, 'r', false, $contextResource);
// See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable
if (function_exists('http_get_last_response_headers')) {
/** @var array|null */
$http_response_header = \http_get_last_response_headers();
}
$this->lastHeaders = $http_response_header ?? [];
if (false === $resource) {

View File

@ -187,12 +187,12 @@ final class Middleware
* Middleware that logs requests, responses, and errors using a message
* formatter.
*
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
*
* @param LoggerInterface $logger Logs messages.
* @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
* @param string $logLevel Level at which to log requests.
*
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
*
* @return callable Returns a function that accepts the next handler.
*/
public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable

View File

@ -1,6 +1,13 @@
# CHANGELOG
## 2.3.0 - 2025-08-22
### Added
- PHP 8.5 support
## 2.2.0 - 2025-03-27
### Fixed

View File

@ -41,7 +41,7 @@ composer require guzzlehttp/promises
| Version | Status | PHP Version |
|---------|---------------------|--------------|
| 1.x | Security fixes only | >=5.5,<8.3 |
| 2.x | Latest | >=7.2.5,<8.5 |
| 2.x | Latest | >=7.2.5,<8.6 |
## Quick Start

View File

@ -30,7 +30,7 @@
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"autoload": {
"psr-4": {

View File

@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 2.8.0 - 2025-08-23
### Added
- Allow empty lists as header values
### Changed
- PHP 8.5 support
## 2.7.1 - 2025-03-27
### Fixed

View File

@ -25,7 +25,7 @@ composer require guzzlehttp/psr7
| Version | Status | PHP Version |
|---------|---------------------|--------------|
| 1.x | EOL (2024-06-30) | >=5.4,<8.2 |
| 2.x | Latest | >=7.2.5,<8.5 |
| 2.x | Latest | >=7.2.5,<8.6 |
## AppendStream

View File

@ -62,7 +62,7 @@
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "0.9.0",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
"phpunit/phpunit": "^8.5.44 || ^9.6.25"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"

View File

@ -174,10 +174,6 @@ trait MessageTrait
return $this->trimAndValidateHeaderValues([$value]);
}
if (count($value) === 0) {
throw new \InvalidArgumentException('Header value can not be an empty array.');
}
return $this->trimAndValidateHeaderValues($value);
}

View File

@ -397,7 +397,7 @@ final class Utils
restore_error_handler();
if ($ex) {
/** @var $ex \RuntimeException */
/** @var \RuntimeException $ex */
throw $ex;
}
@ -444,7 +444,7 @@ final class Utils
restore_error_handler();
if ($ex) {
/** @var $ex \RuntimeException */
/** @var \RuntimeException $ex */
throw $ex;
}

View File

@ -138,6 +138,50 @@ it is still desired to have control of when the connection is opened or closed:
achieved by invoking `$client->connect()` and `$client->disconnect()`. Please note that the effect
of these methods on aggregate connections may differ depending on each specific implementation.
#### Persistent connections ####
To increase a performance of your application you may set up a client to use persistent TCP connection, this way
client saves a time on socket creation and connection handshake. By default, connection is created on first-command
execution and will be automatically closed by GC before the process is being killed.
However, if your application is backed by PHP-FPM the processes are idle, and you may set up it to be persistent and
reusable across multiple script execution within the same process.
To enable the persistent connection mode you should provide following configuration:
```php
// Standalone
$client = new Predis\Client(['persistent' => true]);
// Cluster
$client = new Predis\Client(
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
['cluster' => 'redis', 'parameters' => ['persistent' => true]]
);
```
**Important**
If you operate on multiple clients within the same application, and they communicate with the same resource, by default
they will share the same socket (that's the default behaviour of persistent sockets). So in this case you would need
to additionally provide a `conn_uid` identifier for each client, this way each client will create its own socket so
the connection context won't be shared across clients. This socket behaviour explained
[here](https://www.php.net/manual/en/function.stream-socket-client.php#105393)
```php
// Standalone
$client1 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_1']);
$client2 = new Predis\Client(['persistent' => true, 'conn_uid' => 'id_2']);
// Cluster
$client1 = new Predis\Client(
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_1']]
);
$client2 = new Predis\Client(
['tcp://host:port', 'tcp://host:port', 'tcp://host:port'],
['cluster' => 'redis', 'parameters' => ['persistent' => true, 'conn_uid' => 'id_2']]
);
```
### Client configuration ###

View File

@ -54,7 +54,7 @@ use Traversable;
*/
class Client implements ClientInterface, IteratorAggregate
{
public const VERSION = '3.0.1';
public const VERSION = '3.1.1-dev';
/** @var OptionsInterface */
private $options;

View File

@ -45,6 +45,7 @@ use Predis\Command\Container\Json\JSONDEBUG;
use Predis\Command\Container\Search\FTCONFIG;
use Predis\Command\Container\Search\FTCURSOR;
use Predis\Command\Container\XGROUP;
use Predis\Command\Redis\VADD;
/**
* Interface defining a client-side context such as a pipeline or transaction.
@ -283,6 +284,21 @@ use Predis\Command\Container\XGROUP;
* @method $this tsqueryindex(string ...$filterExpression)
* @method $this tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method $this tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method $this xack(string $key, string $group, string ...$id)
* @method $this xackdel(string $key, string $group, string $mode, array $ids)
* @method $this xadd(string $key, array $dictionary, string $id = '*', array $options = null)
* @method $this xautoclaim(string $key, string $group, string $consumer, int $minIdleTime, string $start, ?int $count = null, bool $justId = false)
* @method $this xclaim(string $key, string $group, string $consumer, int $minIdleTime, string|array $ids, ?int $idle = null, ?int $time = null, ?int $retryCount = null, bool $force = false, bool $justId = false, ?string $lastId = null)
* @method $this xdel(string $key, string ...$id)
* @method $this xdelex(string $key, string $mode, array $ids)
* @method $this xlen(string $key)
* @method $this xpending(string $key, string $group, ?int $minIdleTime = null, ?string $start = null, ?string $end = null, ?int $count = null, ?string $consumer = null)
* @method $this xrevrange(string $key, string $end, string $start, ?int $count = null)
* @method $this xrange(string $key, string $start, string $end, ?int $count = null)
* @method $this xread(int $count = null, int $block = null, array $streams = null, string ...$id)
* @method $this xreadgroup(string $group, string $consumer, ?int $count = null, ?int $blockMs = null, bool $noAck = false, string ...$keyOrId)
* @method $this xsetid(string $key, string $lastId, ?int $entriesAdded = null, ?string $maxDeleteId = null)
* @method $this xtrim(string $key, array|string $strategy, string $threshold, array $options = null)
* @method $this zadd($key, array $membersAndScoresDictionary)
* @method $this zcard($key)
* @method $this zcount($key, $min, $max)
@ -325,6 +341,17 @@ use Predis\Command\Container\XGROUP;
* @method $this unwatch()
* @method $this waitaof(int $numLocal, int $numReplicas, int $timeout)
* @method $this unsubscribe(string ...$channels)
* @method $this vadd(string $key, string|array $vector, string $elem, int $dim = null, bool $cas = false, string $quant = VADD::QUANT_DEFAULT, ?int $BEF = null, string|array $attributes = null, int $numlinks = null)
* @method $this vcard(string $key)
* @method $this vdim(int $key)
* @method $this vemb(string $key, string $elem, bool $raw = false)
* @method $this vgetattr(string $key, string $elem, bool $asJson = false)
* @method $this vinfo(string $key)
* @method $this vlinks(string $key, string $elem, bool $withScores = false)
* @method $this vrandmember(string $key, int $count = null)
* @method $this vrem(string $key, string $elem)
* @method $this vsetattr(string $key, string $elem, string|array $attributes)
* @method $this vsim(string $key, string|array $vectorOrElem, bool $isElem = false, bool $withScores = false, int $count = null, float $epsilon = null, int $ef = null, string $filter = null, int $filterEf = null, bool $truth = false, bool $noThread = false)
* @method $this watch($key)
* @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
* @method $this eval_ro(string $script, array $keys, ...$argument)

View File

@ -47,6 +47,7 @@ use Predis\Command\Container\Search\FTCURSOR;
use Predis\Command\Container\XGROUP;
use Predis\Command\Container\XINFO;
use Predis\Command\FactoryInterface;
use Predis\Command\Redis\VADD;
use Predis\Configuration\OptionsInterface;
use Predis\Connection\ConnectionInterface;
use Predis\Response\Status;
@ -294,18 +295,24 @@ use Predis\Response\Status;
* @method array tsqueryindex(string ...$filterExpression)
* @method array tsrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method array tsrevrange(string $key, $fromTimestamp, $toTimestamp, ?RangeArguments $arguments = null)
* @method int xack(string $key, string $group, string ...$id)
* @method array xackdel(string $key, string $group, string $mode, array $ids)
* @method string xadd(string $key, array $dictionary, string $id = '*', array $options = null)
* @method array xautoclaim(string $key, string $group, string $consumer, int $minIdleTime, string $start, ?int $count = null, bool $justId = false)
* @method array xclaim(string $key, string $group, string $consumer, int $minIdleTime, string|array $ids, ?int $idle = null, ?int $time = null, ?int $retryCount = null, bool $force = false, bool $justId = false, ?string $lastId = null)
* @method int xdel(string $key, string ...$id)
* @method array xdelex(string $key, string $mode, array $ids)
* @method int xlen(string $key)
* @method array xpending(string $key, string $group, ?int $minIdleTime = null, ?string $start = null, ?string $end = null, ?int $count = null, ?string $consumer = null)
* @method array xrevrange(string $key, string $end, string $start, ?int $count = null)
* @method array xrange(string $key, string $start, string $end, ?int $count = null)
* @method array|null xread(int $count = null, int $block = null, array $streams = null, string ...$id)
* @method array xreadgroup(string $group, string $consumer, ?int $count = null, ?int $blockMs = null, bool $noAck = false, string ...$keyOrId)
* @method Status xsetid(string $key, string $lastId, ?int $entriesAdded = null, ?string $maxDeleteId = null)
* @method string xtrim(string $key, array|string $strategy, string $threshold, array $options = null)
* @method int zadd(string $key, array $membersAndScoresDictionary)
* @method int zcard(string $key)
* @method string zcount(string $key, int|string $min, int|string $max)
* @method int zcount(string $key, int|string $min, int|string $max)
* @method array zdiff(array $keys, bool $withScores = false)
* @method int zdiffstore(string $destination, array $keys)
* @method string zincrby(string $key, int $increment, string $member)
@ -346,6 +353,17 @@ use Predis\Response\Status;
* @method mixed multi()
* @method mixed unwatch()
* @method array unsubscribe(string ...$channels)
* @method bool vadd(string $key, string|array $vector, string $elem, int $dim = null, bool $cas = false, string $quant = VADD::QUANT_DEFAULT, int $bef = null, string|array $attributes = null, int $numlinks = null)
* @method int vcard(string $key)
* @method int vdim(string $key)
* @method array vemb(string $key, string $elem, bool $raw = false)
* @method string|array|null vgetattr(string $key, string $elem, bool $asJson = false)
* @method array|null vinfo(string $key)
* @method array|null vlinks(string $key, string $elem, bool $withScores = false)
* @method string|array|null vrandmember(string $key, int $count = null)
* @method bool vrem(string $key, string $elem)
* @method array vsim(string $key, string|array $vectorOrElem, bool $isElem = false, bool $withScores = false, int $count = null, float $epsilon = null, int $ef = null, string $filter = null, int $filterEf = null, bool $truth = false, bool $noThread = false)
* @method bool vsetattr(string $key, string $elem, string|array $attributes)
* @method array waitaof(int $numLocal, int $numReplicas, int $timeout)
* @method mixed watch(string[]|string $keyOrKeys)
* @method mixed eval(string $script, int $numkeys, string ...$keyOrArg = null)

View File

@ -32,7 +32,7 @@ abstract class PrefixableCommand extends Command implements PrefixableCommandInt
*/
public function applyPrefixForAllArguments(string $prefix): void
{
$this->setArguments(
$this->setRawArguments(
array_map(static function ($key) use ($prefix) {
return $prefix . $key;
}, $this->getArguments())
@ -49,7 +49,7 @@ abstract class PrefixableCommand extends Command implements PrefixableCommandInt
{
$arguments = $this->getArguments();
$arguments[0] = $prefix . $arguments[0];
$this->setArguments($arguments);
$this->setRawArguments($arguments);
}
/**

View File

@ -12,6 +12,7 @@
namespace Predis\Command\Redis;
use InvalidArgumentException;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
@ -19,6 +20,8 @@ use Predis\Command\PrefixableCommand as RedisCommand;
*/
class BITOP extends RedisCommand
{
private const VALID_OPERATIONS = ['AND', 'OR', 'XOR', 'NOT', 'DIFF', 'DIFF1', 'ANDOR', 'ONE'];
/**
* {@inheritdoc}
*/
@ -38,6 +41,13 @@ class BITOP extends RedisCommand
array_unshift($arguments, $operation, $destination);
}
if (!empty($arguments)) {
$operation = strtoupper($arguments[0]);
if (!in_array($operation, self::VALID_OPERATIONS, false)) {
throw new InvalidArgumentException('BITOP operation must be one of: AND, OR, XOR, NOT, DIFF, DIFF1, ANDOR, ONE');
}
}
parent::setArguments($arguments);
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/bf.add/
@ -26,4 +26,9 @@ class BFADD extends RedisCommand
{
return 'BF.ADD';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/bf.exists/
@ -25,4 +25,9 @@ class BFEXISTS extends RedisCommand
{
return 'BF.EXISTS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
use UnexpectedValueException;
/**
@ -76,4 +76,9 @@ class BFINFO extends RedisCommand
return $data;
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Traits\BloomFilters\Capacity;
use Predis\Command\Traits\BloomFilters\Error;
use Predis\Command\Traits\BloomFilters\Expansion;
@ -48,6 +48,11 @@ class BFINSERT extends RedisCommand
return 'BF.INSERT';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
public function setArguments(array $arguments)
{
$this->setNoCreate($arguments);

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/bf.loadchunk/
@ -25,4 +25,9 @@ class BFLOADCHUNK extends RedisCommand
{
return 'BF.LOADCHUNK';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/bf.madd/
@ -26,4 +26,9 @@ class BFMADD extends RedisCommand
{
return 'BF.MADD';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/bf.mexists/
@ -25,4 +25,9 @@ class BFMEXISTS extends RedisCommand
{
return 'BF.MEXISTS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Traits\BloomFilters\Expansion;
/**
@ -46,4 +46,9 @@ class BFRESERVE extends RedisCommand
$this->setExpansion($arguments);
$this->filterArguments();
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\BloomFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/bf.scandump/
@ -26,4 +26,9 @@ class BFSCANDUMP extends RedisCommand
{
return 'BF.SCANDUMP';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CountMinSketch;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cms.incrby/
@ -26,4 +26,9 @@ class CMSINCRBY extends RedisCommand
{
return 'CMS.INCRBY';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CountMinSketch;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cms.info/
@ -42,4 +42,9 @@ class CMSINFO extends RedisCommand
return $data;
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CountMinSketch;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cms.initbydim/
@ -25,4 +25,9 @@ class CMSINITBYDIM extends RedisCommand
{
return 'CMS.INITBYDIM';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CountMinSketch;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cms.initbyprob/
@ -25,4 +25,9 @@ class CMSINITBYPROB extends RedisCommand
{
return 'CMS.INITBYPROB';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CountMinSketch;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cms.merge/
@ -39,4 +39,17 @@ class CMSMERGE extends RedisCommand
parent::setArguments($processedArguments);
}
public function prefixKeys($prefix)
{
if ($arguments = $this->getArguments()) {
$arguments[0] = $prefix . $arguments[0];
for ($i = 2, $iMax = (int) $arguments[1] + 2; $i < $iMax; $i++) {
$arguments[$i] = $prefix . $arguments[$i];
}
$this->setRawArguments($arguments);
}
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CountMinSketch;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cms.query/
@ -25,4 +25,9 @@ class CMSQUERY extends RedisCommand
{
return 'CMS.QUERY';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.add/
@ -25,4 +25,9 @@ class CFADD extends RedisCommand
{
return 'CF.ADD';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.addnx/
@ -25,4 +25,9 @@ class CFADDNX extends RedisCommand
{
return 'CF.ADDNX';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.count/
@ -26,4 +26,9 @@ class CFCOUNT extends RedisCommand
{
return 'CF.COUNT';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.del/
@ -27,4 +27,9 @@ class CFDEL extends RedisCommand
{
return 'CF.DEL';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.exists/
@ -25,4 +25,9 @@ class CFEXISTS extends RedisCommand
{
return 'CF.EXISTS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.info/
@ -42,4 +42,9 @@ class CFINFO extends RedisCommand
return $data;
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Traits\BloomFilters\Capacity;
use Predis\Command\Traits\BloomFilters\Items;
use Predis\Command\Traits\BloomFilters\NoCreate;
@ -49,4 +49,9 @@ class CFINSERT extends RedisCommand
$this->setCapacity($arguments);
$this->filterArguments();
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.loadchunk/
@ -26,4 +26,9 @@ class CFLOADCHUNK extends RedisCommand
{
return 'CF.LOADCHUNK';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.mexists/
@ -25,4 +25,9 @@ class CFMEXISTS extends RedisCommand
{
return 'CF.MEXISTS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Traits\BloomFilters\BucketSize;
use Predis\Command\Traits\BloomFilters\Expansion;
use Predis\Command\Traits\BloomFilters\MaxIterations;
@ -49,4 +49,9 @@ class CFRESERVE extends RedisCommand
$this->setBucketSize($arguments);
$this->filterArguments();
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\CuckooFilter;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/cf.scandump/
@ -26,4 +26,9 @@ class CFSCANDUMP extends RedisCommand
{
return 'CF.SCANDUMP';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/expiretime/
@ -26,4 +26,9 @@ class EXPIRETIME extends RedisCommand
{
return 'EXPIRETIME';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Traits\By\GeoBy;
use Predis\Command\Traits\Count;
use Predis\Command\Traits\From\GeoFrom;
@ -119,4 +119,9 @@ class GEOSEARCH extends RedisCommand
return $parsedData;
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
class GETDEL extends RedisCommand
{
@ -20,4 +20,9 @@ class GETDEL extends RedisCommand
{
return 'GETDEL';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.arrappend/
@ -25,4 +25,9 @@ class JSONARRAPPEND extends RedisCommand
{
return 'JSON.ARRAPPEND';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.arrindex/
@ -25,4 +25,9 @@ class JSONARRINDEX extends RedisCommand
{
return 'JSON.ARRINDEX';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.arrinsert/
@ -25,4 +25,9 @@ class JSONARRINSERT extends RedisCommand
{
return 'JSON.ARRINSERT';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.arrlen/
@ -25,4 +25,9 @@ class JSONARRLEN extends RedisCommand
{
return 'JSON.ARRLEN';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.arrpop/
@ -25,4 +25,9 @@ class JSONARRPOP extends RedisCommand
{
return 'JSON.ARRPOP';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.arrtrim/
@ -25,4 +25,9 @@ class JSONARRTRIM extends RedisCommand
{
return 'JSON.ARRTRIM';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.clear/
@ -25,4 +25,9 @@ class JSONCLEAR extends RedisCommand
{
return 'JSON.CLEAR';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.del/
@ -25,4 +25,9 @@ class JSONDEL extends RedisCommand
{
return 'JSON.DEL';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.forget/
@ -25,4 +25,9 @@ class JSONFORGET extends RedisCommand
{
return 'JSON.FORGET';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Traits\Json\Indent;
use Predis\Command\Traits\Json\Newline;
use Predis\Command\Traits\Json\Space;
@ -54,4 +54,9 @@ class JSONGET extends RedisCommand
$this->setIndent($arguments);
$this->filterArguments();
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.merge/
@ -26,4 +26,9 @@ class JSONMERGE extends RedisCommand
{
return 'JSON.MERGE';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
class JSONMGET extends RedisCommand
{
@ -33,4 +33,9 @@ class JSONMGET extends RedisCommand
parent::setArguments($unpackedArguments);
}
public function prefixKeys($prefix)
{
$this->applyPrefixSkippingLastArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.mset/
@ -25,4 +25,15 @@ class JSONMSET extends RedisCommand
{
return 'JSON.MSET';
}
public function prefixKeys($prefix)
{
if ($arguments = $this->getArguments()) {
for ($i = 0, $l = count($arguments); $i < $l; $i += 3) {
$arguments[$i] = $prefix . $arguments[$i];
}
$this->setArguments($arguments);
}
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.numincrby/
@ -25,4 +25,9 @@ class JSONNUMINCRBY extends RedisCommand
{
return 'JSON.NUMINCRBY';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.objkeys/
@ -25,4 +25,9 @@ class JSONOBJKEYS extends RedisCommand
{
return 'JSON.OBJKEYS';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.objlen/
@ -25,4 +25,9 @@ class JSONOBJLEN extends RedisCommand
{
return 'JSON.OBJLEN';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.resp/
@ -25,4 +25,9 @@ class JSONRESP extends RedisCommand
{
return 'JSON.RESP';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
use Predis\Command\Traits\Json\NxXxArgument;
/**
@ -38,4 +38,9 @@ class JSONSET extends RedisCommand
$this->setSubcommand($arguments);
$this->filterArguments();
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.strappend/
@ -25,4 +25,9 @@ class JSONSTRAPPEND extends RedisCommand
{
return 'JSON.STRAPPEND';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.strlen/
@ -25,4 +25,9 @@ class JSONSTRLEN extends RedisCommand
{
return 'JSON.STRLEN';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.toggle/
@ -25,4 +25,9 @@ class JSONTOGGLE extends RedisCommand
{
return 'JSON.TOGGLE';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis\Json;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/json.type/
@ -25,4 +25,9 @@ class JSONTYPE extends RedisCommand
{
return 'JSON.TYPE';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
class LMOVE extends RedisCommand
{
@ -20,4 +20,14 @@ class LMOVE extends RedisCommand
{
return 'LMOVE';
}
public function prefixKeys($prefix)
{
if ($arguments = $this->getArguments()) {
$arguments[0] = $prefix . $arguments[0];
$arguments[1] = $prefix . $arguments[1];
$this->setRawArguments($arguments);
}
}
}

View File

@ -12,7 +12,7 @@
namespace Predis\Command\Redis;
use Predis\Command\Command as RedisCommand;
use Predis\Command\PrefixableCommand as RedisCommand;
/**
* @see https://redis.io/commands/smismember/
@ -25,4 +25,9 @@ class SMISMEMBER extends RedisCommand
{
return 'SMISMEMBER';
}
public function prefixKeys($prefix)
{
$this->applyPrefixForFirstArgument($prefix);
}
}

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