diff --git a/src/Symfony/Component/Uid/AbstractUid.php b/src/Symfony/Component/Uid/AbstractUid.php new file mode 100644 index 0000000000..765fc3b05f --- /dev/null +++ b/src/Symfony/Component/Uid/AbstractUid.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Uid; + +/** + * @experimental in 5.1 + * + * @author Nicolas Grekas
+ */ +abstract class AbstractUid implements \JsonSerializable +{ + /** + * The identifier in its canonic representation. + */ + protected $uid; + + /** + * Whether the passed value is valid for the constructor of the current class. + */ + abstract public static function isValid(string $uid): bool; + + /** + * Creates an AbstractUid from an identifier represented in any of the supported formats. + * + * @return static + * + * @throws \InvalidArgumentException When the passed value is not valid + */ + abstract public static function fromString(string $uid): self; + + /** + * Returns the identifier as a raw binary string. + */ + abstract public function toBinary(): string; + + /** + * Returns the identifier as a base-58 case sensitive string. + */ + public function toBase58(): string + { + return strtr(sprintf('%022s', BinaryUtil::toBase($this->toBinary(), BinaryUtil::BASE58)), '0', '1'); + } + + /** + * Returns the identifier as a base-32 case insensitive string. + */ + public function toBase32(): string + { + $uid = bin2hex($this->toBinary()); + $uid = sprintf('%02s%04s%04s%04s%04s%04s%04s', + base_convert(substr($uid, 0, 2), 16, 32), + base_convert(substr($uid, 2, 5), 16, 32), + base_convert(substr($uid, 7, 5), 16, 32), + base_convert(substr($uid, 12, 5), 16, 32), + base_convert(substr($uid, 17, 5), 16, 32), + base_convert(substr($uid, 22, 5), 16, 32), + base_convert(substr($uid, 27, 5), 16, 32) + ); + + return strtr($uid, 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ'); + } + + /** + * Returns the identifier as a RFC4122 case insensitive string. + */ + public function toRfc4122(): string + { + return uuid_unparse($this->toBinary()); + } + + /** + * Returns whether the argument is an AbstractUid and contains the same value as the current instance. + */ + public function equals($other): bool + { + if (!$other instanceof self) { + return false; + } + + return $this->uid === $other->uid; + } + + public function compare(self $other): int + { + return (\strlen($this->uid) - \strlen($other->uid)) ?: ($this->uid <=> $other->uid); + } + + public function __toString(): string + { + return $this->uid; + } + + public function jsonSerialize(): string + { + return $this->uid; + } +} diff --git a/src/Symfony/Component/Uid/BinaryUtil.php b/src/Symfony/Component/Uid/BinaryUtil.php index 5e3925df8a..812dd4f665 100644 --- a/src/Symfony/Component/Uid/BinaryUtil.php +++ b/src/Symfony/Component/Uid/BinaryUtil.php @@ -23,6 +23,19 @@ class BinaryUtil 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ]; + public const BASE58 = [ + '' => '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', + 1 => 0, 1, 2, 3, 4, 5, 6, 7, 8, 'A' => 9, + 'B' => 10, 'C' => 11, 'D' => 12, 'E' => 13, 'F' => 14, 'G' => 15, + 'H' => 16, 'J' => 17, 'K' => 18, 'L' => 19, 'M' => 20, 'N' => 21, + 'P' => 22, 'Q' => 23, 'R' => 24, 'S' => 25, 'T' => 26, 'U' => 27, + 'V' => 28, 'W' => 29, 'X' => 30, 'Y' => 31, 'Z' => 32, 'a' => 33, + 'b' => 34, 'c' => 35, 'd' => 36, 'e' => 37, 'f' => 38, 'g' => 39, + 'h' => 40, 'i' => 41, 'j' => 42, 'k' => 43, 'm' => 44, 'n' => 45, + 'o' => 46, 'p' => 47, 'q' => 48, 'r' => 49, 's' => 50, 't' => 51, + 'u' => 52, 'v' => 53, 'w' => 54, 'x' => 55, 'y' => 56, 'z' => 57, + ]; + public static function toBase(string $bytes, array $map): string { $base = \strlen($alphabet = $map['']); diff --git a/src/Symfony/Component/Uid/NilUuid.php b/src/Symfony/Component/Uid/NilUuid.php index c7614e46c9..c3b5db16d4 100644 --- a/src/Symfony/Component/Uid/NilUuid.php +++ b/src/Symfony/Component/Uid/NilUuid.php @@ -22,6 +22,6 @@ class NilUuid extends Uuid public function __construct() { - $this->uuid = '00000000-0000-0000-0000-000000000000'; + $this->uid = '00000000-0000-0000-0000-000000000000'; } } diff --git a/src/Symfony/Component/Uid/Tests/UlidTest.php b/src/Symfony/Component/Uid/Tests/UlidTest.php index 4558ec85e9..5a3d076c06 100644 --- a/src/Symfony/Component/Uid/Tests/UlidTest.php +++ b/src/Symfony/Component/Uid/Tests/UlidTest.php @@ -13,6 +13,7 @@ namespace Symfony\Tests\Component\Uid; use PHPUnit\Framework\TestCase; use Symfony\Component\Uid\Ulid; +use Symfony\Component\Uid\UuidV4; class UlidTest extends TestCase { @@ -49,6 +50,28 @@ class UlidTest extends TestCase $this->assertTrue($ulid->equals(Ulid::fromString(hex2bin('7fffffffffffffffffffffffffffffff')))); } + public function testFromUuid() + { + $uuid = new UuidV4(); + + $ulid = Ulid::fromString($uuid); + + $this->assertSame($uuid->toBase32(), (string) $ulid); + $this->assertSame($ulid->toBase32(), (string) $ulid); + $this->assertSame((string) $uuid, $ulid->toRfc4122()); + $this->assertTrue($ulid->equals(Ulid::fromString($uuid))); + } + + public function testBase58() + { + $ulid = new Ulid('00000000000000000000000000'); + $this->assertSame('1111111111111111111111', $ulid->toBase58()); + + $ulid = Ulid::fromString("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); + $this->assertSame('YcVfxkQb6JRzqk5kF2tNLv', $ulid->toBase58()); + $this->assertTrue($ulid->equals(Ulid::fromString('YcVfxkQb6JRzqk5kF2tNLv'))); + } + /** * @group time-sensitive */ diff --git a/src/Symfony/Component/Uid/Tests/UuidTest.php b/src/Symfony/Component/Uid/Tests/UuidTest.php index f6e8bf1bb0..addb9dfa4b 100644 --- a/src/Symfony/Component/Uid/Tests/UuidTest.php +++ b/src/Symfony/Component/Uid/Tests/UuidTest.php @@ -13,6 +13,7 @@ namespace Symfony\Tests\Component\Uid; use PHPUnit\Framework\TestCase; use Symfony\Component\Uid\NilUuid; +use Symfony\Component\Uid\Ulid; use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\UuidV1; use Symfony\Component\Uid\UuidV3; @@ -95,6 +96,26 @@ class UuidTest extends TestCase $this->assertSame(self::A_UUID_V4, (string) $uuid); } + public function testFromUlid() + { + $ulid = new Ulid(); + $uuid = Uuid::fromString($ulid); + + $this->assertSame((string) $ulid, $uuid->toBase32()); + $this->assertSame((string) $uuid, $uuid->toRfc4122()); + $this->assertTrue($uuid->equals(Uuid::fromString($ulid))); + } + + public function testBase58() + { + $uuid = new NilUuid(); + $this->assertSame('1111111111111111111111', $uuid->toBase58()); + + $uuid = Uuid::fromString("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); + $this->assertSame('YcVfxkQb6JRzqk5kF2tNLv', $uuid->toBase58()); + $this->assertTrue($uuid->equals(Uuid::fromString('YcVfxkQb6JRzqk5kF2tNLv'))); + } + public function testIsValid() { $this->assertFalse(Uuid::isValid('not a uuid')); diff --git a/src/Symfony/Component/Uid/Ulid.php b/src/Symfony/Component/Uid/Ulid.php index 6bd2565dfd..aa1d70601c 100644 --- a/src/Symfony/Component/Uid/Ulid.php +++ b/src/Symfony/Component/Uid/Ulid.php @@ -20,17 +20,15 @@ namespace Symfony\Component\Uid; * * @author Nicolas Grekas
*/
-class Ulid implements \JsonSerializable
+class Ulid extends AbstractUid
{
private static $time = -1;
private static $rand = [];
- private $ulid;
-
public function __construct(string $ulid = null)
{
if (null === $ulid) {
- $this->ulid = self::generate();
+ $this->uid = self::generate();
return;
}
@@ -39,7 +37,7 @@ class Ulid implements \JsonSerializable
throw new \InvalidArgumentException(sprintf('Invalid ULID: "%s".', $ulid));
}
- $this->ulid = strtr($ulid, 'abcdefghjkmnpqrstvwxyz', 'ABCDEFGHJKMNPQRSTVWXYZ');
+ $this->uid = strtr($ulid, 'abcdefghjkmnpqrstvwxyz', 'ABCDEFGHJKMNPQRSTVWXYZ');
}
public static function isValid(string $ulid): bool
@@ -55,8 +53,17 @@ class Ulid implements \JsonSerializable
return $ulid[0] <= '7';
}
- public static function fromString(string $ulid): self
+ /**
+ * {@inheritdoc}
+ */
+ public static function fromString(string $ulid): parent
{
+ if (36 === \strlen($ulid) && Uuid::isValid($ulid)) {
+ $ulid = Uuid::fromString($ulid)->toBinary();
+ } elseif (22 === \strlen($ulid) && 22 === strspn($ulid, BinaryUtil::BASE58[''])) {
+ $ulid = BinaryUtil::fromBase($ulid, BinaryUtil::BASE58);
+ }
+
if (16 !== \strlen($ulid)) {
return new static($ulid);
}
@@ -75,9 +82,9 @@ class Ulid implements \JsonSerializable
return new self(strtr($ulid, 'abcdefghijklmnopqrstuv', 'ABCDEFGHJKMNPQRSTVWXYZ'));
}
- public function toBinary()
+ public function toBinary(): string
{
- $ulid = strtr($this->ulid, 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
+ $ulid = strtr($this->uid, 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
$ulid = sprintf('%02s%05s%05s%05s%05s%05s%05s',
base_convert(substr($ulid, 0, 2), 32, 16),
@@ -92,26 +99,14 @@ class Ulid implements \JsonSerializable
return hex2bin($ulid);
}
- /**
- * Returns whether the argument is of class Ulid and contains the same value as the current instance.
- */
- public function equals($other): bool
+ public function toBase32(): string
{
- if (!$other instanceof self) {
- return false;
- }
-
- return $this->ulid === $other->ulid;
- }
-
- public function compare(self $other): int
- {
- return $this->ulid <=> $other->ulid;
+ return $this->uid;
}
public function getTime(): float
{
- $time = strtr(substr($this->ulid, 0, 10), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
+ $time = strtr(substr($this->uid, 0, 10), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv');
if (\PHP_INT_SIZE >= 8) {
return hexdec(base_convert($time, 32, 16)) / 1000;
@@ -126,16 +121,6 @@ class Ulid implements \JsonSerializable
return BinaryUtil::toBase(hex2bin($time), BinaryUtil::BASE10) / 1000;
}
- public function __toString(): string
- {
- return $this->ulid;
- }
-
- public function jsonSerialize(): string
- {
- return $this->ulid;
- }
-
private static function generate(): string
{
$time = microtime(false);
diff --git a/src/Symfony/Component/Uid/Uuid.php b/src/Symfony/Component/Uid/Uuid.php
index f96a0e4d94..50d2993cdb 100644
--- a/src/Symfony/Component/Uid/Uuid.php
+++ b/src/Symfony/Component/Uid/Uuid.php
@@ -16,7 +16,7 @@ namespace Symfony\Component\Uid;
*
* @author Grégoire Pineau