From 75d3207dd9fcb956a6830a5a888c2c27f4d344f5 Mon Sep 17 00:00:00 2001 From: abmmhasan Date: Wed, 5 Jun 2024 23:06:23 +0600 Subject: [PATCH] Fix+GUID --- README.md | 13 +++++++++++-- rector.php | 4 ++++ src/GetSequence.php | 4 +--- src/ULID.php | 36 ++++++++++++++++++------------------ src/UUID.php | 34 ++++++++++++++++++++++++++++++++-- src/functions.php | 24 +++++++++++++++++++----- tests/SnowflakeTest.php | 2 +- tests/SonyflakeTest.php | 2 +- tests/TBSLTest.php | 2 +- tests/ULIDTest.php | 6 +++--- tests/UUIDTest.php | 18 ++++++++++++++---- 11 files changed, 105 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 2403149..a262846 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ ![Packagist Version](https://img.shields.io/packagist/v/infocyph/uid) ![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/infocyph/uid) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/infocyph/uid) +![visitors](https://visitor-badge.laobi.icu/badge?page_id=infocyph.com) An AIO Unique ID generator written in PHP. Supports, - UUID (RFC 4122 + Unofficial/Draft) @@ -172,6 +173,14 @@ $timeInterface = new DateTime(); // DateTime implements DateTimeInterface \Infocyph\UID\UUID::v8($node); // check additional section for, how to generate one ``` +#### GUID + +GUID generator, works in all platform. Generate: +```php +\Infocyph\UID\UUID::guid() +``` +_Note: Sending false in only parameter will return the string enclosed with Braces_ + #### Additional - Generate node for further use (with version: 1, 6, 7, 8) @@ -212,7 +221,7 @@ $timeInterface = new DateTime(); // DateTime implements DateTimeInterface // alternatively \Infocyph\UID\snowflake(); ``` -- Parse Snowflake ID (get the timestamp, sequence, worker_id, datacenter_id from any Snowflake ID): +- Parse Snowflake ID (get the timestamp, sequence, worker_id, datacenter_id): ```php // Parse Snowflake ID // returns [time => DateTimeInterface object, sequence, worker_id, datacenter_id] @@ -237,7 +246,7 @@ $timeInterface = new DateTime(); // DateTime implements DateTimeInterface // alternatively \Infocyph\UID\sonyflake(); ``` -- Parse Sonyflake ID (get the timestamp, sequence, machine_id from any Snowflake ID): +- Parse Sonyflake ID (get the timestamp, sequence, machine_id): ```php // Parse Sonyflake ID diff --git a/rector.php b/rector.php index baf0a8c..c0719af 100644 --- a/rector.php +++ b/rector.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Rector\Config\RectorConfig; +use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; return static function (RectorConfig $rectorConfig): void { @@ -12,4 +13,7 @@ $rectorConfig->sets([ constant("Rector\Set\ValueObject\LevelSetList::UP_TO_PHP_82") ]); + $rectorConfig->skip([ + StringClassNameToClassConstantRector::class + ]); }; diff --git a/src/GetSequence.php b/src/GetSequence.php index 52a07b3..20a7c3d 100644 --- a/src/GetSequence.php +++ b/src/GetSequence.php @@ -23,9 +23,7 @@ private static function sequence(int $dateTime, string $machineId, string $type) touch(self::$fileLocation); } $handle = fopen(self::$fileLocation, "r+"); - if (!flock($handle, LOCK_EX)) { - throw new FileLockException('Could not acquire lock on ' . self::$fileLocation); - } + flock($handle, LOCK_EX) || throw new FileLockException('Could not acquire lock on ' . self::$fileLocation); $line = fgetcsv($handle); $sequence = 0; if ($line && ($line[0] = (int)$line[0]) <= $dateTime) { diff --git a/src/ULID.php b/src/ULID.php index 7ea693b..5d3d375 100644 --- a/src/ULID.php +++ b/src/ULID.php @@ -28,31 +28,31 @@ public static function generate(?DateTimeInterface $dateTime = null): string { $time = (int)($dateTime ?? new DateTimeImmutable('now'))->format('Uv'); - $isDuplicate = $time === static::$lastGenTime; - static::$lastGenTime = $time; + $isDuplicate = $time === self::$lastGenTime; + self::$lastGenTime = $time; // Generate time characters $timeChars = ''; - for ($i = static::$timeLength - 1; $i >= 0; $i--) { - $mod = $time % static::$encodingLength; - $timeChars = static::$encodingChars[$mod] . $timeChars; - $time = ($time - $mod) / static::$encodingLength; + for ($i = self::$timeLength - 1; $i >= 0; $i--) { + $mod = $time % self::$encodingLength; + $timeChars = self::$encodingChars[$mod] . $timeChars; + $time = ($time - $mod) / self::$encodingLength; } // Generate random characters $randChars = ''; if (!$isDuplicate) { - for ($i = 0; $i < static::$randomLength; $i++) { - static::$lastRandChars[$i] = random_int(0, 31); + for ($i = 0; $i < self::$randomLength; $i++) { + self::$lastRandChars[$i] = random_int(0, 31); } } else { - for ($i = static::$randomLength - 1; $i >= 0 && static::$lastRandChars[$i] === 31; $i--) { - static::$lastRandChars[$i] = 0; + for ($i = self::$randomLength - 1; $i >= 0 && self::$lastRandChars[$i] === 31; $i--) { + self::$lastRandChars[$i] = 0; } - static::$lastRandChars[$i]++; + self::$lastRandChars[$i]++; } - for ($i = 0; $i < static::$randomLength; $i++) { - $randChars .= static::$encodingChars[static::$lastRandChars[$i]]; + for ($i = 0; $i < self::$randomLength; $i++) { + $randChars .= self::$encodingChars[self::$lastRandChars[$i]]; } return $timeChars . $randChars; @@ -67,19 +67,19 @@ public static function generate(?DateTimeInterface $dateTime = null): string */ public static function getTime(string $ulid): DateTimeImmutable { - if (!static::isValid($ulid)) { + if (!self::isValid($ulid)) { throw new ULIDException('Invalid ULID string'); } - $timeChars = str_split(strrev(substr($ulid, 0, static::$timeLength))); + $timeChars = str_split(strrev(substr($ulid, 0, self::$timeLength))); $time = 0; foreach ($timeChars as $index => $char) { - $encodingIndex = strripos(static::$encodingChars, $char); - $time += ($encodingIndex * static::$encodingLength ** $index); + $encodingIndex = strripos(self::$encodingChars, $char); + $time += ($encodingIndex * self::$encodingLength ** $index); } - $time = str_split($time, static::$timeLength); + $time = str_split($time, self::$timeLength); if ($time[0] > (time() + (86400 * 365 * 10))) { throw new ULIDException('Invalid ULID string: timestamp too large'); diff --git a/src/UUID.php b/src/UUID.php index ef054ea..f79436c 100644 --- a/src/UUID.php +++ b/src/UUID.php @@ -172,6 +172,27 @@ public static function v8(string $node = null): string return self::output(8, $string); } + /** + * Generates a GUID (Globally Unique Identifier) string. + * + * @param bool $trim Whether to trim the curly braces from the GUID string. Default is true. + * @return string The generated GUID string. + * @throws Exception + */ + public static function guid(bool $trim = true): string + { + if (function_exists('com_create_guid') === true) { + $data = com_create_guid(); + return $trim ? trim($data, '{}') : $data; + } + + $data = random_bytes(16); + $data[6] = chr(ord($data[6]) & 0x0f | 0x40); + $data[8] = chr(ord($data[8]) & 0x3f | 0x80); + $data = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); + return $trim ? $data : "\{$data\}"; + } + /** * Generate unique node. * @@ -192,9 +213,11 @@ public static function getNode(): string */ public static function parse(string $uuid): array { + $uuid = trim($uuid, '{}'); $data = [ 'isValid' => self::isValid($uuid), 'version' => null, + 'variant' => null, 'time' => null, 'node' => null ]; @@ -204,10 +227,17 @@ public static function parse(string $uuid): array } $uuidData = explode('-', $uuid); + $variantN = hexdec($uuidData[3][0]); $data['version'] = (int)$uuidData[2][0]; $data['time'] = in_array($data['version'], [1, 6, 7, 8]) ? self::getTime($uuidData, $data['version']) : null; $data['node'] = $uuidData[4]; - + $data['variant'] = match (true) { + $variantN <= 7 => 'NCS', + $variantN >= 8 && $variantN <= 11 => 'DCE 1.1, ISO/IEC 11578:1996', + $variantN === 12 || $variantN === 13 => 'Microsoft GUID', + $variantN === 14 => 'Reserved', + default => 'Unknown' + }; return $data; } @@ -235,7 +265,7 @@ private static function prepareNode(int $version, string $node = null): string */ private static function isValid(string $uuid): bool { - return (bool)preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-\d[0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $uuid); + return (bool)preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-\d[0-9a-f]{3}-[089a-e][0-9a-f]{3}-[0-9a-f]{12}$/i', $uuid); } /** diff --git a/src/functions.php b/src/functions.php index ba2bcc2..76b3cae 100644 --- a/src/functions.php +++ b/src/functions.php @@ -108,7 +108,21 @@ function uuid8(string $node = null): string } } -if (!function_exists(\Infocyph\UID\ulid::class)) { +if (!function_exists('Infocyph\UID\guid')) { + /** + * Generates a GUID (Globally Unique Identifier) string. + * + * @param bool $trim Whether to trim the curly braces from the GUID string. Default is true. + * @return string The generated GUID string. + * @throws Exception + */ + function guid(bool $trim = true): string + { + return UUID::guid($trim); + } +} + +if (!function_exists('Infocyph\UID\ulid')) { /** * Generates ULID. * @@ -122,7 +136,7 @@ function ulid(?DateTimeInterface $dateTime = null): string } } -if (!function_exists(\Infocyph\UID\snowflake::class)) { +if (!function_exists('Infocyph\UID\snowflake')) { /** * Generates Snowflake ID. * @@ -137,7 +151,7 @@ function snowflake(int $datacenter = 0, int $workerId = 0): string } } -if (!function_exists(\Infocyph\UID\TBSL::class)) { +if (!function_exists('Infocyph\UID\sonyflake')) { /** * Generates Sonyflake ID. * @@ -147,11 +161,11 @@ function snowflake(int $datacenter = 0, int $workerId = 0): string */ function sonyflake(int $machineId = 0): string { - return TBSL::generate($machineId); + return Sonyflake::generate($machineId); } } -if (!function_exists(\Infocyph\UID\tbsl::class)) { +if (!function_exists('Infocyph\UID\tbsl')) { /** * Generates TBSL ID. * diff --git a/tests/SnowflakeTest.php b/tests/SnowflakeTest.php index 3dfe24d..619dd85 100644 --- a/tests/SnowflakeTest.php +++ b/tests/SnowflakeTest.php @@ -5,7 +5,7 @@ test('Basic', function () { $sf = Snowflake::generate(); $parsed = Snowflake::parse($sf); - expect($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time() + 1) + expect($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()) ->and($parsed['worker_id'])->toBe(0) ->and($parsed['datacenter_id'])->toBe(0); }); diff --git a/tests/SonyflakeTest.php b/tests/SonyflakeTest.php index 4ed6b88..de41874 100644 --- a/tests/SonyflakeTest.php +++ b/tests/SonyflakeTest.php @@ -5,7 +5,7 @@ test('Basic', function () { $sf = Sonyflake::generate(); $parsed = Sonyflake::parse($sf); - expect($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time() + 1) + expect($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()) ->and($parsed['machine_id'])->toBe(0); }); diff --git a/tests/TBSLTest.php b/tests/TBSLTest.php index df43b19..53497f4 100644 --- a/tests/TBSLTest.php +++ b/tests/TBSLTest.php @@ -5,7 +5,7 @@ test('Basic', function () { $sf = TBSL::generate(); $parsed = TBSL::parse($sf); - expect($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time() + 1) + expect($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()) ->and($parsed['machineId'])->toBe('00'); }); diff --git a/tests/ULIDTest.php b/tests/ULIDTest.php index 99df658..a01857c 100644 --- a/tests/ULIDTest.php +++ b/tests/ULIDTest.php @@ -4,7 +4,7 @@ test('Basic', function () { $ulid = ULID::generate(); - expect($ulid)->toBeString(); - expect(ULID::isValid($ulid))->toBeTrue() - ->and(ULID::getTime($ulid)->getTimestamp())->toBeBetween(time() - 1, time() + 1); + expect($ulid)->toBeString() + ->and(ULID::isValid($ulid))->toBeTrue() + ->and(ULID::getTime($ulid)->getTimestamp())->toBeBetween(time() - 1, time()); }); diff --git a/tests/UUIDTest.php b/tests/UUIDTest.php index c72b82c..3e344cc 100644 --- a/tests/UUIDTest.php +++ b/tests/UUIDTest.php @@ -10,7 +10,7 @@ ->and($parsed['version'])->toBe(1) ->and($parsed['time'])->not()->toBeNull() ->and($parsed['node'])->toBeString()->not()->toBeNull() - ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time() + 1); + ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); }); $ns = UUID::v4(); @@ -53,7 +53,7 @@ ->and($parsed['version'])->toBe(6) ->and($parsed['time'])->not()->toBeNull() ->and($parsed['node'])->toBeString()->not()->toBeNull() - ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time() + 1); + ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); }); test('UUID v7', function () { @@ -64,7 +64,7 @@ ->and($parsed['version'])->toBe(7) ->and($parsed['time'])->not()->toBeNull() ->and($parsed['node'])->toBeString()->not()->toBeNull() - ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time() + 1); + ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); }); test('UUID v8', function () { @@ -75,6 +75,16 @@ ->and($parsed['version'])->toBe(8) ->and($parsed['time'])->not()->toBeNull() ->and($parsed['node'])->toBeString()->not()->toBeNull() - ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time() + 1); + ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); +}); + +test('GUID', function () { + $uid = UUID::guid(); + expect($uid)->toBeString(); + $parsed = UUID::parse($uid); + expect($parsed['isValid'])->toBeTrue() + ->and($parsed['version'])->toBe(4) + ->and($parsed['time'])->toBeNull() + ->and($parsed['node'])->toBeString()->not()->toBeNull(); });