8000 feature #36066 [Uid] use one class per type of UUID (nicolas-grekas) · symfony/symfony@7dc6da6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7dc6da6

Browse files
feature #36066 [Uid] use one class per type of UUID (nicolas-grekas)
This PR was merged into the 5.1-dev branch. Discussion ---------- [Uid] use one class per type of UUID | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - (embeds #36064 for now) Would it make sense to have one class per type of UUID? This aligns the type system and UUID types, so that one could type hint e.g. `UuidV4 $uuid`. This PR does so. `UuidV1`/2/3/4 and `NullUuid` all extend the base `Uuid` class, which provides common methods and the factories needed to create each king of UUID. This means we don't need the `getType()` nor the `isNull()` methods since they can be replaced by instanceof checks. As expected, `getTime()` and `getMac()` then now exist only on the `UuidV1` class - no need for any version check nor any `LogicException` anymore. Each type is guaranteed to contain a UUID that matches its class' type. The base `Uuid` class is used for the "no type" type. Commits ------- 62f6ac4 [Uid] use one class per type of UUID
2 parents fa5d636 + 62f6ac4 commit 7dc6da6

File tree

9 files changed

+245
-99
lines changed

9 files changed

+245
-99
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Uid;
13+
14+
/**
15+
* @experimental in 5.1
16+
*
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*/
19+
class NullUuid extends Uuid
20+
{
21+
protected const TYPE = UUID_TYPE_NULL;
22+
23+
public function __construct()
24+
{
25+
$this->uuid = '00000000-0000-0000-0000-000000000000';
26+
}
27+
}

src/Symfony/Component/Uid/Tests/UlidTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public function testBinary()
4646
$ulid = new Ulid('3zzzzzzzzzzzzzzzzzzzzzzzzz');
4747
$this->assertSame('7fffffffffffffffffffffffffffffff', bin2hex($ulid->toBinary()));
4848

49-
$this->assertTrue($ulid->equals(Ulid::fromBinary(hex2bin('7fffffffffffffffffffffffffffffff'))));
49+
$this->assertTrue($ulid->equals(Ulid::fromString(hex2bin('7fffffffffffffffffffffffffffffff'))));
5050
}
5151

5252
/**

src/Symfony/Component/Uid/Tests/UuidTest.php

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
namespace Symfony\Tests\Component\Uid;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Uid\NullUuid;
1516
use Symfony\Component\Uid\Uuid;
17+
use Symfony\Component\Uid\UuidV1;
18+
use Symfony\Component\Uid\UuidV3;
19+
use Symfony\Component\Uid\UuidV4;
20+
use Symfony\Component\Uid\UuidV5;
1621

1722
class UuidTest extends TestCase
1823
{
@@ -24,12 +29,12 @@ public function testConstructorWithInvalidUuid()
2429
$this->expectException(\InvalidArgumentException::class);
2530
$this->expectExceptionMessage('Invalid UUID: "this is not a uuid".');
2631

27-
new Uuid('this is not a uuid');
32+
Uuid::fromString('this is not a uuid');
2833
}
2934

3035
public function testConstructorWithValidUuid()
3136
{
32-
$uuid = new Uuid(self::A_UUID_V4);
37+
$uuid = new UuidV4(self::A_UUID_V4);
3338

3439
$this->assertSame(self::A_UUID_V4, (string) $uuid);
3540
$this->assertSame('"'.self::A_UUID_V4.'"', json_encode($uuid));
@@ -39,56 +44,56 @@ public function testV1()
3944
{
4045
$uuid = Uuid::v1();
4146

42-
$this->assertSame(Uuid::TYPE_1, $uuid->getType());
47+
$this->assertInstanceOf(UuidV1::class, $uuid);
48+
49+
$uuid = new UuidV1(self::A_UUID_V1);
50+
51+
$this->assertSame(1583245966.746458, $uuid->getTime());
52+
$this->assertSame('3499710062d0', $uuid->getNode());
4353
}
4454

4555
public function testV3()
4656
{
47-
$uuid = Uuid::v3(new Uuid(self::A_UUID_V4), 'the name');
57+
$uuid = Uuid::v3(new UuidV4(self::A_UUID_V4), 'the name');
4858

49-
$this->assertSame(Uuid::TYPE_3, $uuid->getType());
59+
$this->assertInstanceOf(UuidV3::class, $uuid);
5060
}
5161

5262
public function testV4()
5363
{
5464
$uuid = Uuid::v4();
5565

56-
$this->assertSame(Uuid::TYPE_4, $uuid->getType());
66+
$this->assertInstanceOf(UuidV4::class, $uuid);
5767
}
5868

5969
public function testV5()
6070
{
61-
$uuid = Uuid::v5(new Uuid(self::A_UUID_V4), 'the name');
71+
$uuid = Uuid::v5(new UuidV4(self::A_UUID_V4), 'the name');
6272

63-
$this->assertSame(Uuid::TYPE_5, $uuid->getType());
73+
$this->assertInstanceOf(UuidV5::class, $uuid);
6474
}
6575

6676
public function testBinary()
6777
{
68-
$uuid = new Uuid(self::A_UUID_V4);
78+
$uuid = new UuidV4(self::A_UUID_V4);
79+
$uuid = Uuid::fromString($uuid->toBinary());
6980

70-
$this->assertSame(self::A_UUID_V4, (string) Uuid::fromBinary($uuid->toBinary()));
81+
$this->assertInstanceOf(UuidV4::class, $uuid);
82+
$this->assertSame(self::A_UUID_V4, (string) $uuid);
7183
}
7284

7385
public function testIsValid()
7486
{
7587
$this->assertFalse(Uuid::isValid('not a uuid'));
7688
$this->assertTrue(Uuid::isValid(self::A_UUID_V4));
77-
}
78-
79-
public function testIsNull()
80-
{
81-
$uuid = new Uuid(self::A_UUID_V1);
82-
$this->assertFalse($uuid->isNull());
83-
84-
$uuid = new Uuid('00000000-0000-0000-0000-000000000000');
85-
$this->assertTrue($uuid->isNull());
89+
$this->assertFalse(UuidV4::isValid(self::A_UUID_V1));
90+
$this->assertTrue(UuidV4::isValid(self::A_UUID_V4));
8691
}
8792

8893
public function testEquals()
8994
{
90-
$uuid1 = new Uuid(self::A_UUID_V1);
91-
$uuid2 = new Uuid(self::A_UUID_V4);
95+
$uuid1 = new UuidV1(self::A_UUID_V1);
96+
$uuid2 = new UuidV4(self::A_UUID_V4);
9297

9398
$this->assertTrue($uuid1->equals($uuid1));
9499
$this->assertFalse($uuid1->equals($uuid2));
@@ -99,7 +104,7 @@ public function testEquals()
99104
*/
100105
public function testEqualsAgainstOtherType($other)
101106
{
102-
$this->assertFalse((new Uuid(self::A_UUID_V4))->equals($other));
107+
$this->assertFalse((new UuidV4(self::A_UUID_V4))->equals($other));
103108
}
104109

105110
public function provideInvalidEqualType()
@@ -128,12 +133,11 @@ public function testCompare()
128133
$this->assertSame([$a, $b, $c, $d], $uuids);
129134
}
130135

131-
public function testExtraMethods()
136+
public function testNullUuid()
132137
{
133-
$uuid = new Uuid(self::A_UUID_V1);
138+
$uuid = Uuid::fromString('00000000-0000-0000-0000-000000000000');
134139

135-
$this->assertSame(1583245966.746458, $uuid->getTime());
136-
$this->assertSame('3499710062d0', $uuid->getMac());
137-
$this->assertSame(self::A_UUID_V1, (string) $uuid);
140+
$this->assertInstanceOf(NullUuid::class, $uuid);
141+
$this->assertSame('00000000-0000-0000-0000-000000000000', (string) $uuid);
138142
}
139143
}

src/Symfony/Component/Uid/Ulid.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ public static function isValid(string $ulid): bool
5353
return $ulid[0] <= '7';
5454
}
5555

56-
public static function fromBinary(string $ulid): self
56+
public static function fromString(string $ulid): self
5757
{
5858
if (16 !== \strlen($ulid)) {
59-
throw new \InvalidArgumentException('Invalid binary ULID.');
59+
return new static($ulid);
6060
}
6161

6262
$ulid = bin2hex($ulid);

src/Symfony/Component/Uid/Uuid.php

Lines changed: 40 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -18,74 +18,78 @@
1818
*/
1919
class Uuid implements \JsonSerializable
2020
{
21-
public const TYPE_1 = UUID_TYPE_TIME;
22-
public const TYPE_3 = UUID_TYPE_MD5;
23-
public const TYPE_4 = UUID_TYPE_RANDOM;
24-
public const TYPE_5 = UUID_TYPE_SHA1;
21+
protected const TYPE = UUID_TYPE_DEFAULT;
2522

26-
// https://tools.ietf.org/html/rfc4122#section-4.1.4
27-
// 0x01b21dd213814000 is the number of 100-ns intervals between the
28-
// UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
29-
private const TIME_OFFSET_INT = 0x01b21dd213814000;
30-
private const TIME_OFFSET_COM = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00";
23+
protected $uuid;
3124

32-
private $uuid;
33-
34-
public function __construct(string $uuid = null)
25+
public function __construct(string $uuid)
3526
{
36-
if (null === $uuid) {
37-
$this->uuid = uuid_create(self::TYPE_4);
38-
39-
return;
40-
}
41-
42-
if (!uuid_is_valid($uuid)) {
43-
throw new \InvalidArgumentException(sprintf('Invalid UUID: "%s".', $uuid));
27+
if (static::TYPE !== uuid_type($uuid)) {
28+
throw new \InvalidArgumentException(sprintf('Invalid UUID%s: "%s".', static::TYPE ? 'v'.static::TYPE : '', $uuid));
4429
}
4530

4631
$this->uuid = strtr($uuid, 'ABCDEF', 'abcdef');
4732
}
4833

49-
public static function v1(): self
34+
/**
35+
* @return static
36+
*/
37+
public static function fromString(string $uuid): self
5038
{
51-
return new self(uuid_create(self::TYPE_1));
39+
if (16 === \strlen($uuid)) {
40+
$uuid = uuid_unparse($uuid);
41+
}
42+
43+
if (__CLASS__ !== static::class) {
44+
return new static($uuid);
45+
}
46+
47+
switch (uuid_type($uuid)) {
48+
case UuidV1::TYPE: return new UuidV1($uuid);
49+
case UuidV3::TYPE: return new UuidV3($uuid);
50+
case UuidV4::TYPE: return new UuidV4($uuid);
51+
case UuidV5::TYPE: return new UuidV5($uuid);
52+
case NullUuid::TYPE: return new NullUuid();
53+
case self::TYPE: return new self($uuid);
54+
}
55+
56+
throw new \InvalidArgumentException(sprintf('Invalid UUID: "%s".', $uuid));
5257
}
5358

54-
public static function v3(self $uuidNamespace, string $name): self
59+
final public static function v1(): UuidV1
5560
{
56-
return new self(uuid_generate_md5($uuidNamespace->uuid, $name));
61+
return new UuidV1();
5762
}
5863

59-
public static function v4(): self
64+
final public static function v3(self $namespace, string $name): UuidV3
6065
{
61-
return new self(uuid_create(self::TYPE_4));
66+
return new UuidV3(uuid_generate_md5($namespace->uuid, $name));
6267
}
6368

64-
public static function v5(self $uuidNamespace, string $name): self
69+
final public static function v4(): UuidV4
6570
{
66-
return new self(uuid_generate_sha1($uuidNamespace->uuid, $name));
71+
return new UuidV4();
6772
}
6873

69-
public static function fromBinary(string $uuidAsBinary): self
74+
final public static function v5(self $namespace, string $name): UuidV5
7075
{
71-
return new self(uuid_unparse($uuidAsBinary));
76+
return new UuidV5(uuid_generate_sha1($namespace->uuid, $name));
7277
}
7378

7479
public static function isValid(string $uuid): bool
7580
{
76-
return uuid_is_valid($uuid);
81+
if (__CLASS__ === static::class) {
82+
return uuid_is_valid($uuid);
83+
}
84+
85+
return static::TYPE === uuid_type($uuid);
7786
}
7887

7988
public function toBinary(): string
8089
{
8190
return uuid_parse($this->uuid);
8291
}
8392

84-
public function isNull(): bool
85-
{
86-
return uuid_is_null($this->uuid);
87-
}
88-
8993
/**
9094
* Returns whether the argument is of class Uuid and contains the same value as the current instance.
9195
*/
@@ -103,39 +107,6 @@ public function compare(self $other): int
103107
return uuid_compare($this->uuid, $other->uuid);
104108
}
105109

106-
public function getType(): int
107-
{
108-
return uuid_type($this->uuid);
109-
}
110-
111-
public function getTime(): float
112-
{
113-
if (self::TYPE_1 !== $t = uuid_type($this->uuid)) {
114-
throw new \LogicException("UUID of type $t doesn't contain a time.");
115-
}
116-
117-
$time = '0'.substr($this->uuid, 15, 3).substr($this->uuid, 9, 4).substr($this->uuid, 0, 8);
118-
119-
if (\PHP_INT_SIZE >= 8) {
120-
return (hexdec($time) - self::TIME_OFFSET_INT) / 10000000;
121-
}
122-
123-
$time = str_pad(hex2bin($time), 8, "\0", STR_PAD_LEFT);
124-
$time = BinaryUtil::add($time, self::TIME_OFFSET_COM);
125-
$time[0] = $time[0] & "\x7F";
126-
127-
return BinaryUtil::toBase($time, BinaryUtil::BASE10) / 10000000;
128-
}
129-
130-
public function getMac(): string
131-
{
132-
if (self::TYPE_1 !== $t = uuid_type($this->uuid)) {
133-
throw new \LogicException("UUID of type $t doesn't contain a MAC.");
134-
}
135-
136-
return uuid_mac($this->uuid);
137-
}
138-
139110
public function __toString(): string
140111
{
141112
return $this->uuid;

src/Symfony/Component/Uid/UuidV1.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Uid;
13+
14+
/**
15+
* A v1 UUID contains a 60-bit timestamp and ~60 extra unique bits.
16+
*
17+
* @experimental in 5.1
18+
*
19+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
20+
*/
21+
class UuidV1 extends Uuid
22+
{
23+
protected const TYPE = UUID_TYPE_TIME;
24+
25+
// https://tools.ietf.org/html/rfc4122#section-4.1.4
26+
// 0x01b21dd213814000 is the number of 100-ns intervals between the
27+
// UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
28+
private const TIME_OFFSET_INT = 0x01b21dd213814000;
29+
private const TIME_OFFSET_COM = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00";
30+
31+
public function __construct(string $uuid = null)
32+
{
33+
if (null === $uuid) {
34+
$this->uuid = uuid_create(static::TYPE);
35+
} else {
36+
parent::__construct($uuid);
37+
}
38+
}
39+
40+
public function getTime(): float
41+
{
42+
$time = '0'.substr($this->uuid, 15, 3).substr($this->uuid, 9, 4).substr($this->uuid, 0, 8);
43+
44+
if (\PHP_INT_SIZE >= 8) {
45+
return (hexdec($time) - self::TIME_OFFSET_INT) / 10000000;
46+
}
47+
48+
$time = str_pad(hex2bin($time), 8, "\0", STR_PAD_LEFT);
49+
$time = BinaryUtil::add($time, self::TIME_OFFSET_COM);
50+
$time[0] = $time[0] & "\x7F";
51+
52+
return BinaryUtil::toBase($time, BinaryUtil::BASE10) / 10000000;
53+
}
54+
55+
public function getNode(): string
56+
{
57+
return uuid_mac($this->uuid);
58+
}
59+
}

0 commit comments

Comments
 (0)
0