Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1cf5d37

Browse files
[Uid] Ensure ULIDs are monotonic even when the time goes backward
1 parent 1f4cfc7 commit 1cf5d37

File tree

2 files changed

+18
-20
lines changed

2 files changed

+18
-20
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,16 @@ public function testGenerate()
2626
{
2727
$a = new Ulid();
2828
$b = new Ulid();
29+
usleep(-10000);
30+
$c = new Ulid();
2931

3032
$this->assertSame(0, strncmp($a, $b, 20));
33+
$this->assertSame(0, strncmp($a, $c, 20));
3134
$a = base_convert(strtr(substr($a, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10);
3235
$b = base_convert(strtr(substr($b, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10);
36+
$c = base_convert(strtr(substr($c, -6), 'ABCDEFGHJKMNPQRSTVWXYZ', 'abcdefghijklmnopqrstuv'), 32, 10);
3337
$this->assertSame(1, $b - $a);
38+
$this->assertSame(1, $c - $b);
3439
}
3540

3641
public function testWithInvalidUlid()

src/Symfony/Component/Uid/Ulid.php

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -137,33 +137,23 @@ public function getDateTime(): \DateTimeImmutable
137137
}
138138

139139
if (4 > \strlen($time)) {
140-
$time = str_pad($time, 4, '0', \STR_PAD_LEFT);
140+
$time = '000'.$time;
141141
}
142142

143143
return \DateTimeImmutable::createFromFormat('U.u', substr_replace($time, '.', -3, 0));
144144
}
145145

146146
public static function generate(\DateTimeInterface $time = null): string
147147
{
148-
if (null === $time) {
149-
return self::doGenerate();
150-
}
151-
152-
if (0 > $time = substr($time->format('Uu'), 0, -3)) {
153-
throw new \InvalidArgumentException('The timestamp must be positive.');
154-
}
155-
156-
return self::doGenerate($time);
157-
}
158-
159-
private static function doGenerate(string $mtime = null): string
160-
{
161-
if (null === $time = $mtime) {
148+
if (null === $mtime = $time) {
162149
$time = microtime(false);
163150
$time = substr($time, 11).substr($time, 2, 3);
151+
} elseif (0 > $time = $time->format('Uv')) {
152+
throw new \InvalidArgumentException('The timestamp must be positive.');
164153
}
165154

166-
if ($time !== self::$time) {
155+
if ($time > self::$time || (null !== $mtime && $time !== self::$time)) {
156+
randomize:
167157
$r = unpack('nr1/nr2/nr3/nr4/nr', random_bytes(10));
168158
$r['r1'] |= ($r['r'] <<= 4) & 0xF0000;
169159
$r['r2'] |= ($r['r'] <<= 4) & 0xF0000;
@@ -173,19 +163,22 @@ private static function doGenerate(string $mtime = null): string
173163
self::$rand = array_values($r);
174164
self::$time = $time;
175165
} elseif ([0xFFFFF, 0xFFFFF, 0xFFFFF, 0xFFFFF] === self::$rand) {
176-
if (null === $mtime) {
177-
usleep(100);
166+
if (\PHP_INT_SIZE >= 8 || 10 > \strlen($time = self::$time)) {
167+
$time = (string) (1 + $time);
168+
} elseif ('999999999' === $mtime = substr($time, -9)) {
169+
$time = (1 + substr($time, 0, -9)).'000000000';
178170
} else {
179-
self::$rand = [0, 0, 0, 0];
171+
$time = substr_replace($time, str_pad(++$mtime, 9, '0', \STR_PAD_LEFT), -9);
180172
}
181173

182-
return self::doGenerate($mtime);
174+
goto randomize;
183175
} else {
184176
for ($i = 3; $i >= 0 && 0xFFFFF === self::$rand[$i]; --$i) {
185177
self::$rand[$i] = 0;
186178
}
187179

188180
++self::$rand[$i];
181+
$time = self::$time;
189182
}
190183

191184
if (\PHP_INT_SIZE >= 8) {

0 commit comments

Comments
 (0)