8000 [Uid] add AbstractUid and interop with base-58/32/RFC4122 encodings · symfony/symfony@eb851a5 · GitHub
[go: up one dir, main page]

Skip to content

Commit eb851a5

Browse files
[Uid] add AbstractUid and interop with base-58/32/RFC4122 encodings
1 parent 7dc6da6 commit eb851a5

File tree

9 files changed

+210
-68
lines changed

9 files changed

+210
-68
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 Nicolas Grekas <p@tchwork.com>
18+
*/
19+
abstract class AbstractUid implements \JsonSerializable
20+
{
21+
/**
22+
* The identifier in its canonic representation.
23+
*/
24+
protected $uid;
25+
26+
/**
27+
* Whether the passed value is valid for the constructor of the current class.
28+
*/
29+
abstract public static function isValid(string $uid): bool;
30+
31+
/**
32+
* Creates an AbstractUid from an identifier represented in any of the supported formats.
33+
*
34+
* @return static
35+
*
36+
* @throws \InvalidArgumentException When the passed value is not valid
37+
*/
38+
abstract public static function fromString(string $uid): self;
39+
40+
/**
41+
* Returns the identifier as a raw binary string.
42+
*/
43+
abstract public function toBinary(): string;
44+
45+
/**
46+
* Returns the identifier as a base-58 case sensitive string.
47+
*/
48+
public function toBase58(): string
49+
{
50+
return strtr(sprintf('%022s', BinaryUtil::toBase($this->toBinary(), BinaryUtil::BASE58)), '0', '1');
51+
}
52+
53+
/**
54+
* Returns the identifier as a base-32 case insensitive string.
55+
*/
56+
public function toBase32(): string
57+
{
58+
$uid = bin2hex($this->toBinary());
59+
$uid = sprintf('%02s%04s%04s%04s%04s%04s%04s',
60+
base_convert(substr($uid, 0, 2), 16, 32),
61+
base_convert(substr($uid, 2, 5), 16, 32),
62+
base_convert(substr($uid, 7, 5), 16, 32),
63+
base_convert(substr($uid, 12, 5), 16, 32),
64+
base_convert(substr($uid, 17, 5), 16, 32),
65+
base_convert(substr($uid, 22, 5), 16, 32),
66+
base_convert(substr($uid, 27, 5), 16, 32)
67+
);
68+
69+
return strtr($uid, 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ');
70+
}
71+
72+
/**
73+
* Returns the identifier as a RFC4122 case insensitive string.
74+
*/
75+
public function toRfc4122(): string
76+
{
77+
return uuid_unparse($this->toBinary());
78+
}
79+
80+
/**
81+
* Returns whether the argument is an AbstractUid and contains the same value as the current instance.
82+
*/
83+
public function equals($other): bool
84+
{
85+
if (!$other instanceof self) {
86+
return false;
87+
}
88+
89+
return $this->uid === $other->uid;
90+
}
91+
92+
public function compare(self $other): int
93+
{
94+
return (\strlen($this->uid) - \strlen($other->uid)) ?: ($this->uid <=> $other->uid);
95+
}
96+
97+
public function __toString(): string
98+
{
99+
return $this->uid;
100+
}
101+
102+
public function jsonSerialize(): string
103+
{
104+
return $this->uid;
105+
}
106+
}

src/Symfony/Component/Uid/BinaryUtil.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ class BinaryUtil
2323
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
2424
];
2525

26+
public const BASE58 = [
27+
'' => '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
28+
1 => 0, 1, 2, 3, 4, 5, 6, 7, 8, 'A' => 9,
29+
'B' => 10, 'C' => 11, 'D' => 12, 'E' => 13, 'F' => 14, 'G' => 15,
30+
'H' => 16, 'J' => 17, 'K' => 18, 'L' => 19, 'M' => 20, 'N' => 21,
31+
'P' => 22, 'Q' => 23, 'R' => 24, 'S' => 25, 'T' => 26, 'U' => 27,
32+
'V' => 28, 'W' => 29, 'X' => 30, 'Y' => 31, 'Z' => 32, 'a' => 33,
33+
'b' => 34, 'c' => 35, 'd' => 36, 'e' => 37, 'f' => 38, 'g' => 39,
34+
'h' => 40, 'i' => 41, 'j' => 42, 'k' => 43, 'm' => 44, 'n' => 45,
35+
'o' => 46, 'p' => 47, 'q' => 48, 'r' => 49, 's' => 50, 't' => 51,
36+
'u' => 52, 'v' => 53, 'w' => 54, 'x' => 55, 'y' => 56, 'z' => 57,
37+
];
38+
2639
public static function toBase(string $bytes, array $map): string
2740
{
2841
$base = \strlen($alphabet = $map['']);

src/Symfony/Component/Uid/NullUuid.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ class NullUuid extends Uuid
2222

2323
public function __construct()
2424
{
25-
$this->uuid = '00000000-0000-0000-0000-000000000000';
25+
$this->uid = '00000000-0000-0000-0000-000000000000';
2626
}
2727
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Uid\Ulid;
16+
use Symfony\Component\Uid\UuidV4;
1617

1718
class UlidTest extends TestCase
1819
{
@@ -49,6 +50,28 @@ public function testBinary()
4950
$this->assertTrue($ulid->equals(Ulid::fromString(hex2bin('7fffffffffffffffffffffffffffffff'))));
5051
}
5152

53+
public function testFromUuid()
54+
{
55+
$uuid = new UuidV4();
56+
57+
$ulid = Ulid::fromString($uuid);
58+
59+
$this->assertSame($uuid->toBase32(), (string) $ulid);
60+
$this->assertSame($ulid->toBase32(), (string) $ulid);
61+
$this->assertSame((string) $uuid, $ulid->toRfc4122());
62+
$this->assertTrue($ulid->equals(Ulid::fromString($uuid)));
63+
}
64+
65+
public function testBase58()
66+
{
67+
$ulid = new Ulid('00000000000000000000000000');
68+
$this->assertSame('1111111111111111111111', $ulid->toBase58());
69+
70+
$ulid = Ulid::fromString("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF");
71+
$this->assertSame('YcVfxkQb6JRzqk5kF2tNLv', $ulid->toBase58());
72+
$this->assertTrue($ulid->equals(Ulid::fromString('YcVfxkQb6JRzqk5kF2tNLv')));
73+
}
74+
5275
/**
5376
* @group time-sensitive
5477
*/

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\Uid\NullUuid;
16+
use Symfony\Component\Uid\Ulid;
1617
use Symfony\Component\Uid\Uuid;
1718
use Symfony\Component\Uid\UuidV1;
1819
use Symfony\Component\Uid\UuidV3;
@@ -82,6 +83,26 @@ public function testBinary()
8283
$this->assertSame(self::A_UUID_V4, (string) $uuid);
8384
}
8485

86+
public function testFromUlid()
87+
{
88+
$ulid = new Ulid();
89+
$uuid = Uuid::fromString($ulid);
90+
91+
$this->assertSame((string) $ulid, $uuid->toBase32());
92+
$this->assertSame((string) $uuid, $uuid->toRfc4122());
93+
$this->assertTrue($uuid->equals(Uuid::fromString($ulid)));
94+
}
95+
96+
public function testBase58()
97+
{
98+
$uuid = new NullUuid();
99+
$this->assertSame('1111111111111111111111', $uuid->toBase58());
100+
101+
$uuid = Uuid::fromString("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF");
102+
$this->assertSame('YcVfxkQb6JRzqk5kF2tNLv', $uuid->toBase58());
103+
$this->assertTrue($uuid->equals(Uuid::fromString('YcVfxkQb6JRzqk5kF2tNLv')));
104+
}
105+
85106
public function testIsValid()
86107
{
87108
$this->assertFalse(Uuid::isValid('not a uuid'));

src/Symfony/Component/Uid/Ulid.php

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,15 @@
1818
*
1919
* @author Nicolas Grekas <p@tchwork.com>
2020
*/
21-
class Ulid implements \JsonSerializable
21+
class Ulid extends AbstractUid
2222
{
2323
private static $time = -1;
2424
private static $rand = [];
2525

26-
private $ulid;
27-
2826
public function __construct(string $ulid = null)
2927
{
3028
if (null === $ulid) {
31-
$this->ulid = self::generate();
29+
$this->uid = self::generate();
3230

3331
return;
3432
}
@@ -37,7 +35,7 @@ public function __construct(string $ulid = null)
3735
throw new \InvalidArgumentException(sprintf('Invalid ULID: "%s".', $ulid));
3836
}
3937

40-
$this->ulid = strtr($ulid, 'abcdefghjkmnpqrstvwxyz', 'ABCDEFGHJKMNPQRSTVWXYZ');
38+
$this->uid = strtr($ulid, 'abcdefghjkmnpqrstvwxyz', 'ABCDEFGHJKMNPQRSTVWXYZ');
4139
}
4240

4341
public static function isValid(string $ulid): bool
@@ -53,8 +51,17 @@ public static function isValid(string $ulid): bool
5351
return $ulid[0] <= '7';
5452
}
5553

56-
public static function fromString(string $ulid): self
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public static function fromString(string $ulid): parent
5758
{
59+
if (36 === \strlen($ulid) && Uuid::isValid($ulid)) {
60+
$ulid = Uuid::fromString($ulid)->toBinary();
61+
} elseif (22 === \strlen($ulid) && 22 === strspn($ulid, BinaryUtil::BASE58[''])) {
62+
$ulid = BinaryUtil::fromBase($ulid, BinaryUtil::BASE58);
63+
}
64+
5865
if (16 !== \strlen($ulid)) {
5966
return new static($ulid);
6067
}
@@ -73,9 +80,9 @@ public static function fromString(string $ulid): self
7380
return new self(strtr($ulid, 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ'));
7481
}
7582

76-
public function toBinary()
83+
public function toBinary(): string
7784
{
78-
$ulid = strtr($this->ulid, 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
85+
$ulid = strtr($this->uid, 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
7986

8087
$ulid = sprintf('%02s%05s%05s%05s%05s%05s%05s',
8188
base_convert(substr($ulid, 0, 2), 32, 16),
@@ -90,26 +97,14 @@ public function toBinary()
9097
return hex2bin($ulid);
9198
}
9299

93-
/**
94-
* Returns whether the argument is of class Ulid and contains the same value as the current instance.
95-
*/
96-
public function equals($other): bool
100+
public function toBase32(): string
97101
{
98-
if (!$other instanceof self) {
99-
return false;
100-
}
101-
102-
return $this->ulid === $other->ulid;
103-
}
104-
105-
public function compare(self $other): int
106-
{
107-
return $this->ulid <=> $other->ulid;
102+
return $this->uid;
108103
}
109104

110105
public function getTime(): float
111106
{
112-
$time = strtr(substr($this->ulid, 0, 10), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
107+
$time = strtr(substr($this->uid, 0, 10), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
113108

114109
if (\PHP_INT_SIZE >= 8) {
115110
return hexdec(base_convert($time, 32, 16)) / 1000;
@@ -124,16 +119,6 @@ public function getTime(): float
124119
return BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10) / 1000;
125120
}
126121

127-
public function __toString(): string
128-
{
129-
return $this->ulid;
130-
}
131-
132-
public function jsonSerialize(): string
133-
{
134-
return $this->ulid;
135-
}
136-
137122
private static function generate(): string
138123
{
139124
$time = microtime(false);

0 commit comments

Comments
 (0)
0