From 1cf5d37d5913b22589ca53a623a68f8f0130eb3f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 8 Sep 2022 18:30:24 +0200 Subject: [PATCH] [Uid] Ensure ULIDs are monotonic even when the time goes backward --- src/Symfony/Component/Uid/Tests/UlidTest.php | 5 +++ src/Symfony/Component/Uid/Ulid.php | 33 ++++++++------------ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Symfony/Component/Uid/Tests/UlidTest.php b/src/Symfony/Component/Uid/Tests/UlidTest.php index 9a8ee9a78abe0..50801a840c326 100644 --- a/src/Symfony/Component/Uid/Tests/UlidTest.php +++ b/src/Symfony/Component/Uid/Tests/UlidTest.php @@ -26,11 +26,16 @@ public function testGenerate() { $a = new Ulid(); $b = new Ulid(); + usleep(-10000); + $c = new Ulid(); $this->assertSame(0, strncmp($a, $b, 20)); + $this->assertSame(0, strncmp($a, $c, 20)); $a = base_convert(strtr(substr($a, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10); $b = base_convert(strtr(substr($b, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10); + $c = base_convert(strtr(substr($c, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10); $this->assertSame(1, $b - $a); + $this->assertSame(1, $c - $b); } public function testWithInvalidUlid() diff --git a/src/Symfony/Component/Uid/Ulid.php b/src/Symfony/Component/Uid/Ulid.php index a23481612745e..bda82ef6856c9 100644 --- a/src/Symfony/Component/Uid/Ulid.php +++ b/src/Symfony/Component/Uid/Ulid.php @@ -137,7 +137,7 @@ public function getDateTime(): \DateTimeImmutable } if (4 > \strlen($time)) { - $time = str_pad($time, 4, '0', \STR_PAD_LEFT); + $time = '000'.$time; } return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0)); @@ -145,25 +145,15 @@ public function getDateTime(): \DateTimeImmutable public static function generate(\DateTimeInterface $time = null): string { - if (null === $time) { - return self::doGenerate(); - } - - if (0 > $time = substr($time->format('Uu'), 0, -3)) { - throw new \InvalidArgumentException('The timestamp must be positive.'); - } - - return self::doGenerate($time); - } - - private static function doGenerate(string $mtime = null): string - { - if (null === $time = $mtime) { + if (null === $mtime = $time) { $time = microtime(false); $time = substr($time, 11).substr($time, 2, 3); + } elseif (0 > $time = $time->format('Uv')) { + throw new \InvalidArgumentException('The timestamp must be positive.'); } - if ($time !== self::$time) { + if ($time > self::$time || (null !== $mtime && $time !== self::$time)) { + randomize: $r = unpack('nr1/nr2/nr3/nr4/nr', random_bytes(10)); $r['r1'] |= ($r['r'] <<= 4) & 0xF0000; $r['r2'] |= ($r['r'] <<= 4) & 0xF0000; @@ -173,19 +163,22 @@ private static function doGenerate(string $mtime = null): string self::$rand = array_values($r); self::$time = $time; } elseif ([0xFFFFF, 0xFFFFF, 0xFFFFF, 0xFFFFF] === self::$rand) { - if (null === $mtime) { - usleep(100); + if (\PHP_INT_SIZE >= 8 || 10 > \strlen($time = self::$time)) { + $time = (string) (1 + $time); + } elseif ('999999999' === $mtime = substr($time, -9)) { + $time = (1 + substr($time, 0, -9)).'000000000'; } else { - self::$rand = [0, 0, 0, 0]; + $time = substr_replace($time, str_pad(++$mtime, 9, '0', \STR_PAD_LEFT), -9); } - return self::doGenerate($mtime); + goto randomize; } else { for ($i = 3; $i >= 0 && 0xFFFFF === self::$rand[$i]; --$i) { self::$rand[$i] = 0; } ++self::$rand[$i]; + $time = self::$time; } if (\PHP_INT_SIZE >= 8) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy