From ae622b3e2335a09e4c5dc490fbced97445f65c0d Mon Sep 17 00:00:00 2001 From: gertdepagter Date: Thu, 12 Mar 2026 09:54:33 +0100 Subject: [PATCH 1/3] Allow brick math 0.15 and 0.16 0.15 removed the 'old' rounding mode constants, so this includes a BC layer in the `BrickMathCalculator` --- composer.json | 2 +- composer.lock | 17 ++--- src/Math/BrickMathCalculator.php | 56 ++++++++++---- tests/Builder/FallbackBuilderTest.php | 74 +++++++++---------- .../Number/BigNumberConverterTest.php | 2 +- tests/Math/BrickMathCalculatorTest.php | 2 +- tests/UuidTest.php | 12 ++- 7 files changed, 99 insertions(+), 66 deletions(-) diff --git a/composer.json b/composer.json index 178019f4..a420e5e6 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.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16", "ramsey/collection": "^1.2 || ^2.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 5ce3d12c..d275937e 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": "dbec92684751a2779a8432fcb664f7fb", "packages": [ { "name": "brick/math", - "version": "0.14.1", + "version": "0.16.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + "reference": "9b8bd30c534479b1cd279eec37a7b0aed30a323c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "url": "https://api.github.com/repos/brick/math/zipball/9b8bd30c534479b1cd279eec37a7b0aed30a323c", + "reference": "9b8bd30c534479b1cd279eec37a7b0aed30a323c", "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.16.1" }, "funding": [ { @@ -64,7 +63,7 @@ "type": "github" } ], - "time": "2025-11-24T14:40:29+00:00" + "time": "2026-03-09T13:41:19+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/src/Math/BrickMathCalculator.php b/src/Math/BrickMathCalculator.php index 649f5803..426da725 100644 --- a/src/Math/BrickMathCalculator.php +++ b/src/Math/BrickMathCalculator.php @@ -31,18 +31,10 @@ */ 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, - ]; + /** + * @var array|null $roundingModeMap + */ + private static ?array $roundingModeMap = null; public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface { @@ -149,6 +141,44 @@ public function toInteger(Hexadecimal $value): IntegerObject */ private function getBrickRoundingMode(int $roundingMode) { - return self::ROUNDING_MODE_MAP[$roundingMode] ?? BrickMathRounding::UNNECESSARY; + return self::getRoundingMap()[$roundingMode] ?? self::getRoundingMap()[0]; + } + + /** + * @return array + */ + private static function getRoundingMap(): array + { + if (self::$roundingModeMap === null) { + if (defined(BrickMathRounding::class . '::UNNECESSARY')) { + self::$roundingModeMap = [ + 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, + ]; + } else { + self::$roundingModeMap = [ + RoundingMode::UNNECESSARY => BrickMathRounding::Unnecessary, + RoundingMode::UP => BrickMathRounding::Up, + RoundingMode::DOWN => BrickMathRounding::Down, + RoundingMode::CEILING => BrickMathRounding::Ceiling, + RoundingMode::FLOOR => BrickMathRounding::Floor, + RoundingMode::HALF_UP => BrickMathRounding::HalfUp, + RoundingMode::HALF_DOWN => BrickMathRounding::HalfDown, + RoundingMode::HALF_CEILING => BrickMathRounding::HalfCeiling, + RoundingMode::HALF_FLOOR => BrickMathRounding::HalfFloor, + RoundingMode::HALF_EVEN => BrickMathRounding::HalfEven, + ]; + } + } + + return self::$roundingModeMap; } } diff --git a/tests/Builder/FallbackBuilderTest.php b/tests/Builder/FallbackBuilderTest.php index 31b9589b..5664e8ac 100644 --- a/tests/Builder/FallbackBuilderTest.php +++ b/tests/Builder/FallbackBuilderTest.php @@ -4,15 +4,11 @@ namespace Ramsey\Uuid\Test\Builder; -use Mockery; -use Ramsey\Uuid\Builder\FallbackBuilder; use Ramsey\Uuid\Builder\UuidBuilderInterface; -use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Codec\StringCodec; use Ramsey\Uuid\Converter\Number\GenericNumberConverter; use Ramsey\Uuid\Converter\Time\GenericTimeConverter; use Ramsey\Uuid\Converter\Time\PhpTimeConverter; -use Ramsey\Uuid\Exception\BuilderNotFoundException; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\Guid\GuidBuilder; use Ramsey\Uuid\Math\BrickMathCalculator; @@ -25,41 +21,41 @@ class FallbackBuilderTest extends TestCase { - public function testBuildThrowsExceptionAfterAllConfiguredBuildersHaveErrored(): void - { - $codec = Mockery::mock(CodecInterface::class); - $bytes = 'foobar'; - - $builder1 = Mockery::mock(UuidBuilderInterface::class); - $builder1 - ->shouldReceive('build') - ->once() - ->with($codec, $bytes) - ->andThrow(UnableToBuildUuidException::class); - - $builder2 = Mockery::mock(UuidBuilderInterface::class); - $builder2 - ->shouldReceive('build') - ->once() - ->with($codec, $bytes) - ->andThrow(UnableToBuildUuidException::class); - - $builder3 = Mockery::mock(UuidBuilderInterface::class); - $builder3 - ->shouldReceive('build') - ->once() - ->with($codec, $bytes) - ->andThrow(UnableToBuildUuidException::class); - - $fallbackBuilder = new FallbackBuilder([$builder1, $builder2, $builder3]); - - $this->expectException(BuilderNotFoundException::class); - $this->expectExceptionMessage( - 'Could not find a suitable builder for the provided codec and fields' - ); - - $fallbackBuilder->build($codec, $bytes); - } +// public function testBuildThrowsExceptionAfterAllConfiguredBuildersHaveErrored(): void +// { +// $codec = Mockery::mock(CodecInterface::class); +// $bytes = 'foobar'; +// +// $builder1 = Mockery::mock(UuidBuilderInterface::class); +// $builder1 +// ->shouldReceive('build') +// ->once() +// ->with($codec, $bytes) +// ->andThrow(UnableToBuildUuidException::class); +// +// $builder2 = Mockery::mock(UuidBuilderInterface::class); +// $builder2 +// ->shouldReceive('build') +// ->once() +// ->with($codec, $bytes) +// ->andThrow(UnableToBuildUuidException::class); +// +// $builder3 = Mockery::mock(UuidBuilderInterface::class); +// $builder3 +// ->shouldReceive('build') +// ->once() +// ->with($codec, $bytes) +// ->andThrow(UnableToBuildUuidException::class); +// +// $fallbackBuilder = new FallbackBuilder([$builder1, $builder2, $builder3]); +// +// $this->expectException(BuilderNotFoundException::class); +// $this->expectExceptionMessage( +// 'Could not find a suitable builder for the provided codec and fields' +// ); +// +// $fallbackBuilder->build($codec, $bytes); +// } /** * @dataProvider provideBytes diff --git a/tests/Converter/Number/BigNumberConverterTest.php b/tests/Converter/Number/BigNumberConverterTest.php index 83bcff23..8d32b364 100644 --- a/tests/Converter/Number/BigNumberConverterTest.php +++ b/tests/Converter/Number/BigNumberConverterTest.php @@ -15,7 +15,7 @@ public function testFromHexThrowsExceptionWhenStringDoesNotContainOnlyHexadecima $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 8cb5d906..41b3a380 100644 --- a/tests/Math/BrickMathCalculatorTest.php +++ b/tests/Math/BrickMathCalculatorTest.php @@ -96,7 +96,7 @@ public function testFromBaseThrowsException(): void $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); } diff --git a/tests/UuidTest.php b/tests/UuidTest.php index 415b0c3a..482d0134 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -1213,9 +1213,17 @@ public function test32BitMatch64BitForOneHourPeriod(): void . "; 32-bit: {$uuid32->toString()}, 64-bit: {$uuid64->toString()}" ); + if (defined(RoundingMode::class . '::HALF_UP')) { + $halfUp = RoundingMode::HALF_UP; + $down = RoundingMode::DOWN; + } else { + $halfUp = RoundingMode::HalfUp; + $down = RoundingMode::Down; + } + // 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); + $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()); } From 5fdede4d65378425ad49c78ffa251c3973de767c Mon Sep 17 00:00:00 2001 From: gertdepagter Date: Wed, 15 Apr 2026 08:34:31 +0200 Subject: [PATCH 2/3] Add phpstan ignores to the BC layer These should be safe to remove when the BC layer is removed again --- phpstan.neon.dist | 6 ++++++ src/Math/BrickMathCalculator.php | 7 +++++++ tests/UuidTest.php | 2 ++ 3 files changed, 15 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 87969bfc..6f265fb1 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -18,3 +18,9 @@ parameters: - identifier: staticMethod.resultUnused path: tests/* + # Workaround to deal with the BC layer for brick math. + - + message: "#^Access to undefined constant Brick\\\\Math#" + - + message: "#of (static )?method Brick\\\\Math#" + diff --git a/src/Math/BrickMathCalculator.php b/src/Math/BrickMathCalculator.php index 426da725..eae038a0 100644 --- a/src/Math/BrickMathCalculator.php +++ b/src/Math/BrickMathCalculator.php @@ -33,6 +33,7 @@ final class BrickMathCalculator implements CalculatorInterface { /** * @var array|null $roundingModeMap + * @phpstan-ignore-next-line */ private static ?array $roundingModeMap = null; @@ -103,7 +104,9 @@ public function fromBase(string $value, int $base): IntegerObject return new IntegerObject((string) BigInteger::fromBase($value, $base)); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( + /** @phpstan-ignore possiblyImpure.methodCall */ $exception->getMessage(), + /** @phpstan-ignore possiblyImpure.methodCall */ (int) $exception->getCode(), $exception ); @@ -116,7 +119,9 @@ public function toBase(IntegerObject $value, int $base): string return BigInteger::of($value->toString())->toBase($base); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( + /** @phpstan-ignore possiblyImpure.methodCall */ $exception->getMessage(), + /** @phpstan-ignore possiblyImpure.methodCall */ (int) $exception->getCode(), $exception ); @@ -151,6 +156,7 @@ private static function getRoundingMap(): array { if (self::$roundingModeMap === null) { if (defined(BrickMathRounding::class . '::UNNECESSARY')) { + /** @phpstan-ignore-next-line */ self::$roundingModeMap = [ RoundingMode::UNNECESSARY => BrickMathRounding::UNNECESSARY, RoundingMode::UP => BrickMathRounding::UP, @@ -179,6 +185,7 @@ private static function getRoundingMap(): array } } + /** @phpstan-ignore-next-line */ return self::$roundingModeMap; } } diff --git a/tests/UuidTest.php b/tests/UuidTest.php index 482d0134..d2e2cbc0 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -1222,7 +1222,9 @@ public function test32BitMatch64BitForOneHourPeriod(): void } // Assert that the time matches + // @phpstan-ignore-next-line $usecAdd = BigDecimal::of($usec)->dividedBy('1000000', 14, $halfUp); + // @phpstan-ignore-next-line $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()); From bdcd9cbf921cdac98c572037ae8838be0cc9d573 Mon Sep 17 00:00:00 2001 From: gertdepagter Date: Wed, 15 Apr 2026 08:41:19 +0200 Subject: [PATCH 3/3] Do not ignore test --- tests/Builder/FallbackBuilderTest.php | 74 ++++++++++++++------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/tests/Builder/FallbackBuilderTest.php b/tests/Builder/FallbackBuilderTest.php index 5664e8ac..31b9589b 100644 --- a/tests/Builder/FallbackBuilderTest.php +++ b/tests/Builder/FallbackBuilderTest.php @@ -4,11 +4,15 @@ namespace Ramsey\Uuid\Test\Builder; +use Mockery; +use Ramsey\Uuid\Builder\FallbackBuilder; use Ramsey\Uuid\Builder\UuidBuilderInterface; +use Ramsey\Uuid\Codec\CodecInterface; use Ramsey\Uuid\Codec\StringCodec; use Ramsey\Uuid\Converter\Number\GenericNumberConverter; use Ramsey\Uuid\Converter\Time\GenericTimeConverter; use Ramsey\Uuid\Converter\Time\PhpTimeConverter; +use Ramsey\Uuid\Exception\BuilderNotFoundException; use Ramsey\Uuid\Exception\UnableToBuildUuidException; use Ramsey\Uuid\Guid\GuidBuilder; use Ramsey\Uuid\Math\BrickMathCalculator; @@ -21,41 +25,41 @@ class FallbackBuilderTest extends TestCase { -// public function testBuildThrowsExceptionAfterAllConfiguredBuildersHaveErrored(): void -// { -// $codec = Mockery::mock(CodecInterface::class); -// $bytes = 'foobar'; -// -// $builder1 = Mockery::mock(UuidBuilderInterface::class); -// $builder1 -// ->shouldReceive('build') -// ->once() -// ->with($codec, $bytes) -// ->andThrow(UnableToBuildUuidException::class); -// -// $builder2 = Mockery::mock(UuidBuilderInterface::class); -// $builder2 -// ->shouldReceive('build') -// ->once() -// ->with($codec, $bytes) -// ->andThrow(UnableToBuildUuidException::class); -// -// $builder3 = Mockery::mock(UuidBuilderInterface::class); -// $builder3 -// ->shouldReceive('build') -// ->once() -// ->with($codec, $bytes) -// ->andThrow(UnableToBuildUuidException::class); -// -// $fallbackBuilder = new FallbackBuilder([$builder1, $builder2, $builder3]); -// -// $this->expectException(BuilderNotFoundException::class); -// $this->expectExceptionMessage( -// 'Could not find a suitable builder for the provided codec and fields' -// ); -// -// $fallbackBuilder->build($codec, $bytes); -// } + public function testBuildThrowsExceptionAfterAllConfiguredBuildersHaveErrored(): void + { + $codec = Mockery::mock(CodecInterface::class); + $bytes = 'foobar'; + + $builder1 = Mockery::mock(UuidBuilderInterface::class); + $builder1 + ->shouldReceive('build') + ->once() + ->with($codec, $bytes) + ->andThrow(UnableToBuildUuidException::class); + + $builder2 = Mockery::mock(UuidBuilderInterface::class); + $builder2 + ->shouldReceive('build') + ->once() + ->with($codec, $bytes) + ->andThrow(UnableToBuildUuidException::class); + + $builder3 = Mockery::mock(UuidBuilderInterface::class); + $builder3 + ->shouldReceive('build') + ->once() + ->with($codec, $bytes) + ->andThrow(UnableToBuildUuidException::class); + + $fallbackBuilder = new FallbackBuilder([$builder1, $builder2, $builder3]); + + $this->expectException(BuilderNotFoundException::class); + $this->expectExceptionMessage( + 'Could not find a suitable builder for the provided codec and fields' + ); + + $fallbackBuilder->build($codec, $bytes); + } /** * @dataProvider provideBytes