diff --git a/src/Lazy/LazyUuidFromString.php b/src/Lazy/LazyUuidFromString.php index bad46fb..519d4db 100644 --- a/src/Lazy/LazyUuidFromString.php +++ b/src/Lazy/LazyUuidFromString.php @@ -26,8 +26,10 @@ use Ramsey\Uuid\UuidFactory; use Ramsey\Uuid\UuidInterface; use function assert; +use function bin2hex; use function hex2bin; use function str_replace; +use function substr; /** * Lazy version of a UUID: its format has not been determined yet, so it is mostly only usable for string/bytes @@ -63,6 +65,24 @@ final class LazyUuidFromString implements UuidInterface $this->uuid = $uuid; } + /** @psalm-pure */ + public static function fromBytes(string $bytes): self + { + $base16Uuid = bin2hex($bytes); + + return new self( + substr($base16Uuid, 0, 8) + . '-' + . substr($base16Uuid, 8, 4) + . '-' + . substr($base16Uuid, 12, 4) + . '-' + . substr($base16Uuid, 16, 4) + . '-' + . substr($base16Uuid, 20, 12) + ); + } + public function serialize(): string { return $this->uuid; diff --git a/src/Uuid.php b/src/Uuid.php index d7c08b4..1d67d3e 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -416,6 +416,7 @@ class Uuid implements UuidInterface if (! self::$factoryReplaced && strlen($bytes) === 16) { $base16Uuid = bin2hex($bytes); + // Note: we are calling `fromString` internally because we don't know if the given `$bytes` is a valid UUID return self::fromString( substr($base16Uuid, 0, 8) . '-' diff --git a/src/UuidFactory.php b/src/UuidFactory.php index 2d8b3a1..feddef8 100644 --- a/src/UuidFactory.php +++ b/src/UuidFactory.php @@ -24,6 +24,7 @@ use Ramsey\Uuid\Generator\DefaultTimeGenerator; use Ramsey\Uuid\Generator\NameGeneratorInterface; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Generator\TimeGeneratorInterface; +use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Provider\NodeProviderInterface; use Ramsey\Uuid\Provider\Time\FixedTimeProvider; use Ramsey\Uuid\Type\Hexadecimal; @@ -94,11 +95,16 @@ class UuidFactory implements UuidFactoryInterface */ private $validator; + /** @var bool whether the feature set was provided from outside, or we can operate under "default" assumptions */ + private $isDefaultFeatureSet; + /** * @param FeatureSet $features A set of available features in the current environment */ public function __construct(?FeatureSet $features = null) { + $this->isDefaultFeatureSet = $features === null; + $features = $features ?: new FeatureSet(); $this->codec = $features->getCodec(); @@ -128,6 +134,8 @@ class UuidFactory implements UuidFactoryInterface */ public function setCodec(CodecInterface $codec): void { + $this->isDefaultFeatureSet = false; + $this->codec = $codec; } @@ -147,6 +155,8 @@ class UuidFactory implements UuidFactoryInterface */ public function setNameGenerator(NameGeneratorInterface $nameGenerator): void { + $this->isDefaultFeatureSet = false; + $this->nameGenerator = $nameGenerator; } @@ -182,6 +192,8 @@ class UuidFactory implements UuidFactoryInterface */ public function setTimeGenerator(TimeGeneratorInterface $generator): void { + $this->isDefaultFeatureSet = false; + $this->timeGenerator = $generator; } @@ -201,6 +213,8 @@ class UuidFactory implements UuidFactoryInterface */ public function setDceSecurityGenerator(DceSecurityGeneratorInterface $generator): void { + $this->isDefaultFeatureSet = false; + $this->dceSecurityGenerator = $generator; } @@ -220,6 +234,8 @@ class UuidFactory implements UuidFactoryInterface */ public function setRandomGenerator(RandomGeneratorInterface $generator): void { + $this->isDefaultFeatureSet = false; + $this->randomGenerator = $generator; } @@ -231,6 +247,8 @@ class UuidFactory implements UuidFactoryInterface */ public function setNumberConverter(NumberConverterInterface $converter): void { + $this->isDefaultFeatureSet = false; + $this->numberConverter = $converter; } @@ -250,6 +268,8 @@ class UuidFactory implements UuidFactoryInterface */ public function setUuidBuilder(UuidBuilderInterface $builder): void { + $this->isDefaultFeatureSet = false; + $this->uuidBuilder = $builder; } @@ -269,6 +289,8 @@ class UuidFactory implements UuidFactoryInterface */ public function setValidator(ValidatorInterface $validator): void { + $this->isDefaultFeatureSet = false; + $this->validator = $validator; } @@ -458,6 +480,10 @@ class UuidFactory implements UuidFactoryInterface $bytes = substr_replace($bytes, $timeHiAndVersion, 6, 2); $bytes = substr_replace($bytes, $clockSeqHiAndReserved, 8, 2); + if ($this->isDefaultFeatureSet) { + return LazyUuidFromString::fromBytes($bytes); + } + return $this->uuid($bytes); } } diff --git a/tests/Builder/FallbackBuilderTest.php b/tests/Builder/FallbackBuilderTest.php index b9f55b1..7b63b6c 100644 --- a/tests/Builder/FallbackBuilderTest.php +++ b/tests/Builder/FallbackBuilderTest.php @@ -24,7 +24,6 @@ use Ramsey\Uuid\Rfc4122\UuidBuilder as Rfc4122UuidBuilder; use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Rfc4122\UuidV2; use Ramsey\Uuid\Test\TestCase; -use Ramsey\Uuid\UuidInterface; use Ramsey\Uuid\Validator\GenericValidator; class FallbackBuilderTest extends TestCase @@ -112,7 +111,6 @@ class FallbackBuilderTest extends TestCase try { $uuid = $builder->build($codec, $bytes); - $this->assertInstanceOf(UuidInterface::class, $uuid); if (($uuid instanceof UuidV1) || ($uuid instanceof UuidV2) || ($uuid instanceof UuidV6)) { $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); diff --git a/tests/UuidFactoryTest.php b/tests/UuidFactoryTest.php index 0ca2540..43a4244 100644 --- a/tests/UuidFactoryTest.php +++ b/tests/UuidFactoryTest.php @@ -20,7 +20,6 @@ use Ramsey\Uuid\Generator\NameGeneratorInterface; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Generator\TimeGeneratorInterface; use Ramsey\Uuid\Provider\NodeProviderInterface; -use Ramsey\Uuid\Rfc4122\UuidV1; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\UuidFactory; use Ramsey\Uuid\Validator\ValidatorInterface; @@ -150,10 +149,8 @@ class UuidFactoryTest extends TestCase ): void { $factory = new UuidFactory(); - /** @var UuidV1 $uuid */ $uuid = $factory->fromDateTime($dateTime, $node, $clockSeq); - $this->assertInstanceOf(UuidV1::class, $uuid); $this->assertStringMatchesFormat($expectedUuidFormat, $uuid->toString()); $this->assertSame($expectedTime, $uuid->getDateTime()->format('U.u')); } diff --git a/tests/UuidTest.php b/tests/UuidTest.php index cef8511..b0a78c2 100644 --- a/tests/UuidTest.php +++ b/tests/UuidTest.php @@ -24,15 +24,11 @@ use Ramsey\Uuid\Generator\CombGenerator; use Ramsey\Uuid\Generator\RandomGeneratorFactory; use Ramsey\Uuid\Generator\RandomGeneratorInterface; use Ramsey\Uuid\Guid\Guid; -use Ramsey\Uuid\Nonstandard\UuidV6; +use Ramsey\Uuid\Lazy\LazyUuidFromString; use Ramsey\Uuid\Provider\Node\RandomNodeProvider; use Ramsey\Uuid\Provider\Time\FixedTimeProvider; use Ramsey\Uuid\Rfc4122\FieldsInterface; use Ramsey\Uuid\Rfc4122\UuidV1; -use Ramsey\Uuid\Rfc4122\UuidV2; -use Ramsey\Uuid\Rfc4122\UuidV3; -use Ramsey\Uuid\Rfc4122\UuidV4; -use Ramsey\Uuid\Rfc4122\UuidV5; use Ramsey\Uuid\Type\Hexadecimal; use Ramsey\Uuid\Type\Time; use Ramsey\Uuid\Uuid; @@ -85,8 +81,6 @@ class UuidTest extends TestCase $guid = Guid::fromString('ff6f8cb0-c57d-11e1-9b21-0800200c9a66'); - $this->assertInstanceOf(Uuid::class, $guid); - // UUID's and GUID's share the same textual representation. $this->assertSame($uuid->toString(), $guid->toString()); @@ -97,7 +91,6 @@ class UuidTest extends TestCase public function testFromStringWithCurlyBraces(): void { $uuid = Uuid::fromString('{ff6f8cb0-c57d-11e1-9b21-0800200c9a66}'); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertEquals('ff6f8cb0-c57d-11e1-9b21-0800200c9a66', $uuid->toString()); } @@ -128,7 +121,6 @@ class UuidTest extends TestCase public function testFromStringWithUrn(): void { $uuid = Uuid::fromString('urn:uuid:ff6f8cb0-c57d-11e1-9b21-0800200c9a66'); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertEquals('ff6f8cb0-c57d-11e1-9b21-0800200c9a66', $uuid->toString()); } @@ -512,7 +504,6 @@ class UuidTest extends TestCase public function testUuid1(): void { $uuid = Uuid::uuid1(); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(1, $uuid->getVersion()); @@ -522,7 +513,6 @@ class UuidTest extends TestCase { /** @var Uuid $uuid */ $uuid = Uuid::uuid1('0800200c9a66', 0x1669); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(1, $uuid->getVersion()); @@ -535,7 +525,6 @@ class UuidTest extends TestCase { /** @var Uuid $uuid */ $uuid = Uuid::uuid1(new Hexadecimal('0800200c9a66'), 0x1669); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(1, $uuid->getVersion()); @@ -549,7 +538,6 @@ class UuidTest extends TestCase /** @var Uuid $uuid */ $uuid = Uuid::uuid1('7160355e'); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(1, $uuid->getVersion()); @@ -562,7 +550,6 @@ class UuidTest extends TestCase /** @var Uuid $uuid */ $uuid = Uuid::uuid1(new Hexadecimal('7160355e')); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(1, $uuid->getVersion()); @@ -575,7 +562,6 @@ class UuidTest extends TestCase /** @var Uuid $uuid */ $uuid = Uuid::uuid1('71B0aD5e'); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(1, $uuid->getVersion()); @@ -612,7 +598,6 @@ class UuidTest extends TestCase Uuid::setFactory(new UuidFactory(new FeatureSet(false, false, false, true))); $uuid = Uuid::uuid1(); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(1, $uuid->getVersion()); @@ -621,7 +606,6 @@ class UuidTest extends TestCase public function testUuid1WithUserGeneratedRandomNode(): void { $uuid = Uuid::uuid1(new Hexadecimal((string) (new RandomNodeProvider())->getNode())); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(1, $uuid->getVersion()); @@ -630,7 +614,6 @@ class UuidTest extends TestCase public function testUuid6(): void { $uuid = Uuid::uuid6(); - $this->assertInstanceOf(UuidV6::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(6, $uuid->getVersion()); @@ -639,7 +622,6 @@ class UuidTest extends TestCase public function testUuid6WithNodeAndClockSequence(): void { $uuid = Uuid::uuid6(new Hexadecimal('0800200c9a66'), 0x1669); - $this->assertInstanceOf(UuidV6::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertSame(2, $uuid->getVariant()); $this->assertSame(6, $uuid->getVersion()); @@ -652,7 +634,6 @@ class UuidTest extends TestCase { $uuid = Uuid::uuid6(new Hexadecimal('7160355e')); - $this->assertInstanceOf(UuidV6::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertSame(2, $uuid->getVariant()); $this->assertSame(6, $uuid->getVersion()); @@ -663,7 +644,6 @@ class UuidTest extends TestCase { $uuid = Uuid::uuid6(new Hexadecimal('71B0aD5e')); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertSame(2, $uuid->getVariant()); $this->assertSame(6, $uuid->getVersion()); @@ -691,7 +671,6 @@ class UuidTest extends TestCase Uuid::setFactory(new UuidFactory(new FeatureSet(false, false, false, true))); $uuid = Uuid::uuid6(); - $this->assertInstanceOf(UuidV6::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(6, $uuid->getVersion()); @@ -700,7 +679,6 @@ class UuidTest extends TestCase public function testUuid6WithUserGeneratedRandomNode(): void { $uuid = Uuid::uuid6(new Hexadecimal((string) (new RandomNodeProvider())->getNode())); - $this->assertInstanceOf(UuidV6::class, $uuid); $this->assertInstanceOf(DateTimeInterface::class, $uuid->getDateTime()); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(6, $uuid->getVersion()); @@ -757,7 +735,6 @@ class UuidTest extends TestCase public function testUuid4(): void { $uuid = Uuid::uuid4(); - $this->assertInstanceOf(Uuid::class, $uuid); $this->assertEquals(2, $uuid->getVariant()); $this->assertEquals(4, $uuid->getVersion()); } @@ -1081,8 +1058,16 @@ class UuidTest extends TestCase public function testUsingNilAsValidUuid(): void { - $this->assertInstanceOf(Uuid::class, Uuid::uuid3(Uuid::NIL, 'randomtext')); - $this->assertInstanceOf(Uuid::class, Uuid::uuid5(Uuid::NIL, 'randomtext')); + self::assertSame( + '0cb17687-6ec7-324b-833a-f1d101a7edb7', + Uuid::uuid3(Uuid::NIL, 'randomtext') + ->toString() + ); + self::assertSame( + '3b24c15b-1273-5628-ade4-fc67c6ede500', + Uuid::uuid5(Uuid::NIL, 'randomtext') + ->toString() + ); } public function testFromBytes(): void @@ -1660,17 +1645,49 @@ class UuidTest extends TestCase } /** - * @param class-string $expectedClass * @param mixed[] $args * * @dataProvider provideStaticMethods */ public function testStaticCreationMethodsReturnSpecificUuidInstances( - string $expectedClass, string $staticMethod, array $args = [] ): void { - $this->assertInstanceOf($expectedClass, Uuid::$staticMethod(...$args)); + $this->assertInstanceOf(LazyUuidFromString::class, Uuid::$staticMethod(...$args)); + } + + /** + * @param mixed[] $args + * + * @dataProvider provideStaticMethods + */ + public function testUuidInstancesBuiltFromStringAreEquivalentToTheirGeneratedCounterparts( + string $staticMethod, + array $args = [] + ): void { + $generated = Uuid::$staticMethod(...$args); + + self::assertEquals( + $generated, + Uuid::fromString($generated->toString()) + ); + } + + /** + * @param mixed[] $args + * + * @dataProvider provideStaticMethods + */ + public function testUuidInstancesBuiltFromBytesAreEquivalentToTheirGeneratedCounterparts( + string $staticMethod, + array $args = [] + ): void { + $generated = Uuid::$staticMethod(...$args); + + self::assertEquals( + $generated, + Uuid::fromBytes($generated->getBytes()) + ); } /** @@ -1679,11 +1696,11 @@ class UuidTest extends TestCase public function provideStaticMethods(): array { return [ - [UuidV1::class, 'uuid1'], - [UuidV2::class, 'uuid2', [Uuid::DCE_DOMAIN_PERSON]], - [UuidV3::class, 'uuid3', [Uuid::NIL, 'foobar']], - [UuidV4::class, 'uuid4'], - [UuidV5::class, 'uuid5', [Uuid::NIL, 'foobar']], + ['uuid1'], + ['uuid2', [Uuid::DCE_DOMAIN_PERSON]], + ['uuid3', [Uuid::NIL, 'foobar']], + ['uuid4'], + ['uuid5', [Uuid::NIL, 'foobar']], ]; } } diff --git a/tests/benchmark/UuidGenerationBench.php b/tests/benchmark/UuidGenerationBench.php index 21c7fb9..6c6b5ca 100644 --- a/tests/benchmark/UuidGenerationBench.php +++ b/tests/benchmark/UuidGenerationBench.php @@ -16,7 +16,7 @@ namespace Ramsey\Uuid\Benchmark; use Ramsey\Uuid\Provider\Node\StaticNodeProvider; use Ramsey\Uuid\Type\Hexadecimal; -use Ramsey\Uuid\Type\Integer; +use Ramsey\Uuid\Type\Integer as IntegerIdentifier; use Ramsey\Uuid\Uuid; final class UuidGenerationBench @@ -25,7 +25,7 @@ final class UuidGenerationBench private $node; /** @var int */ private $clockSequence; - /** @var Integer */ + /** @var IntegerIdentifier */ private $localIdentifier; /** @var \Ramsey\Uuid\UuidInterface */ private $namespace; @@ -35,7 +35,7 @@ final class UuidGenerationBench $this->node = (new StaticNodeProvider(new Hexadecimal('121212121212'))) ->getNode(); $this->clockSequence = 16383; - $this->localIdentifier = new Integer(5); + $this->localIdentifier = new IntegerIdentifier(5); $this->namespace = Uuid::fromString('c485840e-9389-4548-a276-aeecd9730e50'); }