diff --git a/CHANGELOG.md b/CHANGELOG.md index b13a29a..689f076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,40 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * Remove dependency on ramsey/collection package. +## 4.9.0 - 2025-06-25 + +### Added + +* Add new `@pure` annotations to the following ([#605](https://github.com/ramsey/uuid/pull/605)): + * `Ramsey\Uuid\Codec\CodecInterface::encode()` + * `Ramsey\Uuid\Codec\CodecInterface::encodeBinary()` + * `Ramsey\Uuid\Codec\CodecInterface::decode()` + * `Ramsey\Uuid\Codec\CodecInterface::decodeBytes()` + * `Ramsey\Uuid\Fields\FieldsInterface::getBytes()` + * `Ramsey\Uuid\Math\CalculatorInterface::add()` + * `Ramsey\Uuid\Math\CalculatorInterface::subtract()` + * `Ramsey\Uuid\Math\CalculatorInterface::multiply()` + * `Ramsey\Uuid\Math\CalculatorInterface::divide()` + * `Ramsey\Uuid\Math\CalculatorInterface::fromBase()` + * `Ramsey\Uuid\Math\CalculatorInterface::toBase()` + * `Ramsey\Uuid\Math\CalculatorInterface::toHexadecimal()` + * `Ramsey\Uuid\Math\CalculatorInterface::toInteger()` + * `Ramsey\Uuid\Nonstandard\Uuid` + * `Ramsey\Uuid\Rfc4122\Fields::isMax()` + * `Ramsey\Uuid\Rfc4122\FieldsInterface::getVersion()` + * `Ramsey\Uuid\Rfc4122\FieldsInterface::isNil()` + * `Ramsey\Uuid\Type\Time::getSeconds()` + * `Ramsey\Uuid\Type\Time::getMicroseconds()` + * `Ramsey\Uuid\Type\TypeInterface::toString()` + * `Ramsey\Uuid\UuidInterface::getBytes()` + * `Ramsey\Uuid\UuidInterface::toString()` + * `Ramsey\Uuid\Validator\ValidatorInterface::validate()` + +### Fixed + +* Restore the `@pure` annotations that were removed in 4.8.0 ([#603](https://github.com/ramsey/uuid/pull/603)). + + ## 4.8.1 - 2025-06-01 ### Fixed diff --git a/composer.lock b/composer.lock index 3eba149..ecb8385 100644 --- a/composer.lock +++ b/composer.lock @@ -65,16 +65,16 @@ "packages-dev": [ { "name": "captainhook/captainhook", - "version": "5.25.2", + "version": "5.25.5", "source": { "type": "git", "url": "https://github.com/captainhook-git/captainhook.git", - "reference": "e096240c130557be6d2059881341db7ebd84f653" + "reference": "7b0feb07ae328c83c458175fe19e5cb55634b1fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/captainhook-git/captainhook/zipball/e096240c130557be6d2059881341db7ebd84f653", - "reference": "e096240c130557be6d2059881341db7ebd84f653", + "url": "https://api.github.com/repos/captainhook-git/captainhook/zipball/7b0feb07ae328c83c458175fe19e5cb55634b1fb", + "reference": "7b0feb07ae328c83c458175fe19e5cb55634b1fb", "shasum": "" }, "require": { @@ -136,8 +136,8 @@ "prepare-commit-msg" ], "support": { - "issues": "https://github.com/captainhookphp/captainhook/issues", - "source": "https://github.com/captainhook-git/captainhook/tree/5.25.2" + "issues": "https://github.com/captainhook-git/captainhook/issues", + "source": "https://github.com/captainhook-git/captainhook/tree/5.25.5" }, "funding": [ { @@ -145,7 +145,7 @@ "type": "github" } ], - "time": "2025-03-30T17:23:19+00:00" + "time": "2025-06-07T07:44:19+00:00" }, { "name": "captainhook/plugin-composer", @@ -990,16 +990,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.4.1", + "version": "6.4.2", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "35d262c94959571e8736db1e5c9bc36ab94ae900" + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/35d262c94959571e8736db1e5c9bc36ab94ae900", - "reference": "35d262c94959571e8736db1e5c9bc36ab94ae900", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02", + "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02", "shasum": "" }, "require": { @@ -1059,9 +1059,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.1" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2" }, - "time": "2025-04-04T13:08:07+00:00" + "time": "2025-06-03T18:27:04+00:00" }, { "name": "localheinz/diff", @@ -3817,16 +3817,16 @@ }, { "name": "sebastianfeldmann/git", - "version": "3.14.2", + "version": "3.14.3", "source": { "type": "git", "url": "https://github.com/sebastianfeldmann/git.git", - "reference": "bc13c3bab8f67dee26352c94e23a4ee775d9d05b" + "reference": "22584df8df01d95b0700000cfd855779fae7d8ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/bc13c3bab8f67dee26352c94e23a4ee775d9d05b", - "reference": "bc13c3bab8f67dee26352c94e23a4ee775d9d05b", + "url": "https://api.github.com/repos/sebastianfeldmann/git/zipball/22584df8df01d95b0700000cfd855779fae7d8ea", + "reference": "22584df8df01d95b0700000cfd855779fae7d8ea", "shasum": "" }, "require": { @@ -3867,7 +3867,7 @@ ], "support": { "issues": "https://github.com/sebastianfeldmann/git/issues", - "source": "https://github.com/sebastianfeldmann/git/tree/3.14.2" + "source": "https://github.com/sebastianfeldmann/git/tree/3.14.3" }, "funding": [ { @@ -3875,7 +3875,7 @@ "type": "github" } ], - "time": "2025-03-28T19:09:31+00:00" + "time": "2025-06-05T16:05:10+00:00" }, { "name": "seld/jsonlint", @@ -3943,16 +3943,16 @@ }, { "name": "slevomat/coding-standard", - "version": "8.18.1", + "version": "8.19.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "06b18b3f64979ab31d27c37021838439f3ed5919" + "reference": "458d665acd49009efebd7e0cb385d71ae9ac3220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/06b18b3f64979ab31d27c37021838439f3ed5919", - "reference": "06b18b3f64979ab31d27c37021838439f3ed5919", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/458d665acd49009efebd7e0cb385d71ae9ac3220", + "reference": "458d665acd49009efebd7e0cb385d71ae9ac3220", "shasum": "" }, "require": { @@ -3992,7 +3992,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.18.1" + "source": "https://github.com/slevomat/coding-standard/tree/8.19.1" }, "funding": [ { @@ -4004,20 +4004,20 @@ "type": "tidelift" } ], - "time": "2025-05-22T14:32:30+00:00" + "time": "2025-06-09T17:53:57+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.0", + "version": "3.13.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186" + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5b5e3821314f947dd040c70f7992a64eac89025c", + "reference": "5b5e3821314f947dd040c70f7992a64eac89025c", "shasum": "" }, "require": { @@ -4088,7 +4088,7 @@ "type": "thanks_dev" } ], - "time": "2025-05-11T03:36:00+00:00" + "time": "2025-06-17T22:17:01+00:00" }, { "name": "staabm/side-effects-detector", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 4afc863..87969bf 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -11,3 +11,10 @@ parameters: analyse: - ./tests/ExpectedBehaviorTest.php - ./tests/static-analysis/stubs.php + ignoreErrors: + - + identifier: method.resultUnused + path: tests/* + - + identifier: staticMethod.resultUnused + path: tests/* diff --git a/src/BinaryUtils.php b/src/BinaryUtils.php index 1a96985..0157571 100644 --- a/src/BinaryUtils.php +++ b/src/BinaryUtils.php @@ -34,6 +34,8 @@ class BinaryUtils * @param int $clockSeq The 16-bit clock sequence value before the variant is applied * * @return int The 16-bit clock sequence multiplexed with the UUID variant + * + * @pure */ public static function applyVariant(int $clockSeq, Variant $variant = Variant::Rfc9562): int { @@ -54,6 +56,8 @@ class BinaryUtils * @param Version $version The version to apply to the `time_hi` field * * @return int The 16-bit time_hi field of the timestamp multiplexed with the UUID version number + * + * @pure */ public static function applyVersion(int $timeHi, Version $version): int { diff --git a/src/Builder/FallbackBuilder.php b/src/Builder/FallbackBuilder.php index 8c45e42..714be71 100644 --- a/src/Builder/FallbackBuilder.php +++ b/src/Builder/FallbackBuilder.php @@ -40,6 +40,8 @@ class FallbackBuilder implements UuidBuilderInterface * @param non-empty-string $bytes The byte string from which to construct a UUID * * @return UuidInterface an instance of a UUID object + * + * @pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { diff --git a/src/Builder/UuidBuilderInterface.php b/src/Builder/UuidBuilderInterface.php index 7c46ef3..b617e3d 100644 --- a/src/Builder/UuidBuilderInterface.php +++ b/src/Builder/UuidBuilderInterface.php @@ -31,6 +31,8 @@ interface UuidBuilderInterface * @param non-empty-string $bytes The byte string from which to construct a UUID * * @return UuidInterface Implementations may choose to return more specific instances of UUIDs that implement UuidInterface + * + * @pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface; } diff --git a/src/Codec/CodecInterface.php b/src/Codec/CodecInterface.php index 2dd706b..27997a2 100644 --- a/src/Codec/CodecInterface.php +++ b/src/Codec/CodecInterface.php @@ -29,6 +29,8 @@ interface CodecInterface * @param UuidInterface $uuid The UUID for which to create a hexadecimal string representation * * @return non-empty-string Hexadecimal string representation of a UUID + * + * @pure */ public function encode(UuidInterface $uuid): string; @@ -38,6 +40,8 @@ interface CodecInterface * @param UuidInterface $uuid The UUID for which to create a binary string representation * * @return non-empty-string Binary string representation of a UUID + * + * @pure */ public function encodeBinary(UuidInterface $uuid): string; @@ -47,6 +51,8 @@ interface CodecInterface * @param non-empty-string $encodedUuid The hexadecimal string representation to convert into a UuidInterface instance * * @return UuidInterface An instance of a UUID decoded from a hexadecimal string representation + * + * @pure */ public function decode(string $encodedUuid): UuidInterface; @@ -56,6 +62,8 @@ interface CodecInterface * @param non-empty-string $bytes The binary string representation to convert into a UuidInterface instance * * @return UuidInterface An instance of a UUID decoded from a binary string representation + * + * @pure */ public function decodeBytes(string $bytes): UuidInterface; } diff --git a/src/Codec/GuidStringCodec.php b/src/Codec/GuidStringCodec.php index 58900a7..a5ea71f 100644 --- a/src/Codec/GuidStringCodec.php +++ b/src/Codec/GuidStringCodec.php @@ -32,6 +32,7 @@ class GuidStringCodec extends StringCodec { public function encode(UuidInterface $uuid): string { + /** @phpstan-ignore possiblyImpure.methodCall */ $hex = bin2hex($uuid->getFields()->getBytes()); /** @var non-empty-string */ @@ -52,8 +53,10 @@ class GuidStringCodec extends StringCodec public function decode(string $encodedUuid): UuidInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ $bytes = $this->getBytes($encodedUuid); + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */ return $this->getBuilder()->build($this, $this->swapBytes($bytes)); } diff --git a/src/Codec/StringCodec.php b/src/Codec/StringCodec.php index 7a41fb9..d3fe448 100644 --- a/src/Codec/StringCodec.php +++ b/src/Codec/StringCodec.php @@ -46,6 +46,7 @@ class StringCodec implements CodecInterface public function encode(UuidInterface $uuid): string { + /** @phpstan-ignore possiblyImpure.methodCall */ $hex = bin2hex($uuid->getFields()->getBytes()); /** @var non-empty-string */ @@ -64,6 +65,7 @@ class StringCodec implements CodecInterface */ public function encodeBinary(UuidInterface $uuid): string { + /** @phpstan-ignore possiblyImpure.methodCall */ return $uuid->getFields()->getBytes(); } @@ -74,6 +76,7 @@ class StringCodec implements CodecInterface */ public function decode(string $encodedUuid): UuidInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ return $this->builder->build($this, $this->getBytes($encodedUuid)); } diff --git a/src/Converter/Number/GenericNumberConverter.php b/src/Converter/Number/GenericNumberConverter.php index bae27d7..cfb25b9 100644 --- a/src/Converter/Number/GenericNumberConverter.php +++ b/src/Converter/Number/GenericNumberConverter.php @@ -29,13 +29,20 @@ class GenericNumberConverter implements NumberConverterInterface { } + /** + * @pure + */ public function fromHex(string $hex): string { return $this->calculator->fromBase($hex, 16)->toString(); } + /** + * @pure + */ public function toHex(string $number): string { + /** @phpstan-ignore possiblyImpure.new */ return $this->calculator->toBase(new IntegerObject($number), 16); } } diff --git a/src/Converter/NumberConverterInterface.php b/src/Converter/NumberConverterInterface.php index a837207..ba0b827 100644 --- a/src/Converter/NumberConverterInterface.php +++ b/src/Converter/NumberConverterInterface.php @@ -30,6 +30,8 @@ interface NumberConverterInterface * @param non-empty-string $hex The hexadecimal string representation to convert * * @return numeric-string String representation of an integer + * + * @pure */ public function fromHex(string $hex): string; @@ -40,6 +42,8 @@ interface NumberConverterInterface * unsigned integers that are greater than `PHP_INT_MAX`. * * @return non-empty-string Hexadecimal string + * + * @pure */ public function toHex(string $number): string; } diff --git a/src/Converter/Time/GenericTimeConverter.php b/src/Converter/Time/GenericTimeConverter.php index 430109b..4083198 100644 --- a/src/Converter/Time/GenericTimeConverter.php +++ b/src/Converter/Time/GenericTimeConverter.php @@ -54,26 +54,36 @@ class GenericTimeConverter implements TimeConverterInterface public function calculateTime(string $seconds, string $microseconds): Hexadecimal { + /** @phpstan-ignore possiblyImpure.new */ $timestamp = new Time($seconds, $microseconds); // Convert the seconds into a count of 100-nanosecond intervals. $sec = $this->calculator->multiply( $timestamp->getSeconds(), - new IntegerObject(self::SECOND_INTERVALS), + new IntegerObject(self::SECOND_INTERVALS), /** @phpstan-ignore possiblyImpure.new */ ); // Convert the microseconds into a count of 100-nanosecond intervals. $usec = $this->calculator->multiply( $timestamp->getMicroseconds(), - new IntegerObject(self::MICROSECOND_INTERVALS), + new IntegerObject(self::MICROSECOND_INTERVALS), /** @phpstan-ignore possiblyImpure.new */ ); - // Combine the intervals of seconds and microseconds and add the count of 100-nanosecond intervals from the - // Gregorian calendar epoch to the Unix epoch. This gives us the correct count of 100-nanosecond intervals since - // the Gregorian calendar epoch for the given seconds and microseconds. - /** @var IntegerObject $uuidTime */ + /** + * Combine the intervals of seconds and microseconds and add the count of 100-nanosecond intervals from the + * Gregorian calendar epoch to the Unix epoch. This gives us the correct count of 100-nanosecond intervals since + * the Gregorian calendar epoch for the given seconds and microseconds. + * + * @var IntegerObject $uuidTime + * @phpstan-ignore possiblyImpure.new + */ $uuidTime = $this->calculator->add($sec, $usec, new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS)); + /** + * PHPStan considers CalculatorInterface::toHexadecimal, Hexadecimal:toString impure. + * + * @phpstan-ignore possiblyImpure.new + */ return new Hexadecimal(str_pad($this->calculator->toHexadecimal($uuidTime)->toString(), 16, '0', STR_PAD_LEFT)); } @@ -83,7 +93,7 @@ class GenericTimeConverter implements TimeConverterInterface // epoch. This gives us the number of 100-nanosecond intervals from the Unix epoch, which also includes the microtime. $epochNanoseconds = $this->calculator->subtract( $this->calculator->toInteger($uuidTimestamp), - new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS), + new IntegerObject(self::GREGORIAN_TO_UNIX_INTERVALS), /** @phpstan-ignore possiblyImpure.new */ ); // Convert the 100-nanosecond intervals into seconds and microseconds. @@ -91,11 +101,12 @@ class GenericTimeConverter implements TimeConverterInterface RoundingMode::HALF_UP, 6, $epochNanoseconds, - new IntegerObject(self::SECOND_INTERVALS), + new IntegerObject(self::SECOND_INTERVALS), /** @phpstan-ignore possiblyImpure.new */ ); $split = explode('.', (string) $unixTimestamp, 2); + /** @phpstan-ignore possiblyImpure.new */ return new Time($split[0], $split[1] ?? 0); } } diff --git a/src/Converter/Time/PhpTimeConverter.php b/src/Converter/Time/PhpTimeConverter.php index 9f82a02..b427486 100644 --- a/src/Converter/Time/PhpTimeConverter.php +++ b/src/Converter/Time/PhpTimeConverter.php @@ -68,8 +68,8 @@ class PhpTimeConverter implements TimeConverterInterface public function calculateTime(string $seconds, string $microseconds): Hexadecimal { - $seconds = new IntegerObject($seconds); - $microseconds = new IntegerObject($microseconds); + $seconds = new IntegerObject($seconds); /** @phpstan-ignore possiblyImpure.new */ + $microseconds = new IntegerObject($microseconds); /** @phpstan-ignore possiblyImpure.new */ // Calculate the count of 100-nanosecond intervals since the Gregorian calendar epoch // for the given seconds and microseconds. @@ -87,7 +87,10 @@ class PhpTimeConverter implements TimeConverterInterface ); } - return new Hexadecimal(str_pad(dechex($uuidTime), 16, '0', STR_PAD_LEFT)); + /** @phpstan-ignore possiblyImpure.new */ + return new Hexadecimal( + str_pad(dechex($uuidTime), 16, '0', STR_PAD_LEFT) + ); } public function convertTime(Hexadecimal $uuidTimestamp): Time @@ -103,6 +106,7 @@ class PhpTimeConverter implements TimeConverterInterface return $this->fallbackConverter->convertTime($uuidTimestamp); } + /** @phpstan-ignore possiblyImpure.new */ return new Time($splitTime['sec'], $splitTime['usec']); } @@ -110,6 +114,8 @@ class PhpTimeConverter implements TimeConverterInterface * @param float | int $time The time to split into seconds and microseconds * * @return array{sec?: numeric-string, usec?: numeric-string} + * + * @pure */ private function splitTime(float | int $time): array { diff --git a/src/Converter/Time/UnixTimeConverter.php b/src/Converter/Time/UnixTimeConverter.php index 954959d..4bd4125 100644 --- a/src/Converter/Time/UnixTimeConverter.php +++ b/src/Converter/Time/UnixTimeConverter.php @@ -42,23 +42,35 @@ class UnixTimeConverter implements TimeConverterInterface public function calculateTime(string $seconds, string $microseconds): Hexadecimal { + /** @phpstan-ignore possiblyImpure.new */ $timestamp = new Time($seconds, $microseconds); // Convert the seconds into milliseconds. - $sec = $this->calculator->multiply($timestamp->getSeconds(), new IntegerObject(self::MILLISECONDS)); + $sec = $this->calculator->multiply( + $timestamp->getSeconds(), + new IntegerObject(self::MILLISECONDS) /** @phpstan-ignore possiblyImpure.new */ + ); // Convert the microseconds into milliseconds; the scale is zero because we need to discard the fractional part. $usec = $this->calculator->divide( RoundingMode::DOWN, // Always round down to stay in the previous millisecond. 0, $timestamp->getMicroseconds(), - new IntegerObject(self::MILLISECONDS), + new IntegerObject(self::MILLISECONDS), /** @phpstan-ignore possiblyImpure.new */ ); /** @var IntegerObject $unixTime */ $unixTime = $this->calculator->add($sec, $usec); - return new Hexadecimal(str_pad($this->calculator->toHexadecimal($unixTime)->toString(), 12, '0', STR_PAD_LEFT)); + /** @phpstan-ignore possiblyImpure.new */ + return new Hexadecimal( + str_pad( + $this->calculator->toHexadecimal($unixTime)->toString(), + 12, + '0', + STR_PAD_LEFT + ), + ); } public function convertTime(Hexadecimal $uuidTimestamp): Time @@ -69,11 +81,12 @@ class UnixTimeConverter implements TimeConverterInterface RoundingMode::HALF_UP, 6, $milliseconds, - new IntegerObject(self::MILLISECONDS), + new IntegerObject(self::MILLISECONDS), /** @phpstan-ignore possiblyImpure.new */ ); $split = explode('.', (string) $unixTimestamp, 2); + /** @phpstan-ignore possiblyImpure.new */ return new Time($split[0], $split[1] ?? '0'); } } diff --git a/src/Converter/TimeConverterInterface.php b/src/Converter/TimeConverterInterface.php index b0b9199..0e50197 100644 --- a/src/Converter/TimeConverterInterface.php +++ b/src/Converter/TimeConverterInterface.php @@ -34,6 +34,8 @@ interface TimeConverterInterface * @param numeric-string $microseconds A string representation of the micro-seconds associated with the time to calculate * * @return Hexadecimal The full UUID timestamp as a Hexadecimal value + * + * @pure */ public function calculateTime(string $seconds, string $microseconds): Hexadecimal; @@ -44,6 +46,8 @@ interface TimeConverterInterface * of 100-nanosecond intervals since UTC 00:00:00.00, 15 October 1582. * * @return Time An instance of {@see Time} + * + * @pure */ public function convertTime(Hexadecimal $uuidTimestamp): Time; } diff --git a/src/Fields/FieldsInterface.php b/src/Fields/FieldsInterface.php index a28f25b..f8d66ca 100644 --- a/src/Fields/FieldsInterface.php +++ b/src/Fields/FieldsInterface.php @@ -36,6 +36,8 @@ interface FieldsInterface * Returns the bytes that comprise the fields * * @return non-empty-string + * + * @pure */ public function getBytes(): string; } diff --git a/src/Generator/DefaultNameGenerator.php b/src/Generator/DefaultNameGenerator.php index a73b630..cf37b45 100644 --- a/src/Generator/DefaultNameGenerator.php +++ b/src/Generator/DefaultNameGenerator.php @@ -25,6 +25,9 @@ use function hash; */ class DefaultNameGenerator implements NameGeneratorInterface { + /** + * @pure + */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string { try { diff --git a/src/Generator/NameGeneratorInterface.php b/src/Generator/NameGeneratorInterface.php index 18d9aec..403686d 100644 --- a/src/Generator/NameGeneratorInterface.php +++ b/src/Generator/NameGeneratorInterface.php @@ -30,6 +30,8 @@ interface NameGeneratorInterface * @param non-empty-string $hashAlgorithm The hashing algorithm to use * * @return non-empty-string A binary string + * + * @pure */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string; } diff --git a/src/Generator/PeclUuidNameGenerator.php b/src/Generator/PeclUuidNameGenerator.php index ecd02de..daba792 100644 --- a/src/Generator/PeclUuidNameGenerator.php +++ b/src/Generator/PeclUuidNameGenerator.php @@ -29,16 +29,20 @@ use function uuid_parse; */ class PeclUuidNameGenerator implements NameGeneratorInterface { + /** + * @pure + */ public function generate(UuidInterface $ns, string $name, string $hashAlgorithm): string { $uuid = match ($hashAlgorithm) { - 'md5' => uuid_generate_md5($ns->toString(), $name), - 'sha1' => uuid_generate_sha1($ns->toString(), $name), + 'md5' => uuid_generate_md5($ns->toString(), $name), /** @phpstan-ignore possiblyImpure.functionCall */ + 'sha1' => uuid_generate_sha1($ns->toString(), $name), /** @phpstan-ignore possiblyImpure.functionCall */ default => throw new NameException( sprintf('Unable to hash namespace and name with algorithm \'%s\'', $hashAlgorithm), ), }; + /** @phpstan-ignore possiblyImpure.functionCall */ return uuid_parse($uuid) ?: throw new NameException('Unable to generate UUID from ext-uuid'); } } diff --git a/src/Generator/UnixTimeGenerator.php b/src/Generator/UnixTimeGenerator.php index e54db66..a49e4c1 100644 --- a/src/Generator/UnixTimeGenerator.php +++ b/src/Generator/UnixTimeGenerator.php @@ -19,6 +19,7 @@ use DateTimeImmutable; use DateTimeInterface; use Ramsey\Uuid\Type\Hexadecimal; +use function assert; use function hash; use function pack; use function str_pad; @@ -86,7 +87,8 @@ class UnixTimeGenerator implements TimeGeneratorInterface $time = str_pad(BigInteger::of($time)->toBytes(false), 6, "\x00", STR_PAD_LEFT); } - /** @var non-empty-string */ + assert(strlen($time) === 6); + return $time . pack('n*', self::$rand[1], self::$rand[2], self::$rand[3], self::$rand[4], self::$rand[5]); } diff --git a/src/Guid/Fields.php b/src/Guid/Fields.php index 47d3dce..5f3813f 100644 --- a/src/Guid/Fields.php +++ b/src/Guid/Fields.php @@ -177,6 +177,7 @@ final class Fields implements FieldsInterface /** @var int[] $parts */ $parts = unpack('n*', $this->bytes); + /** @phpstan-ignore possiblyImpure.methodCall */ return Version::tryFrom(($parts[4] >> 4) & 0x00f); } diff --git a/src/Guid/GuidBuilder.php b/src/Guid/GuidBuilder.php index 7b813f0..bc194b8 100644 --- a/src/Guid/GuidBuilder.php +++ b/src/Guid/GuidBuilder.php @@ -49,12 +49,16 @@ class GuidBuilder implements UuidBuilderInterface * @param non-empty-string $bytes The byte string from which to construct a UUID * * @return Guid The GuidBuilder returns an instance of Ramsey\Uuid\Guid\Guid + * + * @pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { try { + /** @phpstan-ignore possiblyImpure.new */ return new Guid($this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter); } catch (Throwable $e) { + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */ throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } @@ -63,9 +67,12 @@ class GuidBuilder implements UuidBuilderInterface * Proxy method to allow injecting a mock for testing * * @param non-empty-string $bytes + * + * @pure */ protected function buildFields(string $bytes): Fields { + /** @phpstan-ignore possiblyImpure.new */ return new Fields($bytes); } } diff --git a/src/Lazy/LazyUuidFromString.php b/src/Lazy/LazyUuidFromString.php index 44ad835..d1bc8e1 100644 --- a/src/Lazy/LazyUuidFromString.php +++ b/src/Lazy/LazyUuidFromString.php @@ -132,7 +132,10 @@ final class LazyUuidFromString implements TimeBasedUuidInterface public function getBytes(): string { - /** @var non-empty-string */ + /** + * @var non-empty-string + * @phpstan-ignore possiblyImpure.functionCall, possiblyImpure.functionCall + */ return (string) hex2bin(str_replace('-', '', $this->uuid)); } diff --git a/src/Math/BrickMathCalculator.php b/src/Math/BrickMathCalculator.php index 697871b..8ea3e1b 100644 --- a/src/Math/BrickMathCalculator.php +++ b/src/Math/BrickMathCalculator.php @@ -49,9 +49,10 @@ final class BrickMathCalculator implements CalculatorInterface $sum = BigInteger::of($augend->toString()); foreach ($addends as $addend) { - $sum = $sum->plus($addend->toString()); + $sum = $sum->plus($addend->toString()); /** @phpstan-ignore possiblyImpure.methodCall */ } + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.new */ return new IntegerObject((string) $sum); } @@ -60,9 +61,10 @@ final class BrickMathCalculator implements CalculatorInterface $difference = BigInteger::of($minuend->toString()); foreach ($subtrahends as $subtrahend) { - $difference = $difference->minus($subtrahend->toString()); + $difference = $difference->minus($subtrahend->toString()); /** @phpstan-ignore possiblyImpure.methodCall */ } + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.new */ return new IntegerObject((string) $difference); } @@ -71,9 +73,11 @@ final class BrickMathCalculator implements CalculatorInterface $product = BigInteger::of($multiplicand->toString()); foreach ($multipliers as $multiplier) { + /** @phpstan-ignore possiblyImpure.methodCall */ $product = $product->multipliedBy($multiplier->toString()); } + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.new */ return new IntegerObject((string) $product); } @@ -83,24 +87,29 @@ final class BrickMathCalculator implements CalculatorInterface NumberInterface $dividend, NumberInterface ...$divisors, ): NumberInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ $brickRounding = $this->getBrickRoundingMode($roundingMode); $quotient = BigDecimal::of($dividend->toString()); foreach ($divisors as $divisor) { + /** @phpstan-ignore possiblyImpure.methodCall */ $quotient = $quotient->dividedBy($divisor->toString(), $scale, $brickRounding); } if ($scale === 0) { + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall, possiblyImpure.new */ return new IntegerObject((string) $quotient->toBigInteger()); } + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.new */ return new Decimal((string) $quotient); } public function fromBase(string $value, int $base): IntegerObject { try { + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.new */ return new IntegerObject((string) BigInteger::fromBase($value, $base)); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( @@ -114,7 +123,10 @@ final class BrickMathCalculator implements CalculatorInterface public function toBase(IntegerObject $value, int $base): string { try { - /** @var non-empty-string */ + /** + * @var non-empty-string + * @phpstan-ignore possiblyImpure.methodCall + */ return BigInteger::of($value->toString())->toBase($base); } catch (MathException | \InvalidArgumentException $exception) { throw new InvalidArgumentException( @@ -127,6 +139,7 @@ final class BrickMathCalculator implements CalculatorInterface public function toHexadecimal(IntegerObject $value): Hexadecimal { + /** @phpstan-ignore possiblyImpure.new */ return new Hexadecimal($this->toBase($value, 16)); } diff --git a/src/Math/CalculatorInterface.php b/src/Math/CalculatorInterface.php index 7e04b77..f234b14 100644 --- a/src/Math/CalculatorInterface.php +++ b/src/Math/CalculatorInterface.php @@ -32,6 +32,8 @@ interface CalculatorInterface * @param NumberInterface ...$addends The additional integers to add to the augend * * @return NumberInterface The sum of all the parameters + * + * @pure */ public function add(NumberInterface $augend, NumberInterface ...$addends): NumberInterface; @@ -42,6 +44,8 @@ interface CalculatorInterface * @param NumberInterface ...$subtrahends The integers to subtract from the minuend * * @return NumberInterface The difference after subtracting all parameters + * + * @pure */ public function subtract(NumberInterface $minuend, NumberInterface ...$subtrahends): NumberInterface; @@ -52,6 +56,8 @@ interface CalculatorInterface * @param NumberInterface ...$multipliers The factors by which to multiply the multiplicand * * @return NumberInterface The product of multiplying all the provided parameters + * + * @pure */ public function multiply(NumberInterface $multiplicand, NumberInterface ...$multipliers): NumberInterface; @@ -65,6 +71,8 @@ interface CalculatorInterface * operations should take place (left-to-right) * * @return NumberInterface The quotient of dividing the provided parameters left-to-right + * + * @pure */ public function divide( int $roundingMode, @@ -80,6 +88,8 @@ interface CalculatorInterface * @param int $base The base to convert from (i.e., 2, 16, 32, etc.) * * @return IntegerObject The base-10 integer value of the converted value + * + * @pure */ public function fromBase(string $value, int $base): IntegerObject; @@ -90,16 +100,22 @@ interface CalculatorInterface * @param int $base The base to convert to (i.e., 2, 16, 32, etc.) * * @return non-empty-string The value represented in the specified base + * + * @pure */ public function toBase(IntegerObject $value, int $base): string; /** * Converts an Integer instance to a Hexadecimal instance + * + * @pure */ public function toHexadecimal(IntegerObject $value): Hexadecimal; /** * Converts a Hexadecimal instance to an Integer instance + * + * @pure */ public function toInteger(Hexadecimal $value): IntegerObject; } diff --git a/src/Nonstandard/Uuid.php b/src/Nonstandard/Uuid.php index 32a2496..7f15e6e 100644 --- a/src/Nonstandard/Uuid.php +++ b/src/Nonstandard/Uuid.php @@ -23,6 +23,7 @@ use Ramsey\Uuid\Uuid as BaseUuid; * Nonstandard\Uuid is a UUID that doesn't conform to RFC 9562 (formerly RFC 4122) * * @immutable + * @pure */ final class Uuid extends BaseUuid { diff --git a/src/Nonstandard/UuidBuilder.php b/src/Nonstandard/UuidBuilder.php index 4286c25..7ad00c9 100644 --- a/src/Nonstandard/UuidBuilder.php +++ b/src/Nonstandard/UuidBuilder.php @@ -47,12 +47,16 @@ class UuidBuilder implements UuidBuilderInterface * @param non-empty-string $bytes The byte string from which to construct a UUID * * @return Uuid The Nonstandard\UuidBuilder returns an instance of Nonstandard\Uuid + * + * @pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { try { + /** @phpstan-ignore possiblyImpure.new */ return new Uuid($this->buildFields($bytes), $this->numberConverter, $codec, $this->timeConverter); } catch (Throwable $e) { + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */ throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } @@ -61,9 +65,12 @@ class UuidBuilder implements UuidBuilderInterface * Proxy method to allow injecting a mock for testing * * @param non-empty-string $bytes + * + * @pure */ protected function buildFields(string $bytes): Fields { + /** @phpstan-ignore possiblyImpure.new */ return new Fields($bytes); } } diff --git a/src/Rfc4122/Fields.php b/src/Rfc4122/Fields.php index a99149b..6181cda 100644 --- a/src/Rfc4122/Fields.php +++ b/src/Rfc4122/Fields.php @@ -73,6 +73,9 @@ final class Fields implements FieldsInterface } } + /** + * @pure + */ public function getBytes(): string { return $this->bytes; @@ -191,6 +194,7 @@ final class Fields implements FieldsInterface /** @var int[] $parts */ $parts = unpack('n*', $this->bytes); + /** @phpstan-ignore possiblyImpure.methodCall */ return Version::tryFrom($parts[4] >> 12); } diff --git a/src/Rfc4122/FieldsInterface.php b/src/Rfc4122/FieldsInterface.php index 311323e..9fc139e 100644 --- a/src/Rfc4122/FieldsInterface.php +++ b/src/Rfc4122/FieldsInterface.php @@ -115,6 +115,8 @@ interface FieldsInterface extends BaseFieldsInterface * meaningful for this variant. * * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field + * + * @pure */ public function getVersion(): ?Version; @@ -122,6 +124,8 @@ interface FieldsInterface extends BaseFieldsInterface * Returns true if these fields represent a nil UUID * * The nil UUID is a special form of UUID that is specified to have all 128 bits set to zero. + * + * @pure */ public function isNil(): bool; } diff --git a/src/Rfc4122/MaxTrait.php b/src/Rfc4122/MaxTrait.php index 988b524..8646e6a 100644 --- a/src/Rfc4122/MaxTrait.php +++ b/src/Rfc4122/MaxTrait.php @@ -23,11 +23,15 @@ trait MaxTrait { /** * Returns the bytes that comprise the fields + * + * @pure */ abstract public function getBytes(): string; /** * Returns true if the byte string represents a max UUID + * + * @pure */ public function isMax(): bool { diff --git a/src/Rfc4122/NilTrait.php b/src/Rfc4122/NilTrait.php index 6461e96..19d1377 100644 --- a/src/Rfc4122/NilTrait.php +++ b/src/Rfc4122/NilTrait.php @@ -23,6 +23,8 @@ trait NilTrait { /** * Returns the bytes that comprise the fields + * + * @pure */ abstract public function getBytes(): string; diff --git a/src/Rfc4122/UuidBuilder.php b/src/Rfc4122/UuidBuilder.php index d58e350..5fe37b6 100644 --- a/src/Rfc4122/UuidBuilder.php +++ b/src/Rfc4122/UuidBuilder.php @@ -56,6 +56,8 @@ class UuidBuilder implements UuidBuilderInterface * @param non-empty-string $bytes The byte string from which to construct a UUID * * @return Rfc4122UuidInterface UuidBuilder returns instances of Rfc4122UuidInterface + * + * @pure */ public function build(CodecInterface $codec, string $bytes): UuidInterface { @@ -64,27 +66,38 @@ class UuidBuilder implements UuidBuilderInterface $fields = $this->buildFields($bytes); if ($fields->isNil()) { + /** @phpstan-ignore possiblyImpure.new */ return new NilUuid($fields, $this->numberConverter, $codec, $this->timeConverter); } if ($fields->isMax()) { + /** @phpstan-ignore possiblyImpure.new */ return new MaxUuid($fields, $this->numberConverter, $codec, $this->timeConverter); } return match ($fields->getVersion()) { + /** @phpstan-ignore possiblyImpure.new */ Version::Time => new UuidV1($fields, $this->numberConverter, $codec, $this->timeConverter), + /** @phpstan-ignore possiblyImpure.new */ Version::DceSecurity => new UuidV2($fields, $this->numberConverter, $codec, $this->timeConverter), + /** @phpstan-ignore possiblyImpure.new */ Version::HashMd5 => new UuidV3($fields, $this->numberConverter, $codec, $this->timeConverter), + /** @phpstan-ignore possiblyImpure.new */ Version::Random => new UuidV4($fields, $this->numberConverter, $codec, $this->timeConverter), + /** @phpstan-ignore possiblyImpure.new */ Version::HashSha1 => new UuidV5($fields, $this->numberConverter, $codec, $this->timeConverter), + /** @phpstan-ignore possiblyImpure.new */ Version::ReorderedTime => new UuidV6($fields, $this->numberConverter, $codec, $this->timeConverter), + /** @phpstan-ignore possiblyImpure.new */ Version::UnixTime => new UuidV7($fields, $this->numberConverter, $codec, $this->unixTimeConverter), + /** @phpstan-ignore possiblyImpure.new */ Version::Custom => new UuidV8($fields, $this->numberConverter, $codec, $this->timeConverter), default => throw new UnsupportedOperationException( 'The UUID version in the given fields is not supported by this UUID builder', ), }; } catch (Throwable $e) { + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */ throw new UnableToBuildUuidException($e->getMessage(), (int) $e->getCode(), $e); } } @@ -93,9 +106,12 @@ class UuidBuilder implements UuidBuilderInterface * Proxy method to allow injecting a mock for testing * * @param non-empty-string $bytes + * + * @pure */ protected function buildFields(string $bytes): FieldsInterface { + /** @phpstan-ignore possiblyImpure.new */ return new Fields($bytes); } } diff --git a/src/Rfc4122/Validator.php b/src/Rfc4122/Validator.php index d2b7645..1736286 100644 --- a/src/Rfc4122/Validator.php +++ b/src/Rfc4122/Validator.php @@ -40,8 +40,10 @@ final class Validator implements ValidatorInterface public function validate(string $uuid): bool { + /** @phpstan-ignore possiblyImpure.functionCall */ $uuid = strtolower(str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid)); + /** @phpstan-ignore possiblyImpure.functionCall */ return $uuid === Uuid::NIL || $uuid === Uuid::MAX || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid); } } diff --git a/src/Rfc4122/VariantTrait.php b/src/Rfc4122/VariantTrait.php index 50f1324..a9a40e5 100644 --- a/src/Rfc4122/VariantTrait.php +++ b/src/Rfc4122/VariantTrait.php @@ -35,6 +35,8 @@ trait VariantTrait { /** * Returns the bytes that comprise the fields + * + * @pure */ abstract public function getBytes(): string; diff --git a/src/Rfc4122/VersionTrait.php b/src/Rfc4122/VersionTrait.php index 0633e58..a082819 100644 --- a/src/Rfc4122/VersionTrait.php +++ b/src/Rfc4122/VersionTrait.php @@ -28,6 +28,8 @@ trait VersionTrait * meaningful for this variant. * * @link https://www.rfc-editor.org/rfc/rfc9562#section-4.2 RFC 9562, 4.2. Version Field + * + * @pure */ abstract public function getVersion(): ?Version; diff --git a/src/Type/Hexadecimal.php b/src/Type/Hexadecimal.php index 2268d47..75b91f1 100644 --- a/src/Type/Hexadecimal.php +++ b/src/Type/Hexadecimal.php @@ -48,6 +48,8 @@ final class Hexadecimal implements TypeInterface /** * @return non-empty-string + * + * @pure */ public function toString(): string { diff --git a/src/Type/Integer.php b/src/Type/Integer.php index b0f7896..dd8bf67 100644 --- a/src/Type/Integer.php +++ b/src/Type/Integer.php @@ -59,6 +59,8 @@ final class Integer implements NumberInterface /** * @return numeric-string + * + * @pure */ public function toString(): string { diff --git a/src/Type/Time.php b/src/Type/Time.php index 89625c6..b2061da 100644 --- a/src/Type/Time.php +++ b/src/Type/Time.php @@ -40,11 +40,17 @@ final class Time implements TypeInterface $this->microseconds = $microseconds instanceof IntegerObject ? $microseconds : new IntegerObject($microseconds); } + /** + * @pure + */ public function getSeconds(): IntegerObject { return $this->seconds; } + /** + * @pure + */ public function getMicroseconds(): IntegerObject { return $this->microseconds; diff --git a/src/Type/TypeInterface.php b/src/Type/TypeInterface.php index c350a02..b60169c 100644 --- a/src/Type/TypeInterface.php +++ b/src/Type/TypeInterface.php @@ -35,11 +35,15 @@ interface TypeInterface extends JsonSerializable /** * @return non-empty-string + * + * @pure */ public function toString(): string; /** * @return non-empty-string + * + * @pure */ public function __toString(): string; } diff --git a/src/Uuid.php b/src/Uuid.php index f3fcc31..ce71fe7 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -324,9 +324,12 @@ class Uuid implements Rfc4122UuidInterface * @return UuidInterface A UuidInterface instance created from a binary string representation * * @throws InvalidArgumentException + * + * @pure */ public static function fromBytes(string $bytes): UuidInterface { + /** @phpstan-ignore impure.staticPropertyAccess */ if (!self::$factoryReplaced && strlen($bytes) === 16) { $base16Uuid = bin2hex($bytes); @@ -344,6 +347,7 @@ class Uuid implements Rfc4122UuidInterface ); } + /** @phpstan-ignore possiblyImpure.methodCall */ return self::getFactory()->fromBytes($bytes); } @@ -355,16 +359,22 @@ class Uuid implements Rfc4122UuidInterface * @return UuidInterface A UuidInterface instance created from a hexadecimal string representation * * @throws InvalidArgumentException + * + * @pure */ public static function fromString(string $uuid): UuidInterface { $uuid = strtolower($uuid); + /** @phpstan-ignore impure.staticPropertyAccess, possiblyImpure.functionCall */ if (!self::$factoryReplaced && preg_match(LazyUuidFromString::VALID_REGEX, $uuid) === 1) { + /** @phpstan-ignore possiblyImpure.functionCall */ assert($uuid !== ''); + /** @phpstan-ignore possiblyImpure.new */ return new LazyUuidFromString($uuid); } + /** @phpstan-ignore possiblyImpure.methodCall */ return self::getFactory()->fromString($uuid); } @@ -394,13 +404,18 @@ class Uuid implements Rfc4122UuidInterface * @return UuidInterface A UuidInterface instance created from the Hexadecimal object representing a hexadecimal number * * @throws InvalidArgumentException + * + * @pure */ public static function fromHexadecimal(Hexadecimal $hex): UuidInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ $factory = self::getFactory(); if (method_exists($factory, 'fromHexadecimal')) { + /** @phpstan-ignore possiblyImpure.methodCall */ $uuid = $factory->fromHexadecimal($hex); + /** @phpstan-ignore possiblyImpure.functionCall */ assert($uuid instanceof UuidInterface); return $uuid; @@ -417,9 +432,12 @@ class Uuid implements Rfc4122UuidInterface * @return UuidInterface A UuidInterface instance created from the string representation of a 128-bit integer * * @throws InvalidArgumentException + * + * @pure */ public static function fromInteger(string $integer): UuidInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ return self::getFactory()->fromInteger($integer); } @@ -431,9 +449,12 @@ class Uuid implements Rfc4122UuidInterface * @return bool True if the string is a valid UUID, false otherwise * * @phpstan-assert-if-true =non-empty-string $uuid + * + * @pure */ public static function isValid(string $uuid): bool { + /** @phpstan-ignore possiblyImpure.methodCall, possiblyImpure.methodCall */ return self::getFactory()->getValidator()->validate($uuid); } @@ -482,9 +503,12 @@ class Uuid implements Rfc4122UuidInterface * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a version 3 UUID + * + * @pure */ public static function uuid3(UuidInterface | string $ns, string $name): UuidInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ return self::getFactory()->uuid3($ns, $name); } @@ -505,9 +529,12 @@ class Uuid implements Rfc4122UuidInterface * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a version 5 UUID + * + * @pure */ public static function uuid5(UuidInterface | string $ns, string $name): UuidInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ return self::getFactory()->uuid5($ns, $name); } @@ -558,13 +585,19 @@ class Uuid implements Rfc4122UuidInterface * and 65 will be replaced with the UUID variant. You MUST NOT rely on these bits for your application needs. * * @return UuidInterface A UuidInterface instance that represents a version 8 UUID + * + * @pure */ public static function uuid8(string $bytes): UuidInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ $factory = self::getFactory(); if (method_exists($factory, 'uuid8')) { - /** @var UuidInterface */ + /** + * @var UuidInterface + * @phpstan-ignore possiblyImpure.methodCall + */ return $factory->uuid8($bytes); } diff --git a/src/UuidFactory.php b/src/UuidFactory.php index 34a2917..1e1f3ea 100644 --- a/src/UuidFactory.php +++ b/src/UuidFactory.php @@ -250,11 +250,17 @@ class UuidFactory implements UuidFactoryInterface $this->validator = $validator; } + /** + * @pure + */ public function fromBytes(string $bytes): UuidInterface { return $this->codec->decodeBytes($bytes); } + /** + * @pure + */ public function fromString(string $uuid): UuidInterface { $uuid = strtolower($uuid); @@ -262,6 +268,9 @@ class UuidFactory implements UuidFactoryInterface return $this->codec->decode($uuid); } + /** + * @pure + */ public function fromInteger(string $integer): UuidInterface { $hex = $this->numberConverter->toHex($integer); @@ -282,6 +291,9 @@ class UuidFactory implements UuidFactoryInterface return $this->uuidFromBytesAndVersion($bytes, Version::Time); } + /** + * @pure + */ public function fromHexadecimal(Hexadecimal $hex): UuidInterface { return $this->codec->decode($hex->__toString()); @@ -305,6 +317,9 @@ class UuidFactory implements UuidFactoryInterface return $this->uuidFromBytesAndVersion($bytes, Version::DceSecurity); } + /** + * @pure + */ public function uuid3(UuidInterface | string $ns, string $name): UuidInterface { return $this->uuidFromNsAndName($ns, $name, Version::HashMd5, 'md5'); @@ -317,6 +332,9 @@ class UuidFactory implements UuidFactoryInterface return $this->uuidFromBytesAndVersion($bytes, Version::Random); } + /** + * @pure + */ public function uuid5(UuidInterface | string $ns, string $name): UuidInterface { return $this->uuidFromNsAndName($ns, $name, Version::HashSha1, 'sha1'); @@ -368,9 +386,12 @@ class UuidFactory implements UuidFactoryInterface * needs. * * @return UuidInterface A UuidInterface instance that represents a version 8 UUID + * + * @pure */ public function uuid8(string $bytes): UuidInterface { + /** @phpstan-ignore possiblyImpure.methodCall */ return $this->uuidFromBytesAndVersion($bytes, Version::Custom); } @@ -382,6 +403,8 @@ class UuidFactory implements UuidFactoryInterface * @param non-empty-string $bytes The byte string from which to construct a UUID * * @return UuidInterface An instance of UuidInterface, created from the provided bytes + * + * @pure */ public function uuid(string $bytes): UuidInterface { @@ -397,6 +420,8 @@ class UuidFactory implements UuidFactoryInterface * @param non-empty-string $hashAlgorithm The hashing algorithm to use when hashing together the namespace and name * * @return UuidInterface An instance of UuidInterface, created by hashing together the provided namespace and name + * + * @pure */ private function uuidFromNsAndName( UuidInterface | string $ns, @@ -413,6 +438,7 @@ class UuidFactory implements UuidFactoryInterface /** @var non-empty-string $bytes */ $bytes = substr($bytes, 0, 16); + /** @phpstan-ignore possiblyImpure.methodCall */ return $this->uuidFromBytesAndVersion($bytes, $version); } diff --git a/src/UuidFactoryInterface.php b/src/UuidFactoryInterface.php index c15d8ac..18c364c 100644 --- a/src/UuidFactoryInterface.php +++ b/src/UuidFactoryInterface.php @@ -32,6 +32,8 @@ interface UuidFactoryInterface * @param non-empty-string $bytes A binary string * * @return UuidInterface A UuidInterface instance created from a binary string representation + * + * @pure */ public function fromBytes(string $bytes): UuidInterface; @@ -57,6 +59,8 @@ interface UuidFactoryInterface * @param numeric-string $integer String representation of 128-bit integer * * @return UuidInterface A UuidInterface instance created from the string representation of a 128-bit integer + * + * @pure */ public function fromInteger(string $integer): UuidInterface; @@ -66,6 +70,8 @@ interface UuidFactoryInterface * @param non-empty-string $uuid A hexadecimal string * * @return UuidInterface A UuidInterface instance created from a hexadecimal string representation + * + * @pure */ public function fromString(string $uuid): UuidInterface; @@ -114,6 +120,8 @@ interface UuidFactoryInterface * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a version 3 UUID + * + * @pure */ public function uuid3(UuidInterface | string $ns, string $name): UuidInterface; @@ -131,6 +139,8 @@ interface UuidFactoryInterface * @param string $name The name to use for creating a UUID * * @return UuidInterface A UuidInterface instance that represents a version 5 UUID + * + * @pure */ public function uuid5(UuidInterface | string $ns, string $name): UuidInterface; diff --git a/src/UuidInterface.php b/src/UuidInterface.php index 5efaacd..e386f9e 100644 --- a/src/UuidInterface.php +++ b/src/UuidInterface.php @@ -36,6 +36,8 @@ interface UuidInterface extends JsonSerializable, Stringable * Casts the UUID to the string standard representation * * @return non-empty-string + * + * @pure */ public function __toString(): string; @@ -72,6 +74,8 @@ interface UuidInterface extends JsonSerializable, Stringable * Returns the binary string representation of the UUID * * @return non-empty-string + * + * @pure */ public function getBytes(): string; @@ -104,6 +108,8 @@ interface UuidInterface extends JsonSerializable, Stringable * Returns the string standard representation of the UUID * * @return non-empty-string + * + * @pure */ public function toString(): string; } diff --git a/src/Validator/GenericValidator.php b/src/Validator/GenericValidator.php index 36fb56e..6d60647 100644 --- a/src/Validator/GenericValidator.php +++ b/src/Validator/GenericValidator.php @@ -41,8 +41,10 @@ final class GenericValidator implements ValidatorInterface public function validate(string $uuid): bool { + /** @phpstan-ignore possiblyImpure.functionCall */ $uuid = str_replace(['urn:', 'uuid:', 'URN:', 'UUID:', '{', '}'], '', $uuid); + /** @phpstan-ignore possiblyImpure.functionCall */ return $uuid === Uuid::NIL || preg_match('/' . self::VALID_PATTERN . '/Dms', $uuid); } } diff --git a/src/Validator/ValidatorInterface.php b/src/Validator/ValidatorInterface.php index f125513..95dc8eb 100644 --- a/src/Validator/ValidatorInterface.php +++ b/src/Validator/ValidatorInterface.php @@ -34,6 +34,8 @@ interface ValidatorInterface * @param string $uuid The string to validate as a UUID * * @return bool True if the string is a valid UUID, false otherwise + * + * @pure */ public function validate(string $uuid): bool; } diff --git a/src/functions.php b/src/functions.php index 05962bd..1c41286 100644 --- a/src/functions.php +++ b/src/functions.php @@ -63,6 +63,8 @@ function v2( * @param non-empty-string $name The name to use for creating a UUID * * @return non-empty-string Version 3 UUID as a string + * + * @pure */ function v3(UuidInterface | string $ns, string $name): string { @@ -86,6 +88,8 @@ function v4(): string * @param non-empty-string $name The name to use for creating a UUID * * @return non-empty-string Version 5 UUID as a string + * + * @pure */ function v5(UuidInterface | string $ns, string $name): string { @@ -130,6 +134,8 @@ function v7(?DateTimeInterface $dateTime = null): string * 65 will be replaced with the UUID variant. You MUST NOT rely on these bits for your application needs. * * @return non-empty-string Version 8 UUID as a string + * + * @pure */ function v8(string $bytes): string { diff --git a/tests/Generator/UnixTimeGeneratorTest.php b/tests/Generator/UnixTimeGeneratorTest.php index 00a24a0..9a3b672 100644 --- a/tests/Generator/UnixTimeGeneratorTest.php +++ b/tests/Generator/UnixTimeGeneratorTest.php @@ -16,6 +16,8 @@ use Ramsey\Uuid\Test\TestCase; class UnixTimeGeneratorTest extends TestCase { + private const ITERATIONS = 2000; + #[RunInSeparateProcess] #[PreserveGlobalState(false)] public function testGenerate(): void @@ -33,7 +35,11 @@ class UnixTimeGeneratorTest extends TestCase $bytes = $unixTimeGenerator->generate(null, null, $dateTime); - $this->assertSame($expectedBytes, $bytes); + $this->assertSame( + $expectedBytes, + $bytes, + 'Failed asserting that "' . bin2hex($bytes) . '" is equal to "' . bin2hex($expectedBytes) . '"', + ); } #[RunInSeparateProcess] @@ -44,9 +50,13 @@ class UnixTimeGeneratorTest extends TestCase $unixTimeGenerator = new UnixTimeGenerator($randomGenerator); $previous = ''; - for ($i = 0; $i < 25; $i++) { + for ($i = 0; $i < self::ITERATIONS; $i++) { $bytes = $unixTimeGenerator->generate(); - $this->assertTrue($bytes > $previous); + $this->assertTrue( + $bytes > $previous, + 'Failed on iteration ' . $i . ' when evaluating ' . bin2hex($bytes) . ' > ' . bin2hex($previous), + ); + $previous = $bytes; } } @@ -59,9 +69,13 @@ class UnixTimeGeneratorTest extends TestCase $unixTimeGenerator = new UnixTimeGenerator($randomGenerator); $previous = ''; - for ($i = 0; $i < 25; $i++) { + for ($i = 0; $i < self::ITERATIONS; $i++) { $bytes = $unixTimeGenerator->generate(null, null, $dateTime); - $this->assertTrue($bytes > $previous); + $this->assertTrue( + $bytes > $previous, + 'Failed on iteration ' . $i . ' when evaluating ' . bin2hex($bytes) . ' > ' . bin2hex($previous), + ); + $previous = $bytes; } } @@ -73,9 +87,13 @@ class UnixTimeGeneratorTest extends TestCase $unixTimeGenerator = new UnixTimeGenerator($randomGenerator, 4); $previous = ''; - for ($i = 0; $i < 25; $i++) { + for ($i = 0; $i < self::ITERATIONS; $i++) { $bytes = $unixTimeGenerator->generate(); - $this->assertTrue($bytes > $previous); + $this->assertTrue( + $bytes > $previous, + 'Failed on iteration ' . $i . ' when evaluating ' . bin2hex($bytes) . ' > ' . bin2hex($previous), + ); + $previous = $bytes; } } @@ -88,9 +106,13 @@ class UnixTimeGeneratorTest extends TestCase $unixTimeGenerator = new UnixTimeGenerator($randomGenerator, 4); $previous = ''; - for ($i = 0; $i < 25; $i++) { + for ($i = 0; $i < self::ITERATIONS; $i++) { $bytes = $unixTimeGenerator->generate(null, null, $dateTime); - $this->assertTrue($bytes > $previous); + $this->assertTrue( + $bytes > $previous, + 'Failed on iteration ' . $i . ' when evaluating ' . bin2hex($bytes) . ' > ' . bin2hex($previous), + ); + $previous = $bytes; } } @@ -103,22 +125,26 @@ class UnixTimeGeneratorTest extends TestCase $randomGenerator->expects()->generate(16)->andReturns( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", ); - $randomGenerator->expects()->generate(10)->times(24)->andReturns( + $randomGenerator->allows()->generate(10)->andReturns( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", ); $unixTimeGenerator = new UnixTimeGenerator($randomGenerator); $previous = ''; - for ($i = 0; $i < 25; $i++) { + for ($i = 0; $i < self::ITERATIONS; $i++) { $bytes = $unixTimeGenerator->generate(); - $this->assertTrue($bytes > $previous); + $this->assertTrue( + $bytes > $previous, + 'Failed on iteration ' . $i . ' when evaluating ' . bin2hex($bytes) . ' > ' . bin2hex($previous), + ); + $previous = $bytes; } } #[RunInSeparateProcess] #[PreserveGlobalState(false)] - public function testGenerateProducesMonotonicResultsStartingWithAllBitsSetWithSameDate(): void + public function testGenerateRollsOverWithAllBitsSetWithSameDate(): void { $dateTime = new DateTimeImmutable('now'); @@ -127,17 +153,19 @@ class UnixTimeGeneratorTest extends TestCase $randomGenerator->expects()->generate(16)->andReturns( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", ); - $randomGenerator->expects()->generate(10)->times(24)->andReturns( + $randomGenerator->allows()->generate(10)->andReturns( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", ); $unixTimeGenerator = new UnixTimeGenerator($randomGenerator); - $previous = ''; - for ($i = 0; $i < 25; $i++) { - $bytes = $unixTimeGenerator->generate(null, null, $dateTime); - $this->assertTrue($bytes > $previous); - } + // We can only call this twice before the overflow kicks in, "randomizing" all the bits back to 1's, according to + // our mocked random generator. As a result, we can't run this in a loop like with the other monotonicity tests + // in this class; it starts failing at the third loop. This is okay, since our goal is to test the overflow. + $first = $unixTimeGenerator->generate(null, null, $dateTime); + $second = $unixTimeGenerator->generate(null, null, $dateTime); + + $this->assertTrue($second > $first); } #[RunInSeparateProcess] @@ -149,22 +177,26 @@ class UnixTimeGeneratorTest extends TestCase $randomGenerator->expects()->generate(16)->andReturns( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", ); - $randomGenerator->expects()->generate(10)->times(24)->andReturns( + $randomGenerator->allows()->generate(10)->andReturns( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", ); $unixTimeGenerator = new UnixTimeGenerator($randomGenerator, 4); $previous = ''; - for ($i = 0; $i < 25; $i++) { + for ($i = 0; $i < self::ITERATIONS; $i++) { $bytes = $unixTimeGenerator->generate(); - $this->assertTrue($bytes > $previous); + $this->assertTrue( + $bytes > $previous, + 'Failed on iteration ' . $i . ' when evaluating ' . bin2hex($bytes) . ' > ' . bin2hex($previous), + ); + $previous = $bytes; } } #[RunInSeparateProcess] #[PreserveGlobalState(false)] - public function testGenerateProducesMonotonicResultsStartingWithAllBitsSetWithSameDateFor32BitPath(): void + public function testGenerateRollsOverWithAllBitsSetWithSameDateFor32BitPath(): void { $dateTime = new DateTimeImmutable('now'); @@ -173,16 +205,18 @@ class UnixTimeGeneratorTest extends TestCase $randomGenerator->expects()->generate(16)->andReturns( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", ); - $randomGenerator->expects()->generate(10)->times(24)->andReturns( + $randomGenerator->allows()->generate(10)->andReturns( "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", ); $unixTimeGenerator = new UnixTimeGenerator($randomGenerator, 4); - $previous = ''; - for ($i = 0; $i < 25; $i++) { - $bytes = $unixTimeGenerator->generate(null, null, $dateTime); - $this->assertTrue($bytes > $previous); - } + // We can only call this twice before the overflow kicks in, "randomizing" all the bits back to 1's, according to + // our mocked random generator. As a result, we can't run this in a loop like with the other monotonicity tests + // in this class; it starts failing at the third loop. This is okay, since our goal is to test the overflow. + $first = $unixTimeGenerator->generate(null, null, $dateTime); + $second = $unixTimeGenerator->generate(null, null, $dateTime); + + $this->assertTrue($second > $first); } } diff --git a/tests/UuidTest.php b/tests/UuidTest.php index 7b96532..ba60f32 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -786,6 +786,7 @@ class UuidTest extends TestCase $this->expectException(UnsupportedOperationException::class); $this->expectExceptionMessage('The provided factory does not support the uuid8() method'); + /** @phpstan-ignore staticMethod.resultUnused */ Uuid::uuid8("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff"); } @@ -1600,7 +1601,7 @@ class UuidTest extends TestCase $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid UUID string:'); - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore argument.type */ Uuid::uuid3('', ''); } @@ -1623,7 +1624,7 @@ class UuidTest extends TestCase $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid UUID string:'); - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore argument.type */ Uuid::uuid5('', ''); }