Skip to content

Commit ec44dbf

Browse files
committed
updates, more tests
1 parent db5dd3d commit ec44dbf

File tree

2 files changed

+644
-35
lines changed

2 files changed

+644
-35
lines changed

src/Ulid.php

Lines changed: 135 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,45 +29,165 @@ class Ulid
2929
* Generate a new ULID.
3030
*
3131
* @return string The generated ULID.
32-
* @throws \Exception
3332
*/
3433
public static function generate(bool $upperCase = true): string
3534
{
36-
$time = (int)(microtime(true) * 1000000);
35+
$time = self::microtimeToUlidTime(\microtime(true));
3736
$timeChars = self::encodeTime($time);
3837
$randChars = self::encodeRandomness();
3938
$ulid = $timeChars . $randChars;
4039

41-
return $upperCase ? strtoupper($ulid) : strtolower($ulid);
40+
$ulid = $upperCase ? \strtoupper($ulid) : \strtolower($ulid);
41+
42+
return $ulid;
4243
}
4344

44-
private static function encodeTime(int $time): string
45+
/**
46+
* @param int $time
47+
*
48+
* @return string
49+
*/
50+
public static function encodeTime(int $time): string
4551
{
52+
$encodingCharsArray = str_split(self::ENCODING_CHARS);
4653
$timeChars = '';
4754
for ($i = 0; $i < 10; $i++) {
48-
$mod = $time % self::ENCODING_LENGTH;
49-
$timeChars = self::ENCODING_CHARS[$mod] . $timeChars;
50-
$time = ($time - $mod) / self::ENCODING_LENGTH;
55+
$mod = \floor($time % self::ENCODING_LENGTH);
56+
$timeChars = $encodingCharsArray[$mod] . $timeChars;
57+
$time = (int)(($time - $mod) / self::ENCODING_LENGTH);
5158
}
5259
return $timeChars;
5360
}
5461

55-
/**
56-
* @throws \Exception
57-
*/
58-
private static function encodeRandomness(): string
62+
public static function encodeRandomness(): string
5963
{
60-
$randomBytes = random_bytes(10); // 80 bits
64+
$encodingCharsArray = str_split(self::ENCODING_CHARS);
65+
$randomBytes = \random_bytes(10); // 80 bits
66+
// Check if the random bytes were generated successfully.
67+
if (false === $randomBytes) {
68+
throw new \RuntimeException('Failed to generate random bytes');
69+
}
70+
6171
$randChars = '';
6272
for ($i = 0; $i < 16; $i++) {
63-
$randValue = ord($randomBytes[$i % 10]);
64-
if ($i % 2 === 0) {
73+
$randValue = \ord($randomBytes[$i % 10]);
74+
if (0 === $i % 2) {
6575
$randValue >>= 3; // take the upper 5 bits
6676
} else {
6777
$randValue &= 31; // take the lower 5 bits
6878
}
69-
$randChars .= self::ENCODING_CHARS[$randValue];
79+
$randChars .= $encodingCharsArray[$randValue];
7080
}
7181
return $randChars;
7282
}
83+
84+
/**
85+
* @param string $ulid
86+
*
87+
* @return array
88+
*/
89+
public static function decode(string $ulid): array
90+
{
91+
if (!self::isValid($ulid)) {
92+
throw new \InvalidArgumentException('Invalid ULID string');
93+
}
94+
95+
$time = self::decodeTime($ulid);
96+
$rand = self::decodeRandomness($ulid);
97+
98+
return [
99+
'time' => $time,
100+
'rand' => $rand,
101+
];
102+
}
103+
104+
/**
105+
* @param string $ulid
106+
*
107+
* @return int
108+
*/
109+
public static function decodeTime(string $ulid): int
110+
{
111+
// $encodingCharsArray = str_split(self::ENCODING_CHARS);
112+
113+
// Check if the ULID string is valid.
114+
if (!self::isValid($ulid)) {
115+
throw new \InvalidArgumentException('Invalid ULID string');
116+
}
117+
118+
$time = 0;
119+
for ($i = 0; $i < 10; $i++) {
120+
$char = $ulid[$i];
121+
$value = \strpos(self::ENCODING_CHARS, $char);
122+
$exponent = 9 - $i;
123+
$time += $value * \bcpow((string)self::ENCODING_LENGTH, (string)$exponent);
124+
}
125+
126+
return $time;
127+
}
128+
129+
/**
130+
* @param string $ulid
131+
*
132+
* @return int
133+
*/
134+
public static function decodeRandomness(string $ulid): int
135+
{
136+
if (26 !== strlen($ulid)) {
137+
throw new \InvalidArgumentException('Invalid ULID length'); // Changed line
138+
}
139+
140+
$rand = 0;
141+
for ($i = 10; $i < 26; $i++) {
142+
$char = $ulid[$i];
143+
$value = \strpos(self::ENCODING_CHARS, $char);
144+
145+
// Check if the random value is within the valid range.
146+
if ($value < 0 || $value >= self::ENCODING_LENGTH) {
147+
throw new \InvalidArgumentException('Invalid ULID random value');
148+
}
149+
$exponent = 15 - $i;
150+
$rand += $value * \bcpow((string)self::ENCODING_LENGTH, (string)$exponent);
151+
}
152+
153+
return $rand;
154+
}
155+
156+
/**
157+
* @param string $ulid
158+
*
159+
* @return bool
160+
*/
161+
public static function isValid(string $ulid): bool
162+
{
163+
// Check the length of the ULID string before throwing an exception.
164+
if (26 !== strlen($ulid)) {
165+
return false;
166+
}
167+
168+
// Throw an exception if the ULID is invalid.
169+
try {
170+
self::decodeRandomness($ulid);
171+
} catch (\InvalidArgumentException $e) {
172+
return false;
73173
}
174+
175+
return true;
176+
}
177+
178+
/**
179+
* @param float $microtime
180+
*
181+
* @return int
182+
*/
183+
public static function microtimeToUlidTime(float $microtime): int
184+
{
185+
$timestamp = $microtime * 1000000;
186+
$unixEpoch = 946684800000000; // Microseconds since the Unix epoch.
187+
188+
return (int)($timestamp - $unixEpoch);
189+
}
190+
}
191+
192+
193+

0 commit comments

Comments
 (0)