Ensure that Uuid::uuidX() generators produce instances equivalent to Uuid::fromString() and Uuid::fromBytes()

With this change, `Uuid::uuid1()`, `Uuid::uuid2()` and so forth now produce a `LazyUuidFromString` instance, which
is both more memory efficient and comparable to `Uuid::fromString()` instances in other tools, such as within
PHPUnit's `Assertion::assertEqual()`, which would reject any two objects not matching each other's types.

Before this patch, `Assertion::assertEquals(Uuid::uuid5(...), Uuid::fromString(...))` would always fail due to
different subtypes produced by the two factory methods.
This commit is contained in:
Marco Pivetta
2020-07-03 14:18:24 +02:00
parent 2421b79841
commit b805572537
7 changed files with 101 additions and 42 deletions
+20
View File
@@ -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;
+1
View File
@@ -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)
. '-'
+26
View File
@@ -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);
}
}
-2
View File
@@ -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());
-3
View File
@@ -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'));
}
+51 -34
View File
@@ -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']],
];
}
}
+3 -3
View File
@@ -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');
}