Skip to content

Commit

Permalink
Merge pull request #4 from infocyph/feature/update
Browse files Browse the repository at this point in the history
Feature/update
  • Loading branch information
abmmhasan authored May 3, 2024
2 parents bc6810c + d82cdc4 commit 2e48a5f
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 120 deletions.
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

0 comments on commit 2e48a5f

Please sign in to comment.