From 1a1f98b037d664d9093a620cfa85305c7e50b244 Mon Sep 17 00:00:00 2001 From: Antonio del Olmo <659868+delolmo@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:11:13 +0200 Subject: [PATCH] =?UTF-8?q?[4.x]=20Upgrade=20`brick/math`=20to=20support?= =?UTF-8?q?=20versions=20^0.14=E2=80=93^0.17=20(#638)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 +- composer.lock | 17 +++--- phpstan.neon.dist | 1 + .../Number/GenericNumberConverter.php | 1 + src/Math/BrickMathCalculator.php | 34 ++--------- src/Math/BrickMathRoundingMode.php | 60 +++++++++++++++++++ src/Math/CalculatorInterface.php | 8 +-- .../Number/BigNumberConverterTest.php | 2 +- tests/Math/BrickMathCalculatorTest.php | 3 +- tests/Math/BrickMathRoundingModeTest.php | 57 ++++++++++++++++++ tests/UuidTest.php | 9 ++- 11 files changed, 146 insertions(+), 48 deletions(-) create mode 100644 src/Math/BrickMathRoundingMode.php create mode 100644 tests/Math/BrickMathRoundingModeTest.php diff --git a/composer.json b/composer.json index 178019f..16d8796 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ ], "require": { "php": "^8.0", - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": ">=0.8.16 <=0.17", "ramsey/collection": "^1.2 || ^2.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 5ce3d12..077e3f9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,26 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bb087ab286c5dc749e589e7bb6eb96c1", + "content-hash": "d67e7c6f49a1cb1105b75ffd24d2a49d", "packages": [ { "name": "brick/math", - "version": "0.14.1", + "version": "0.17.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + "reference": "a62af7ab2e3cee9f9bf4cf77a5d1e6ba408a44ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "url": "https://api.github.com/repos/brick/math/zipball/a62af7ab2e3cee9f9bf4cf77a5d1e6ba408a44ee", + "reference": "a62af7ab2e3cee9f9bf4cf77a5d1e6ba408a44ee", "shasum": "" }, "require": { "php": "^8.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", "phpstan/phpstan": "2.1.22", "phpunit/phpunit": "^11.5" }, @@ -56,7 +55,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.1" + "source": "https://github.com/brick/math/tree/0.17.0" }, "funding": [ { @@ -64,7 +63,7 @@ "type": "github" } ], - "time": "2025-11-24T14:40:29+00:00" + "time": "2026-03-17T12:54:54+00:00" }, { "name": "ramsey/collection", @@ -5930,5 +5929,5 @@ "php": "^8.0" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 87969bf..701f651 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,6 +2,7 @@ parameters: tmpDir: ./build/cache/phpstan level: max treatPhpDocTypesAsCertain: false + reportUnmatchedIgnoredErrors: false paths: - ./src - ./tests diff --git a/src/Converter/Number/GenericNumberConverter.php b/src/Converter/Number/GenericNumberConverter.php index 86968ab..c93746a 100644 --- a/src/Converter/Number/GenericNumberConverter.php +++ b/src/Converter/Number/GenericNumberConverter.php @@ -34,6 +34,7 @@ class GenericNumberConverter implements NumberConverterInterface */ public function fromHex(string $hex): string { + /** @var non-empty-string $hex */ return $this->calculator->fromBase($hex, 16)->toString(); } diff --git a/src/Math/BrickMathCalculator.php b/src/Math/BrickMathCalculator.php index 649f580..3009add 100644 --- a/src/Math/BrickMathCalculator.php +++ b/src/Math/BrickMathCalculator.php @@ -17,7 +17,6 @@ namespace Ramsey\Uuid\Math; use Brick\Math\BigDecimal; use Brick\Math\BigInteger; use Brick\Math\Exception\MathException; -use Brick\Math\RoundingMode as BrickMathRounding; use Ramsey\Uuid\Exception\InvalidArgumentException; use Ramsey\Uuid\Type\Decimal; use Ramsey\Uuid\Type\Hexadecimal; @@ -31,19 +30,6 @@ use Ramsey\Uuid\Type\NumberInterface; */ final class BrickMathCalculator implements CalculatorInterface { - private const ROUNDING_MODE_MAP = [ - RoundingMode::UNNECESSARY => BrickMathRounding::UNNECESSARY, - RoundingMode::UP => BrickMathRounding::UP, - RoundingMode::DOWN => BrickMathRounding::DOWN, - RoundingMode::CEILING => BrickMathRounding::CEILING, - RoundingMode::FLOOR => BrickMathRounding::FLOOR, - RoundingMode::HALF_UP => BrickMathRounding::HALF_UP, - RoundingMode::HALF_DOWN => BrickMathRounding::HALF_DOWN, - RoundingMode::HALF_CEILING => BrickMathRounding::HALF_CEILING, - RoundingMode::HALF_FLOOR => BrickMathRounding::HALF_FLOOR, - RoundingMode::HALF_EVEN => BrickMathRounding::HALF_EVEN, - ]; - public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface { $sum = BigInteger::of($augend->toString()); @@ -87,7 +73,7 @@ final class BrickMathCalculator implements CalculatorInterface NumberInterface ...$divisors, ): NumberInterface { /** @phpstan-ignore possiblyImpure.methodCall */ - $brickRounding = $this->getBrickRoundingMode($roundingMode); + $brickRounding = BrickMathRoundingMode::resolve($roundingMode); $quotient = BigDecimal::of($dividend->toString()); @@ -111,8 +97,8 @@ final class BrickMathCalculator implements CalculatorInterface return new IntegerObject((string) BigInteger::fromBase($value, $base)); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( - $exception->getMessage(), - (int) $exception->getCode(), + $exception->getMessage(), /** @phpstan-ignore possiblyImpure.methodCall */ + (int) $exception->getCode(), /** @phpstan-ignore possiblyImpure.methodCall */ $exception ); } @@ -124,8 +110,8 @@ final class BrickMathCalculator implements CalculatorInterface return BigInteger::of($value->toString())->toBase($base); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( - $exception->getMessage(), - (int) $exception->getCode(), + $exception->getMessage(), /** @phpstan-ignore possiblyImpure.methodCall */ + (int) $exception->getCode(), /** @phpstan-ignore possiblyImpure.methodCall */ $exception ); } @@ -141,14 +127,4 @@ final class BrickMathCalculator implements CalculatorInterface { return $this->fromBase($value->toString(), 16); } - - /** - * Maps ramsey/uuid rounding modes to those used by brick/math - * - * @return BrickMathRounding::* - */ - private function getBrickRoundingMode(int $roundingMode) - { - return self::ROUNDING_MODE_MAP[$roundingMode] ?? BrickMathRounding::UNNECESSARY; - } } diff --git a/src/Math/BrickMathRoundingMode.php b/src/Math/BrickMathRoundingMode.php new file mode 100644 index 0000000..e3271ac --- /dev/null +++ b/src/Math/BrickMathRoundingMode.php @@ -0,0 +1,60 @@ + + * @license http://opensource.org/licenses/MIT MIT + */ + +declare(strict_types=1); + +namespace Ramsey\Uuid\Math; + +use Brick\Math\RoundingMode as BrickMathRounding; + +/** + * @internal Polyfill for Brick\Math\RoundingMode constant naming + * changes introduced in brick/math 0.15 (UPPER_SNAKE_CASE → PascalCase) + */ +final class BrickMathRoundingMode +{ + /** + * Maps ramsey/uuid rounding mode constants to their PascalCase (>= 0.15) + * and UPPER_SNAKE_CASE (< 0.15) equivalents in brick/math + */ + private const ROUNDING_MODE_MAP = [ + RoundingMode::UNNECESSARY => ['Unnecessary', 'UNNECESSARY'], + RoundingMode::UP => ['Up', 'UP'], + RoundingMode::DOWN => ['Down', 'DOWN'], + RoundingMode::CEILING => ['Ceiling', 'CEILING'], + RoundingMode::FLOOR => ['Floor', 'FLOOR'], + RoundingMode::HALF_UP => ['HalfUp', 'HALF_UP'], + RoundingMode::HALF_DOWN => ['HalfDown', 'HALF_DOWN'], + RoundingMode::HALF_CEILING => ['HalfCeiling', 'HALF_CEILING'], + RoundingMode::HALF_FLOOR => ['HalfFloor', 'HALF_FLOOR'], + RoundingMode::HALF_EVEN => ['HalfEven', 'HALF_EVEN'], + ]; + + /** + * Resolves a ramsey/uuid rounding mode to the correct + * Brick\Math\RoundingMode value for the installed version. + * + * @return BrickMathRounding::* + */ + public static function resolve(int $roundingMode) + { + [$pascal, $snake] = self::ROUNDING_MODE_MAP[$roundingMode] + ?? self::ROUNDING_MODE_MAP[RoundingMode::UNNECESSARY]; + + $class = BrickMathRounding::class; + + /** @var BrickMathRounding::* */ + return defined("$class::$pascal") + ? constant("$class::$pascal") + : constant("$class::$snake"); + } +} diff --git a/src/Math/CalculatorInterface.php b/src/Math/CalculatorInterface.php index e6789a6..d71681f 100644 --- a/src/Math/CalculatorInterface.php +++ b/src/Math/CalculatorInterface.php @@ -65,7 +65,7 @@ interface CalculatorInterface * Returns the quotient of the provided parameters divided left-to-right * * @param int $roundingMode The RoundingMode constant to use for this operation - * @param int $scale The scale to use for this operation + * @param int<0, max> $scale The scale to use for this operation * @param NumberInterface $dividend The integer to be divided * @param NumberInterface ...$divisors The integers to divide $dividend by, in the order in which the division * operations should take place (left-to-right) @@ -84,8 +84,8 @@ interface CalculatorInterface /** * Converts a value from an arbitrary base to a base-10 integer value * - * @param string $value The value to convert - * @param int $base The base to convert from (i.e., 2, 16, 32, etc.) + * @param non-empty-string $value The value to convert + * @param int<2, 36> $base The base to convert from (i.e., 2, 16, 32, etc.) * * @return IntegerObject The base-10 integer value of the converted value * @@ -97,7 +97,7 @@ interface CalculatorInterface * Converts a base-10 integer value to an arbitrary base * * @param IntegerObject $value The integer value to convert - * @param int $base The base to convert to (i.e., 2, 16, 32, etc.) + * @param int<2, 36> $base The base to convert to (i.e., 2, 16, 32, etc.) * * @return string The value represented in the specified base * diff --git a/tests/Converter/Number/BigNumberConverterTest.php b/tests/Converter/Number/BigNumberConverterTest.php index 83bcff2..8d32b36 100644 --- a/tests/Converter/Number/BigNumberConverterTest.php +++ b/tests/Converter/Number/BigNumberConverterTest.php @@ -15,7 +15,7 @@ class BigNumberConverterTest extends TestCase $converter = new BigNumberConverter(); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('"." is not a valid character in base 16'); + $this->expectExceptionMessageMatches('/"\." is not (a )?valid (character )?in base 16/'); $converter->fromHex('123.34'); } diff --git a/tests/Math/BrickMathCalculatorTest.php b/tests/Math/BrickMathCalculatorTest.php index 8cb5d90..3de267c 100644 --- a/tests/Math/BrickMathCalculatorTest.php +++ b/tests/Math/BrickMathCalculatorTest.php @@ -96,7 +96,7 @@ class BrickMathCalculatorTest extends TestCase $calculator = new BrickMathCalculator(); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('"o" is not a valid character in base 16'); + $this->expectExceptionMessageMatches('/"o" is not (a )?valid (character )?in base 16/'); $calculator->fromBase('foobar', 16); } @@ -108,6 +108,7 @@ class BrickMathCalculatorTest extends TestCase $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Base 1024 is out of range [2, 36]'); + /** @phpstan-ignore argument.type */ $calculator->toBase(new IntegerObject(42), 1024); } } diff --git a/tests/Math/BrickMathRoundingModeTest.php b/tests/Math/BrickMathRoundingModeTest.php new file mode 100644 index 0000000..374eb6d --- /dev/null +++ b/tests/Math/BrickMathRoundingModeTest.php @@ -0,0 +1,57 @@ +assertTrue( + defined(BrickMathRounding::class . '::' . $expectedName), + "Expected constant Brick\\Math\\RoundingMode::$expectedName to exist", + ); + $this->assertSame(constant(BrickMathRounding::class . '::' . $expectedName), $result); + } + + /** + * @return array + */ + public static function roundingModeProvider(): array + { + $usesPascalCase = defined(BrickMathRounding::class . '::Unnecessary'); + + return [ + 'UNNECESSARY' => [RoundingMode::UNNECESSARY, $usesPascalCase ? 'Unnecessary' : 'UNNECESSARY'], + 'UP' => [RoundingMode::UP, $usesPascalCase ? 'Up' : 'UP'], + 'DOWN' => [RoundingMode::DOWN, $usesPascalCase ? 'Down' : 'DOWN'], + 'CEILING' => [RoundingMode::CEILING, $usesPascalCase ? 'Ceiling' : 'CEILING'], + 'FLOOR' => [RoundingMode::FLOOR, $usesPascalCase ? 'Floor' : 'FLOOR'], + 'HALF_UP' => [RoundingMode::HALF_UP, $usesPascalCase ? 'HalfUp' : 'HALF_UP'], + 'HALF_DOWN' => [RoundingMode::HALF_DOWN, $usesPascalCase ? 'HalfDown' : 'HALF_DOWN'], + 'HALF_CEILING' => [RoundingMode::HALF_CEILING, $usesPascalCase ? 'HalfCeiling' : 'HALF_CEILING'], + 'HALF_FLOOR' => [RoundingMode::HALF_FLOOR, $usesPascalCase ? 'HalfFloor' : 'HALF_FLOOR'], + 'HALF_EVEN' => [RoundingMode::HALF_EVEN, $usesPascalCase ? 'HalfEven' : 'HALF_EVEN'], + ]; + } + + public function testResolveDefaultsToUnnecessaryForUnknownMode(): void + { + $result = BrickMathRoundingMode::resolve(999); + + $expectedName = defined(BrickMathRounding::class . '::Unnecessary') ? 'Unnecessary' : 'UNNECESSARY'; + + $this->assertSame(constant(BrickMathRounding::class . '::' . $expectedName), $result); + } +} diff --git a/tests/UuidTest.php b/tests/UuidTest.php index 415b0c3..494f417 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -6,7 +6,6 @@ namespace Ramsey\Uuid\Test; use BadMethodCallException; use Brick\Math\BigDecimal; -use Brick\Math\RoundingMode; use DateTimeImmutable; use DateTimeInterface; use Mockery; @@ -28,6 +27,8 @@ use Ramsey\Uuid\Generator\RandomGeneratorFactory; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Guid\Guid; use Ramsey\Uuid\Lazy\LazyUuidFromString; +use Ramsey\Uuid\Math\BrickMathRoundingMode; +use Ramsey\Uuid\Math\RoundingMode; use Ramsey\Uuid\Provider\Node\RandomNodeProvider; use Ramsey\Uuid\Provider\Time\FixedTimeProvider; use Ramsey\Uuid\Rfc4122\Fields; @@ -1214,8 +1215,10 @@ class UuidTest extends TestCase ); // Assert that the time matches - $usecAdd = BigDecimal::of($usec)->dividedBy('1000000', 14, RoundingMode::HALF_UP); - $testTime = BigDecimal::of($currentTime)->plus($usecAdd)->toScale(0, RoundingMode::DOWN); + $halfUp = BrickMathRoundingMode::resolve(RoundingMode::HALF_UP); + $down = BrickMathRoundingMode::resolve(RoundingMode::DOWN); + $usecAdd = BigDecimal::of($usec)->dividedBy('1000000', 14, $halfUp); + $testTime = BigDecimal::of($currentTime)->plus($usecAdd)->toScale(0, $down); $this->assertSame((string) $testTime, (string) $uuid64->getDateTime()->getTimestamp()); $this->assertSame((string) $testTime, (string) $uuid32->getDateTime()->getTimestamp()); }