Skip to content

Feature/update #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 30 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ if you wanna track the source of the UUIDs, you should use it (pre-define the no
```php
// Get v1 UUID (Time based)
\Infocyph\UID\UUID::v1();

// alternatively can also use
\Infocyph\UID\uuid1();

Expand All @@ -71,27 +70,25 @@ if you wanna track the source of the UUIDs, you should use it (pre-define the no

```php
// Get v3 UUID for 'TestString'
\Infocyph\UID\UUID::v3('TestString');

\Infocyph\UID\UUID::v3('a pre-generated UUID', 'the string you wanna get UUID for');
// alternatively can also use
\Infocyph\UID\uuid3();

/**
* Get v3 UUID for an URL & pre-defined namespace
* You can pass X500, URL, OID, DNS (check RFC4122 #Appendix C)
*/
\Infocyph\UID\UUID::v3('abmmhasan.github.io','url');
\Infocyph\UID\UUID::v3('url', 'abmmhasan.github.io');

// You can generate a random UUID & use as namespace as well
\Infocyph\UID\UUID::v3('abmmhasan.github.io','fa1700dd-828c-4d1b-8e6d-a6104807da90');
\Infocyph\UID\UUID::v3('fa1700dd-828c-4d1b-8e6d-a6104807da90', 'abmmhasan.github.io');
```

#### UUID v4: Random UUID.

```php
// Get v4 UUID (completely random)
\Infocyph\UID\UUID::v4();

// alternatively can also use
\Infocyph\UID\uuid4();
```
Expand All @@ -102,19 +99,18 @@ Better replacement for v3 due to better hashing algorithm (SHA1 instead of MD5).

```php
// Get v5 UUID for 'TestString'
\Infocyph\UID\UUID::v5('TestString');

\Infocyph\UID\UUID::v5('a pre-generated UUID', 'the string you wanna get UUID for');
// alternatively can also use
\Infocyph\UID\uuid5();

/**
* Get v5 UUID for an URL & pre-defined namespace
* You can pass X500, URL, OID, DNS (check RFC4122 #Appendix C)
*/
\Infocyph\UID\UUID::v5('abmmhasan.github.io','url');
\Infocyph\UID\UUID::v5('url', 'abmmhasan.github.io');

// You can generate a random UUID & use as namespace as well
\Infocyph\UID\UUID::v5('abmmhasan.github.io','fa1700dd-828c-4d1b-8e6d-a6104807da90');
\Infocyph\UID\UUID::v5('fa1700dd-828c-4d1b-8e6d-a6104807da90', 'abmmhasan.github.io');
```

#### UUID v6 (draft-based/unofficial): Time-based UUID.
Expand All @@ -124,7 +120,6 @@ Better replacement for v1. Provides more randomness & uniqueness.
```php
// Get v6 UUID (Time based)
\Infocyph\UID\UUID::v6();

// alternatively can also use
\Infocyph\UID\uuid6();

Expand All @@ -135,22 +130,24 @@ Better replacement for v1. Provides more randomness & uniqueness.
#### UUID v7 (draft-based/unofficial): Time-based UUID.

```php
// Get v7 UUID (Time based)
// Get v7 UUID for curren time
\Infocyph\UID\UUID::v7();

// alternatively can also use
\Infocyph\UID\uuid7();

// Pass your pre-generated node (for node specific UUID)
\Infocyph\UID\UUID::v7($node); // check additional section for how to generate one
// Pass your pre-generated node (for node specific UUID) for current time
\Infocyph\UID\UUID::v7(null, $node); // check additional section for how to generate one

// Get v7 UUID for pre-define time using DateTimeInterface
$timeInterface = new DateTime(); // DateTime implements DateTimeInterface
\Infocyph\UID\UUID::v7($timeInterface);
```

#### UUID v8 (draft-based/unofficial): Time-based UUID. Lexicographically sortable.

```php
// Get v6 UUID (Time based)
\Infocyph\UID\UUID::v8();

// alternatively can also use
\Infocyph\UID\uuid8();

Expand Down Expand Up @@ -237,23 +234,23 @@ Library exclusive.

## Benchmark

| Type | Total Duration (1M Generation, Single thread) |
|:---------------------------|:---------------------------------------------:|
| UUID v1 (random node) | 1.23968s |
| UUID v1 (fixed node) | 0.94521s |
| UUID v3 (custom namespace) | 0.76439s |
| UUID v4 | 0.70501s |
| UUID v5 (custom namespace) | 0.89831s |
| UUID v6 (random node) | 1.39867s |
| UUID v6 (fixed node) | 1.42344s |
| UUID v7 (random node) | 1.40466s |
| UUID v7 (fixed node) | 1.49268s |
| UUID v8 (random node) | 1.80438s |
| UUID v8 (fixed node) | 1.78257s |
| ULID | 1.79775s |
| TBSL | 0.80612s |

_Note: Snowflake & Sonyflake not included, due to their way of work_
| Type | Generation time (ms) |
|:---------------------------|:---------------------------------------------------------------------------------:|
| UUID v1 (random node) | 0.00411 (ramsey/Uuid: 0.18753) |
| UUID v1 (fixed node) | 0.00115 (ramsey/Uuid: 0.17386) |
| UUID v3 (custom namespace) | 0.00257 (ramsey/Uuid: 0.03015) |
| UUID v4 | 0.00362 (ramsey/Uuid: 0.16501) |
| UUID v5 (custom namespace) | 0.00108 (ramsey/Uuid: 0.03658) |
| UUID v6 (random node) | 0.00444 (ramsey/Uuid: 0.17469) |
| UUID v6 (fixed node) | 0.00164 (ramsey/Uuid: 0.17382) |
| UUID v7 (random node) | 0.00503 (ramsey/Uuid: 0.16278) |
| UUID v7 (fixed node)** | 0.00154 (ramsey/Uuid: 0.18753) |
| UUID v8 (random node) | 0.00505 (ramsey/Uuid: N/A) |
| UUID v8 (fixed node) | 0.00209 (ramsey/Uuid: 0.16029 _*predefined random node, not usable as signature_) |
| ULID | 0.00506 (robinvdvleuten/php-ulid: 0.00508) |
| TBSL | 0.0034 (library unique) |
| Snowflake | 0.13951 (godruoyi/php-snowflake: 0.14856) |
| Sonyflake | 0.13821 (godruoyi/php-snowflake: 0.14583) |

## Support

Expand Down
8 changes: 8 additions & 0 deletions src/Exceptions/FileLockException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Infocyph\UID\Exceptions;

class FileLockException extends \Exception
{

}
46 changes: 46 additions & 0 deletions src/GetSequence.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Infocyph\UID;

use DateTimeInterface;
use Infocyph\UID\Exceptions\FileLockException;

trait GetSequence
{
private static string $fileLocation;

/**
* Generates a sequence number based on the current time.
*
* @param DateTimeInterface $dateTime The current time.
* @param string $machineId The machine ID.
* @return int The generated sequence number.
* @throws FileLockException
*/
private static function sequence(DateTimeInterface $dateTime, string $machineId, string $type): int
{
self::$fileLocation = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "uid-$type-$machineId.seq";
if (!file_exists(self::$fileLocation)) {
touch(self::$fileLocation);
}
$handle = fopen(self::$fileLocation, "r+");
if (!flock($handle, LOCK_EX)) {
throw new FileLockException('Could not acquire lock on ' . self::$fileLocation);
}
$now = $dateTime->format('Uv');
$line = fgetcsv($handle);
$sequence = 0;
if ($line) {
$sequence = match ($line[0]) {
$now => $line[1],
default => $sequence
};
}
ftruncate($handle, 0);
rewind($handle);
fputcsv($handle, [$now, ++$sequence]);
flock($handle, LOCK_UN);
fclose($handle);
return $sequence;
}
}
47 changes: 6 additions & 41 deletions src/Snowflake.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@
namespace Infocyph\UID;

use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use Infocyph\UID\Exceptions\FileLockException;
use Infocyph\UID\Exceptions\SnowflakeException;

class Snowflake
{
use GetSequence;

private static int $maxTimestampLength = 41;
private static int $maxDatacenterLength = 5;
private static int $maxWorkIdLength = 5;
private static int $maxSequenceLength = 12;
private static ?int $startTime;
private static string $fileLocation;

/**
* Generates a unique snowflake ID.
*
* @param int $datacenter The ID of the datacenter (default: 0)
* @param int $workerId The ID of the worker (default: 0)
* @return string The generated snowflake ID
* @throws SnowflakeException
* @throws SnowflakeException|FileLockException
*/
public static function generate(int $datacenter = 0, int $workerId = 0): string
{
Expand All @@ -41,8 +42,8 @@ public static function generate(int $datacenter = 0, int $workerId = 0): string
$currentTime = (int)$now->format('Uv');
while (($sequence = self::sequence(
$now,
$datacenter,
$workerId
$datacenter . $workerId,
'snowflake'
)) > (-1 ^ (-1 << self::$maxSequenceLength))) {
++$currentTime;
}
Expand Down Expand Up @@ -119,40 +120,4 @@ private static function getStartTimeStamp(): float|int
{
return self::$startTime ??= (strtotime('2020-01-01 00:00:00') * 1000);
}

/**
* Generates a sequence number based on the current time.
*
* @param DateTimeInterface $now The current time.
* @param int $datacenter
* @param int $workerId
* @return int The generated sequence number.
* @throws SnowflakeException
*/
private static function sequence(DateTimeInterface $now, int $datacenter, int $workerId): int
{
self::$fileLocation = sys_get_temp_dir() . DIRECTORY_SEPARATOR .
'uid-snf-' . $datacenter . $workerId . $now->format('Ymd') . '.seq';
if (!file_exists(self::$fileLocation)) {
touch(self::$fileLocation);
}
$handle = fopen(self::$fileLocation, "r+");
if (!flock($handle, LOCK_EX)) {
throw new SnowflakeException('Could not acquire lock on ' . self::$fileLocation);
}
$content = '';
while (!feof($handle)) {
$content .= fread($handle, 1024);
}
$content = json_decode($content, true);
$currentTime = (int)$now->format('Uv');
$content[$currentTime] = ($content[$currentTime] ?? 0) + 1;
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, json_encode($content));
flock($handle, LOCK_UN);
fclose($handle);

return $content[$currentTime];
}
}
44 changes: 5 additions & 39 deletions src/Sonyflake.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@
namespace Infocyph\UID;

use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use Infocyph\UID\Exceptions\FileLockException;
use Infocyph\UID\Exceptions\SonyflakeException;

class Sonyflake
{
use GetSequence;

private static int $maxTimestampLength = 39;
private static int $maxMachineIdLength = 16;
private static int $maxSequenceLength = 8;
private static ?int $startTime;
private static string $fileLocation;

/**
* Generates a unique identifier using the SonyFlake algorithm.
*
* @param int $machineId The machine identifier. Must be between 0 and the maximum machine ID.
* @return string The generated unique identifier.
* @throws SonyflakeException
* @throws SonyflakeException|FileLockException
*/
public static function generate(int $machineId = 0): string
{
Expand All @@ -30,7 +31,7 @@ public static function generate(int $machineId = 0): string
}
$now = new DateTimeImmutable('now');
$elapsedTime = self::elapsedTime();
while (($sequence = self::sequence($now, $machineId)) > (-1 ^ (-1 << self::$maxSequenceLength))) {
while (($sequence = self::sequence($now, $machineId, 'sonyflake')) > (-1 ^ (-1 << self::$maxSequenceLength))) {
$nextMillisecond = self::elapsedTime();
while ($nextMillisecond === $elapsedTime) {
++$nextMillisecond;
Expand Down Expand Up @@ -122,39 +123,4 @@ private static function elapsedTime(): int
{
return floor(((new DateTimeImmutable('now'))->format('Uv') - self::getStartTimeStamp()) / 10) | 0;
}

/**
* Generates a sequence number based on the current time.
*
* @param DateTimeInterface $now The current time.
* @param string $machineId The machine identifier.
* @return int The generated sequence number.
* @throws SonyflakeException
*/
private static function sequence(DateTimeInterface $now, string $machineId): int
{
self::$fileLocation = sys_get_temp_dir() . DIRECTORY_SEPARATOR .
'uid-sof-' . $machineId . $now->format('Ymd') . '.seq';
if (!file_exists(self::$fileLocation)) {
touch(self::$fileLocation);
}
$handle = fopen(self::$fileLocation, "r+");
if (!flock($handle, LOCK_EX)) {
throw new SonyflakeException('Could not acquire lock on ' . self::$fileLocation);
}
$content = '';
while (!feof($handle)) {
$content .= fread($handle, 1024);
}
$content = json_decode($content, true);
$currentTime = (int)$now->format('Uv');
$content[$currentTime] = ($content[$currentTime] ?? 0) + 1;
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, json_encode($content));
flock($handle, LOCK_UN);
fclose($handle);

return $content[$currentTime];
}
}
Loading