diff --git a/src/XMLSchema/Type/IntegerValue.php b/src/XMLSchema/Type/IntegerValue.php index 2ca85a4b..cdc00e7f 100644 --- a/src/XMLSchema/Type/IntegerValue.php +++ b/src/XMLSchema/Type/IntegerValue.php @@ -50,7 +50,14 @@ public function toInteger(): int { $value = $this->getValue(); - if (bccomp($value, strval(PHP_INT_MAX)) === 1) { + try { + $tooHigh = bccomp($value, (string)PHP_INT_MAX, 0) === 1; + $tooLow = bccomp($value, (string)PHP_INT_MIN, 0) === -1; + } catch (\ValueError $e) { + throw new SchemaViolationException("Not a well-formed integer string.", previous: $e); + } + + if ($tooHigh || $tooLow) { throw new RuntimeException("Cannot convert to integer: out of bounds."); } diff --git a/tests/XMLSchema/Type/IntegerFromIntegerTest.php b/tests/XMLSchema/Type/IntegerFromIntegerTest.php new file mode 100644 index 00000000..5b6d8dbc --- /dev/null +++ b/tests/XMLSchema/Type/IntegerFromIntegerTest.php @@ -0,0 +1,46 @@ +assertSame((string) $integer, $value->getValue()); + $this->assertSame($integer, $value->toInteger()); + } + + + /** + * @return array + */ + public static function provideIntegers(): array + { + return [ + 'negative integer' => [-1234], + 'zero' => [0], + 'positive integer' => [1234], + 'minimum integer' => [PHP_INT_MIN], + 'maximum integer' => [PHP_INT_MAX], + ]; + } +} diff --git a/tests/XMLSchema/Type/IntegerOverflowTest.php b/tests/XMLSchema/Type/IntegerOverflowTest.php new file mode 100644 index 00000000..0424264f --- /dev/null +++ b/tests/XMLSchema/Type/IntegerOverflowTest.php @@ -0,0 +1,91 @@ + $shouldPass + * @param string $integer + * @param string|null $message + */ + #[DataProvider('provideInvalidInteger')] + #[DataProvider('provideValidInteger')] + #[DataProviderExternal(IntegerTest::class, 'provideValidInteger')] + public function testInteger(bool|string $shouldPass, string $integer, ?string $message = null): void + { + try { + IntegerValue::fromString($integer)->toInteger(); + $this->assertTrue($shouldPass); + } catch (RuntimeException | SchemaViolationException $e) { + $this->assertSame($shouldPass, $e::class); + if ($message !== null) { + $this->assertSame($message, $e->getMessage()); + } + } + } + + + /** + * @return array + */ + public static function provideValidInteger(): array + { + return [ + 'valid with whitespace collapse' => [true, " 1234 \n "], + ]; + } + + + /** + * @return array, 1: string, 2?: string}> + */ + public static function provideInvalidInteger(): array + { + return [ + 'empty' => [SchemaViolationException::class, ''], + 'invalid positive signed out-of-bounds' => [ + RuntimeException::class, + '+9223372036854775808', + 'Cannot convert to integer: out of bounds.', + ], + 'invalid negative signed out-of-bounds' => [ + RuntimeException::class, + '-9223372036854775809', + 'Cannot convert to integer: out of bounds.', + ], + 'invalid' => [SchemaViolationException::class, '0x123'], + 'invalid with fractional' => [SchemaViolationException::class, '1234.'], + 'invalid with thousands-delimiter' => [SchemaViolationException::class, '+1,234'], + ]; + } + + + public function testToIntegerWithNonWellFormedIntegerStringThrowsException(): void + { + $this->expectException(SchemaViolationException::class); + $this->expectExceptionMessageMatches('/^Not a well-formed integer string\.$/'); + + /* mock the internal rawValue to test the exception handling within IntegerValue::toInteger() */ + $value = $this->createPartialMock(IntegerValue::class, ['getRawValue']); + $value->expects($this->once())->method('getRawValue')->willReturn('0x42'); + $value->toInteger(); + } +}