diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6537ca4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes index ec91691..84e409a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,13 @@ -tests export-ignore -docs export-ignore -.github export-ignore -.readthedocs.yaml export-ignore -captainhook.json export-ignore -phpunit.xml export-ignore -pint.json export-ignore -rector.php export-ignore +.editorconfig export-ignore +.gitignore export-ignore +tests export-ignore +docs export-ignore +.github export-ignore +.readthedocs.yaml export-ignore +captainhook.json export-ignore +phpunit.xml export-ignore +pint.json export-ignore +rector.php export-ignore +.gitattributes export-ignore + +* text eol=lf diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1161b61..b7445f3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,6 @@ -version: 2 -updates: - - package-ecosystem: "composer" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "weekly" +version: 2 +updates: + - package-ecosystem: "composer" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0fc349d..2274fa9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,42 +1,42 @@ -name: "Security & Standards" - -on: - schedule: - - cron: '0 0 * * 0' - push: - branches: [ '*' ] - pull_request: - branches: [ "main", "master", "develop" ] - -jobs: - run: - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: [ ubuntu-latest ] - php-versions: [ '8.2' , '8.3' ] - dependency-version: [ prefer-lowest, prefer-stable ] - - name: PHP ${{ matrix.php-versions }} - ${{ matrix.operating-system }} - ${{ matrix.dependency-version }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: composer:v2 - coverage: xdebug - - - name: Validate composer.json and composer.lock - run: composer validate --strict - - - name: Install dependencies - run: composer install --no-interaction --prefer-dist --no-progress - - - name: Package Audit - run: composer audit - - - name: Test +name: "Security & Standards" + +on: + schedule: + - cron: '0 0 * * 0' + push: + branches: [ '*' ] + pull_request: + branches: [ "main", "master", "develop" ] + +jobs: + run: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: [ ubuntu-latest ] + php-versions: [ '8.2' , '8.3' ] + dependency-version: [ prefer-lowest, prefer-stable ] + + name: PHP ${{ matrix.php-versions }} - ${{ matrix.operating-system }} - ${{ matrix.dependency-version }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: composer:v2 + coverage: xdebug + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Install dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Package Audit + run: composer audit + + - name: Test run: composer tests \ No newline at end of file diff --git a/.gitignore b/.gitignore index b3a9a9f..f45ef09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -vendor -.idea -composer.lock -git-story_media -test.php +vendor +example +.idea +example.php +test.php +composer.lock +git-story_media +*~ +diffs.txt +index.php diff --git a/LICENSE b/LICENSE index 7cabe02..7ceb1ae 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2024 Infocyph - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2024 Infocyph + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 14cd6e3..b7b4f0f 100644 --- a/README.md +++ b/README.md @@ -1,400 +1,400 @@ -# UID - -[![build](https://github.com/infocyph/UID/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/infocyph/UID/actions/workflows/build.yml) -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/cec4c7ed0e274b3da4571973732a363e)](https://app.codacy.com/gh/infocyph/UID/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) -![Packagist Downloads](https://img.shields.io/packagist/dt/infocyph/uid) -[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) -![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) -[![]()](https://visitor-badge.laobi.icu/badge?page_id=infocyph.com) - -An AIO Unique ID generator written in PHP. Supports (references available at the bottom), - -- UUID -- ULID -- Snowflake ID -- Sonyflake ID -- TBSL -- NanoId -- Cuid2 - -## Table of contents - - - -* [Prerequisites](#prerequisites) -* [Installation](#installation) -* [Usage](#usage) - * [UUID](#uuid-universal-unique-identifier) - * [UUID v1](#uuid-v1-time-based-uuid) - * [UUID v3](#uuid-v3-namespace-based-uuid) - * [UUID v4](#uuid-v4-random-uuid) - * [UUID v5](#uuid-v5-namespace-based-uuid) - * [UUID v6](#uuid-v6-draft-basedunofficial-time-based-uuid) - * [UUID v7](#uuid-v7-draft-basedunofficial-time-based-uuid) - * [UUID v8](#uuid-v8-draft-basedunofficial-time-based-uuid-lexicographically-sortable) - * [GUID](#guid) - * [Additional](#additional) - * [ULID](#ulid-universally-unique-lexicographically-sortable-identifier) - * [Snowflake ID](#snowflake-id) - * [Sonyflake ID](#sonyflake-id) - * [TBSL ID](#tbsl-time-based-keys-with-lexicographic-sorting-library-exclusive) - * [RandomId](#randomid) - * [NanoId](#nanoid-url-friendly-unique-random-id) - * [Cuid2](#cuid2-url-friendly-secure--collision-free) -* [Benchmark](#benchmark) -* [Support](#support) -* [References](#references) - - - -## Prerequisites - -Language: PHP 8/+ - -## Installation - -``` -composer require infocyph/uid -``` - -## Usage - -### UUID (Universal Unique Identifier) - -The node specific UUID's `$node` parameter (1, 6, 7, 8) is optional. If not provided, it will be generated randomly. -But, if you wanna track the source of the UUIDs, you should use it (pre-define the node per server & pass it -accordingly). - -#### UUID v1: Time-based UUID. - -- Generate v1 UUID - -```php -// Get v1 UUID -\Infocyph\UID\UUID::v1(); -// alternatively can also use -\Infocyph\UID\uuid1(); -``` - -- Pass your pre-generated node (for node specific UUID) - -```php -\Infocyph\UID\UUID::v1($node); // check additional section for how to generate one -``` - -#### UUID v3: Namespace based UUID. - -- Generate v3 UUID - -```php -// Get v3 UUID -\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 predefined namespaces (RFC4122 #Appendix C) - -```php -/** -* You can pass X500, URL, OID, DNS (check RFC4122 #Appendix C) -*/ -\Infocyph\UID\UUID::v3('url', 'abmmhasan.github.io'); -``` - -- You can generate a UUID & use as namespace as well - -```php -\Infocyph\UID\UUID::v3('fa1700dd-828c-4d1b-8e6d-a6104807da90', 'abmmhasan.github.io'); -``` - -#### UUID v4: Random UUID. - -- Generate v4 UUID - -```php -// Get v4 UUID (completely random) -\Infocyph\UID\UUID::v4(); -// alternatively can also use -\Infocyph\UID\uuid4(); -``` - -#### UUID v5: Namespace based UUID. - -- Generate v5 UUID - -```php -// Get v5 UUID -\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 predefined namespaces (RFC4122 #Appendix C) - -```php -/** -* You can pass X500, URL, OID, DNS (check RFC4122 #Appendix C) -*/ -\Infocyph\UID\UUID::v5('url', 'abmmhasan.github.io'); -``` - -- You can generate a UUID & use as namespace as well - -```php -\Infocyph\UID\UUID::v5('fa1700dd-828c-4d1b-8e6d-a6104807da90', 'abmmhasan.github.io'); -``` - -#### UUID v6 (draft-based/unofficial): Time-based UUID. - -- Generate v6 UUID - -```php -// Get v6 UUID (Time based) -\Infocyph\UID\UUID::v6(); -// alternatively can also use -\Infocyph\UID\uuid6(); -``` - -- Get v6 UUID using predefined node: - -```php -\Infocyph\UID\UUID::v6($node); // check additional section for how to generate one -``` - -#### UUID v7 (draft-based/unofficial): Time-based UUID. - -- Generate v7 UUID - -```php -// Get v7 UUID for current time -\Infocyph\UID\UUID::v7(); -// alternatively can also use -\Infocyph\UID\uuid7(); -``` - -- Get v7 UUID using predefined node: - -```php -\Infocyph\UID\UUID::v7(null, $node); // check additional section for, how to generate one -``` - -- Or if you wanna get v7 UUID using predefined time: - -```php -$timeInterface = new DateTime(); // DateTime implements DateTimeInterface -\Infocyph\UID\UUID::v7($timeInterface); -``` - -- You can combine both parameters together as well. - -#### UUID v8 (draft-based/unofficial): Time-based UUID. Lexicographically sortable. - -- Generate v8 UUID - -```php -// Get v8 UUID -\Infocyph\UID\UUID::v8(); -// alternatively can also use -\Infocyph\UID\uuid8(); -``` - -- Get v8 UUID using predefined node: - -```php -\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) - -```php -\Infocyph\UID\UUID::getNode(); -``` - -- Parse any UUID string: - -```php -\Infocyph\UID\UUID::parse($uuid); // returns ['isValid', 'version', 'time', 'node'] -``` - - -### ULID (Universally Unique Lexicographically Sortable Identifier) - -- Generating ULID is very simple, - -```php -\Infocyph\UID\ULID::generate(); -``` - -- Or if you wanna get ULID for specific time: - -```php -\Infocyph\UID\ULID::generate(new DateTimeImmutable('2020-01-01 00:00:00')); -``` - -- Extract datetime from any ULID string: - -```php -\Infocyph\UID\ULID::getTime($ulid); // returns DateTimeInterface object -``` - -- Validate any ULID string: - -```php -\Infocyph\UID\ULID::isValid($ulid); // true/false -``` - -### Snowflake ID - -- Generate a new Snowflake ID (You can also pass your pre-generated worker_id & datacenter_id for server/module - detection): - -```php -// Get Snowflake ID -// optionally you can set worker_id & datacenter_id, for server/module detection -\Infocyph\UID\Snowflake::generate(); -// alternatively -\Infocyph\UID\snowflake(); -``` - -- Parse Snowflake ID (get the timestamp, sequence, worker_id, datacenter_id): - -```php -// Parse Snowflake ID -// returns [time => DateTimeInterface object, sequence, worker_id, datacenter_id] -\Infocyph\UID\Snowflake::parse($snowflake); -``` - -- Specify start time for Snowflake ID (a Snowflake ID is unique upto 69 years from the start date): - -```php -// By default, the start time is set to `2020-01-01 00:00:00`, which is changeable -// but if changed, this should always stay same as long as your project lives -// & must call this before any Snowflake call (generate/parse) -\Infocyph\UID\Snowflake::setStartTimeStamp('2000-01-01 00:00:00'); -``` - -### Sonyflake ID - -- Generate a new Sonyflake ID (You can also pass your pre-generated machine_id for server detection): - -```php -// Get Sonyflake ID -// optionally set machine_id, for server detection -\Infocyph\UID\Sonyflake::generate(); -// alternatively -\Infocyph\UID\sonyflake(); -``` - -- Parse Sonyflake ID (get the timestamp, sequence, machine_id): - -```php -// Parse Sonyflake ID -// returns [time => DateTimeInterface object, sequence, machine_id] -\Infocyph\UID\Sonyflake::parse($sonyflake); -``` - -- Specify start time for Sonyflake ID (a Sonyflake ID is unique upto 174 years from the start date): - -```php -// By default, the start time is set to `2020-01-01 00:00:00`, which is changeable -// but if changed, this should always stay same as long as your project lives -// & must call this before any Sonyflake call (generate/parse) -\Infocyph\UID\Sonyflake::setStartTimeStamp('2000-01-01 00:00:00'); -``` - -### TBSL: Time-Based Keys with Lexicographic Sorting (library exclusive) - -- Get TBSL ID (You can also pass your pre-generated machine_id for server detection): - -```php -// Get TBSL ID -// optionally set machine_id, for server detection -\Infocyph\UID\TBSL::generate(); -// alternatively -\Infocyph\UID\tbsl(); -``` - -- Parse TBSL ID (get the timestamp, machine_id): - -```php -// Parse TBSL -// returns [isValid, time => DateTimeInterface object, machine_id] -\Infocyph\UID\TBSL::parse($tbsl); -``` - -### RandomId - -With this you can generate RandomIds. These are great for usage where you don't want a large length/formatted IDs like UUID4. -These IDs are unique & can't be backtracked. - -#### NanoID (URL friendly Unique Random ID) - -- Generate - -```php -// By default, it will generate id of length 21. -// You can pass in desired length -\Infocyph\UID\RandomId::nanoId(); -``` - -#### Cuid2 (URL friendly, secure & collision free) - -- Generate - -```php -// By default, it will generate id of length 24. -// You can pass in desired length in between 4 & 24 -\Infocyph\UID\RandomId::cuid2(); -``` - -## Benchmark - -| 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) | -| Snowflake | 0.13951 (godruoyi/php-snowflake: 0.14856) | -| Sonyflake | 0.13821 (godruoyi/php-snowflake: 0.14583) | -| TBSL | 0.0034 | -| NanoID | 0.00057 | -| Cuid2 | 0.01817 | - -## Support - -Having trouble? Create an issue! - -## References - -- UUID (RFC4122): https://tools.ietf.org/html/rfc4122 -- UUID (Drafts/Proposals): https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis -- ULID: https://github.com/ulid/spec -- Snowflake ID: https://github.com/twitter-archive/snowflake/tree/snowflake-2010 -- Sonyflake ID: https://github.com/sony/sonyflake -- TBSL ID: https://github.com/infocyph/UID/blob/main/TBSL.md -- NanoID: https://github.com/ai/nanoid -- Cuid2: https://github.com/paralleldrive/cuid2 +# UID + +[![build](https://github.com/infocyph/UID/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/infocyph/UID/actions/workflows/build.yml) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/cec4c7ed0e274b3da4571973732a363e)](https://app.codacy.com/gh/infocyph/UID/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) +![Packagist Downloads](https://img.shields.io/packagist/dt/infocyph/uid) +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) +![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) +[![]()](https://visitor-badge.laobi.icu/badge?page_id=infocyph.com) + +An AIO Unique ID generator written in PHP. Supports (references available at the bottom), + +- UUID +- ULID +- Snowflake ID +- Sonyflake ID +- TBSL +- NanoId +- Cuid2 + +## Table of contents + + + +* [Prerequisites](#prerequisites) +* [Installation](#installation) +* [Usage](#usage) + * [UUID](#uuid-universal-unique-identifier) + * [UUID v1](#uuid-v1-time-based-uuid) + * [UUID v3](#uuid-v3-namespace-based-uuid) + * [UUID v4](#uuid-v4-random-uuid) + * [UUID v5](#uuid-v5-namespace-based-uuid) + * [UUID v6](#uuid-v6-draft-basedunofficial-time-based-uuid) + * [UUID v7](#uuid-v7-draft-basedunofficial-time-based-uuid) + * [UUID v8](#uuid-v8-draft-basedunofficial-time-based-uuid-lexicographically-sortable) + * [GUID](#guid) + * [Additional](#additional) + * [ULID](#ulid-universally-unique-lexicographically-sortable-identifier) + * [Snowflake ID](#snowflake-id) + * [Sonyflake ID](#sonyflake-id) + * [TBSL ID](#tbsl-time-based-keys-with-lexicographic-sorting-library-exclusive) + * [RandomId](#randomid) + * [NanoId](#nanoid-url-friendly-unique-random-id) + * [Cuid2](#cuid2-url-friendly-secure--collision-free) +* [Benchmark](#benchmark) +* [Support](#support) +* [References](#references) + + + +## Prerequisites + +Language: PHP 8/+ + +## Installation + +``` +composer require infocyph/uid +``` + +## Usage + +### UUID (Universal Unique Identifier) + +The node specific UUID's `$node` parameter (1, 6, 7, 8) is optional. If not provided, it will be generated randomly. +But, if you wanna track the source of the UUIDs, you should use it (pre-define the node per server & pass it +accordingly). + +#### UUID v1: Time-based UUID. + +- Generate v1 UUID + +```php +// Get v1 UUID +\Infocyph\UID\UUID::v1(); +// alternatively can also use +uuid1(); +``` + +- Pass your pre-generated node (for node specific UUID) + +```php +\Infocyph\UID\UUID::v1($node); // check additional section for how to generate one +``` + +#### UUID v3: Namespace based UUID. + +- Generate v3 UUID + +```php +// Get v3 UUID +\Infocyph\UID\UUID::v3('a pre-generated UUID', 'the string you wanna get UUID for'); +// alternatively can also use +uuid3(); +``` + +- Get v3 UUID for predefined namespaces (RFC4122 #Appendix C) + +```php +/** +* You can pass X500, URL, OID, DNS (check RFC4122 #Appendix C) +*/ +\Infocyph\UID\UUID::v3('url', 'abmmhasan.github.io'); +``` + +- You can generate a UUID & use as namespace as well + +```php +\Infocyph\UID\UUID::v3('fa1700dd-828c-4d1b-8e6d-a6104807da90', 'abmmhasan.github.io'); +``` + +#### UUID v4: Random UUID. + +- Generate v4 UUID + +```php +// Get v4 UUID (completely random) +\Infocyph\UID\UUID::v4(); +// alternatively can also use +uuid4(); +``` + +#### UUID v5: Namespace based UUID. + +- Generate v5 UUID + +```php +// Get v5 UUID +\Infocyph\UID\UUID::v5('a pre-generated UUID', 'the string you wanna get UUID for'); +// alternatively can also use +uuid5(); +``` + +- Get v5 UUID for predefined namespaces (RFC4122 #Appendix C) + +```php +/** +* You can pass X500, URL, OID, DNS (check RFC4122 #Appendix C) +*/ +\Infocyph\UID\UUID::v5('url', 'abmmhasan.github.io'); +``` + +- You can generate a UUID & use as namespace as well + +```php +UUID::v5('fa1700dd-828c-4d1b-8e6d-a6104807da90', 'abmmhasan.github.io'); +``` + +#### UUID v6 (draft-based/unofficial): Time-based UUID. + +- Generate v6 UUID + +```php +// Get v6 UUID (Time based) +\Infocyph\UID\UUID::v6(); +// alternatively can also use +uuid6(); +``` + +- Get v6 UUID using predefined node: + +```php +\Infocyph\UID\UUID::v6($node); // check additional section for how to generate one +``` + +#### UUID v7 (draft-based/unofficial): Time-based UUID. + +- Generate v7 UUID + +```php +// Get v7 UUID for current time +\Infocyph\UID\UUID::v7(); +// alternatively can also use +uuid7(); +``` + +- Get v7 UUID using predefined node: + +```php +\Infocyph\UID\UUID::v7(null, $node); // check additional section for, how to generate one +``` + +- Or if you wanna get v7 UUID using predefined time: + +```php +$timeInterface = new DateTime(); // DateTime implements DateTimeInterface +\Infocyph\UID\UUID::v7($timeInterface); +``` + +- You can combine both parameters together as well. + +#### UUID v8 (draft-based/unofficial): Time-based UUID. Lexicographically sortable. + +- Generate v8 UUID + +```php +// Get v8 UUID +\Infocyph\UID\UUID::v8(); +// alternatively can also use +uuid8(); +``` + +- Get v8 UUID using predefined node: + +```php +\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) + +```php +\Infocyph\UID\UUID::getNode(); +``` + +- Parse any UUID string: + +```php +\Infocyph\UID\UUID::parse($uuid); // returns ['isValid', 'version', 'time', 'node'] +``` + + +### ULID (Universally Unique Lexicographically Sortable Identifier) + +- Generating ULID is very simple, + +```php +\Infocyph\UID\ULID::generate(); +``` + +- Or if you wanna get ULID for specific time: + +```php +\Infocyph\UID\ULID::generate(new DateTimeImmutable('2020-01-01 00:00:00')); +``` + +- Extract datetime from any ULID string: + +```php +\Infocyph\UID\ULID::getTime($ulid); // returns DateTimeInterface object +``` + +- Validate any ULID string: + +```php +\Infocyph\UID\ULID::isValid($ulid); // true/false +``` + +### Snowflake ID + +- Generate a new Snowflake ID (You can also pass your pre-generated worker_id & datacenter_id for server/module + detection): + +```php +// Get Snowflake ID +// optionally you can set worker_id & datacenter_id, for server/module detection +\Infocyph\UID\Snowflake::generate(); +// alternatively +snowflake(); +``` + +- Parse Snowflake ID (get the timestamp, sequence, worker_id, datacenter_id): + +```php +// Parse Snowflake ID +// returns [time => DateTimeInterface object, sequence, worker_id, datacenter_id] +\Infocyph\UID\Snowflake::parse($snowflake); +``` + +- Specify start time for Snowflake ID (a Snowflake ID is unique upto 69 years from the start date): + +```php +// By default, the start time is set to `2020-01-01 00:00:00`, which is changeable +// but if changed, this should always stay same as long as your project lives +// & must call this before any Snowflake call (generate/parse) +\Infocyph\UID\Snowflake::setStartTimeStamp('2000-01-01 00:00:00'); +``` + +### Sonyflake ID + +- Generate a new Sonyflake ID (You can also pass your pre-generated machine_id for server detection): + +```php +// Get Sonyflake ID +// optionally set machine_id, for server detection +\Infocyph\UID\Sonyflake::generate(); +// alternatively +\Infocyph\UID\sonyflake(); +``` + +- Parse Sonyflake ID (get the timestamp, sequence, machine_id): + +```php +// Parse Sonyflake ID +// returns [time => DateTimeInterface object, sequence, machine_id] +\Infocyph\UID\Sonyflake::parse($sonyflake); +``` + +- Specify start time for Sonyflake ID (a Sonyflake ID is unique upto 174 years from the start date): + +```php +// By default, the start time is set to `2020-01-01 00:00:00`, which is changeable +// but if changed, this should always stay same as long as your project lives +// & must call this before any Sonyflake call (generate/parse) +\Infocyph\UID\Sonyflake::setStartTimeStamp('2000-01-01 00:00:00'); +``` + +### TBSL: Time-Based Keys with Lexicographic Sorting (library exclusive) + +- Get TBSL ID (You can also pass your pre-generated machine_id for server detection): + +```php +// Get TBSL ID +// optionally set machine_id, for server detection +\Infocyph\UID\TBSL::generate(); +// alternatively +tbsl(); +``` + +- Parse TBSL ID (get the timestamp, machine_id): + +```php +// Parse TBSL +// returns [isValid, time => DateTimeInterface object, machine_id] +\Infocyph\UID\TBSL::parse($tbsl); +``` + +### RandomId + +With this you can generate RandomIds. These are great for usage where you don't want a large length/formatted IDs like UUID4. +These IDs are unique & can't be backtracked. + +#### NanoID (URL friendly Unique Random ID) + +- Generate + +```php +// By default, it will generate id of length 21. +// You can pass in desired length +\Infocyph\UID\RandomId::nanoId(); +``` + +#### Cuid2 (URL friendly, secure & collision free) + +- Generate + +```php +// By default, it will generate id of length 24. +// You can pass in desired length in between 4 & 24 +\Infocyph\UID\RandomId::cuid2(); +``` + +## Benchmark + +| 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) | +| Snowflake | 0.13951 (godruoyi/php-snowflake: 0.14856) | +| Sonyflake | 0.13821 (godruoyi/php-snowflake: 0.14583) | +| TBSL | 0.0034 | +| NanoID | 0.00057 | +| Cuid2 | 0.01817 | + +## Support + +Having trouble? Create an issue! + +## References + +- UUID (RFC4122): https://tools.ietf.org/html/rfc4122 +- UUID (Drafts/Proposals): https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis +- ULID: https://github.com/ulid/spec +- Snowflake ID: https://github.com/twitter-archive/snowflake/tree/snowflake-2010 +- Sonyflake ID: https://github.com/sony/sonyflake +- TBSL ID: https://github.com/infocyph/UID/blob/main/TBSL.md +- NanoID: https://github.com/ai/nanoid +- Cuid2: https://github.com/paralleldrive/cuid2 diff --git a/SECURITY.md b/SECURITY.md index f9b2b64..56d709e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,14 +1,14 @@ -# Security Policy - -![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/infocyph/uid) - -## Supported Versions - -| Version | Supported | -|---------|--------------------| -| 2.x | :white_check_mark: | -| 1.x | :x: | - -## Reporting a Vulnerability - -Report any vulnerabilities to the [security advisory tracker](https://github.com/infocyph/uid/issues)! +# Security Policy + +![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/infocyph/uid) + +## Supported Versions + +| Version | Supported | +|---------|--------------------| +| 2.x | :white_check_mark: | +| 1.x | :x: | + +## Reporting a Vulnerability + +Report any vulnerabilities to the [security advisory tracker](https://github.com/infocyph/uid/issues)! diff --git a/TBSL.md b/TBSL.md index 66a2d18..5a194f1 100644 --- a/TBSL.md +++ b/TBSL.md @@ -1,9 +1,9 @@ -# TBSL - -A 20 character alphanumeric ID generated by a time-based lexicographically sortable algorithm. It stores time in first -13 characters and machine ID in 14th-15th characters. Rest 5 is randomly generated. - -| Time Identifier | Machine Identifier | Random Character | -|:---------------:|:------------------:|:----------------:| -| 13 | 2 | 5 | - +# TBSL + +A 20 character alphanumeric ID generated by a time-based lexicographically sortable algorithm. It stores time in first +13 characters and machine ID in 14th-15th characters. Rest 5 is randomly generated. + +| Time Identifier | Machine Identifier | Random Character (can be sequenced) | +|:---------------:|:------------------:|:-----------------------------------:| +| 13 | 2 | 5 | + diff --git a/composer.json b/composer.json index d21dc55..5219cbf 100644 --- a/composer.json +++ b/composer.json @@ -1,77 +1,77 @@ -{ - "name": "infocyph/uid", - "description": "UUID (RFC 4122 + Unofficial/Draft), ULID, Snowflake ID, Sonyflake ID, TBSL (library exclusive) generator!", - "type": "library", - "license": "MIT", - "keywords": [ - "uuid", - "ulid", - "snowflake", - "sonyflake", - "tbsl", - "id", - "key", - "unique", - "rfc4122" - ], - "authors": [ - { - "name": "abmmhasan", - "email": "abmmhasan@gmail.com" - } - ], - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Infocyph\\UID\\": "src/" - } - }, - "minimum-stability": "stable", - "prefer-stable": true, - "require": { - "php": ">=8.0", - "ext-bcmath": "*" - }, - "config": { - "sort-packages": true, - "optimize-autoloader": true, - "allow-plugins": { - "pestphp/pest-plugin": true - } - }, - "replace": { - "abmmhasan/uuid": "*" - }, - "require-dev": { - "captainhook/captainhook": "^5.23", - "laravel/pint": "^1.16", - "pestphp/pest": "^2.34", - "rector/rector": "^1.1", - "symfony/var-dumper": "^7.0" - }, - "scripts": { - "test:code": "pest --parallel --processes=10", - "test:refactor": "rector process --dry-run", - "test:lint": "pint --test", - "test:hook": [ - "captainhook hook:post-checkout", - "captainhook hook:pre-commit", - "captainhook hook:post-commit", - "captainhook hook:post-merge", - "captainhook hook:post-rewrite", - "captainhook hook:pre-push" - ], - "tests": [ - "@test:code", - "@test:lint", - "@test:refactor" - ], - "git:hook": "captainhook install --only-enabled -nf", - "test": "pest", - "refactor": "rector process", - "lint": "pint", - "post-autoload-dump": "@git:hook" - } -} +{ + "name": "infocyph/uid", + "description": "UUID (RFC 4122 + Unofficial/Draft), ULID, Snowflake ID, Sonyflake ID, TBSL (library exclusive) generator!", + "type": "library", + "license": "MIT", + "keywords": [ + "uuid", + "ulid", + "snowflake", + "sonyflake", + "tbsl", + "id", + "key", + "unique", + "rfc4122" + ], + "authors": [ + { + "name": "abmmhasan", + "email": "abmmhasan@gmail.com" + } + ], + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Infocyph\\UID\\": "src/" + } + }, + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": ">=8.0", + "ext-bcmath": "*" + }, + "config": { + "sort-packages": true, + "optimize-autoloader": true, + "allow-plugins": { + "pestphp/pest-plugin": true + } + }, + "replace": { + "abmmhasan/uuid": "*" + }, + "require-dev": { + "captainhook/captainhook": "^5.24", + "laravel/pint": "^1.20", + "pestphp/pest": "^3.7", + "rector/rector": "^2.0", + "symfony/var-dumper": "^7.2" + }, + "scripts": { + "test:code": "pest --parallel --processes=10", + "test:refactor": "rector process --dry-run", + "test:lint": "pint --test", + "test:hook": [ + "captainhook hook:post-checkout", + "captainhook hook:pre-commit", + "captainhook hook:post-commit", + "captainhook hook:post-merge", + "captainhook hook:post-rewrite", + "captainhook hook:pre-push" + ], + "tests": [ + "@test:code", + "@test:lint", + "@test:refactor" + ], + "git:hook": "captainhook install --only-enabled -nf", + "test": "pest", + "refactor": "rector process", + "lint": "pint", + "post-autoload-dump": "@git:hook" + } +} diff --git a/pint.json b/pint.json index cd88583..e44f7b4 100644 --- a/pint.json +++ b/pint.json @@ -1,10 +1,10 @@ -{ - "preset": "psr12", - "exclude": [ - "tests" - ], - "notPath": [ - "rector.php", - "test.php" - ] +{ + "preset": "psr12", + "exclude": [ + "tests" + ], + "notPath": [ + "rector.php", + "test.php" + ] } \ No newline at end of file diff --git a/rector.php b/rector.php index 4586bff..310abde 100644 --- a/rector.php +++ b/rector.php @@ -1,19 +1,16 @@ -paths([ - __DIR__ . '/src' - ]); - $rectorConfig->sets([ - constant("Rector\Set\ValueObject\LevelSetList::UP_TO_PHP_82") - ]); - $rectorConfig->skip([ - StringClassNameToClassConstantRector::class - ]); -}; +paths([ + __DIR__ . '/src' + ]); + $rectorConfig->sets([ + constant("Rector\Set\ValueObject\LevelSetList::UP_TO_PHP_84") + ]); +}; diff --git a/src/GetSequence.php b/src/GetSequence.php index 20a7c3d..b79da56 100644 --- a/src/GetSequence.php +++ b/src/GetSequence.php @@ -2,41 +2,114 @@ namespace Infocyph\UID; -use Infocyph\UID\Exceptions\FileLockException; +use RuntimeException; trait GetSequence { private static string $fileLocation; + private static int $waitTime = 100; + private static int $lockTimeout = 1000; + private static int $maxAttempts = 10; /** * Generates a sequence number based on the current time. * * @param int $dateTime The current time. * @param string $machineId The machine ID. - * @return int The generated sequence number. - * @throws FileLockException + * @param string $type The type identifier. + * @param int $maxSequence The maximum sequence number. + * @return int The generated sequence number, or `0` if lock was not acquired. */ - private static function sequence(int $dateTime, string $machineId, string $type): int + private static function sequence(int $dateTime, string $machineId, string $type, int $maxSequence = 0): int { self::$fileLocation ??= sys_get_temp_dir() . DIRECTORY_SEPARATOR . "uid-$type-$machineId.seq"; - if (!file_exists(self::$fileLocation)) { - touch(self::$fileLocation); + + // Attempt to acquire a lock + $handle = self::acquireLock(); + if (!$handle) { + return getmypid() % ($maxSequence + 1); } - $handle = fopen(self::$fileLocation, "r+"); - flock($handle, LOCK_EX) || throw new FileLockException('Could not acquire lock on ' . self::$fileLocation); - $line = fgetcsv($handle); + + // Update sequence + $sequence = self::updateSequence($handle, $dateTime); + + // Unlock and close + flock($handle, LOCK_UN); + fclose($handle); + + return $sequence; + } + + /** + * Acquires an exclusive lock on the sequence file. + * If a stale lock is detected, it resets the lock. + * + * @return resource|false The file handle if successful, or false if unable to acquire the lock. + */ + private static function acquireLock() + { + ($handle = fopen(self::$fileLocation, "c+")) || throw new RuntimeException( + 'Failed to open file: ' . self::$fileLocation, + ); + + $lockStartTime = microtime(true); + $attempts = 0; + + // Attempt to acquire lock with retry mechanism + while (!flock($handle, LOCK_EX | LOCK_NB)) { + usleep(self::$waitTime); + + // If lock is held too long, assume stale lock and reset + if ((microtime(true) - $lockStartTime) * 1_000_000 >= self::$lockTimeout) { + fclose($handle); + ($handle = fopen(self::$fileLocation, "c+")) || throw new RuntimeException( + 'Failed to reopen file after stale lock reset: ' . self::$fileLocation, + ); + + // Acquire exclusive lock after reopening + flock($handle, LOCK_EX); + return $handle; + } + + // If max attempts are reached, return false + if (++$attempts >= self::$maxAttempts) { + fclose($handle); + return false; + } + } + + return $handle; + } + + /** + * Reads the current sequence from the file, increments it, and writes it back. + * + * @param resource $handle The file handle. + * @param int $dateTime The timestamp for sequence tracking. + * @return int The updated sequence number. + */ + private static function updateSequence($handle, int $dateTime): int + { $sequence = 0; - if ($line && ($line[0] = (int)$line[0]) <= $dateTime) { - $sequence = match ($line[0]) { - $dateTime => $line[1], - default => $sequence - }; + $line = stream_get_contents($handle); + + if ($line !== false && trim($line) !== '') { + [$lastTimestamp, $lastSequence] = explode(',', trim($line)); + $lastTimestamp = (int) $lastTimestamp; + + if ($lastTimestamp === $dateTime) { + $sequence = (int) $lastSequence; + } } - ftruncate($handle, 0); + + // Increment sequence + $sequence++; + + // Move pointer to the beginning and write updated values rewind($handle); - fputcsv($handle, [$dateTime, ++$sequence]); - flock($handle, LOCK_UN); - fclose($handle); + fwrite($handle, "$dateTime,$sequence"); + ftruncate($handle, ftell($handle)); + return $sequence; } } diff --git a/src/Snowflake.php b/src/Snowflake.php index 3ed82ec..feefa31 100644 --- a/src/Snowflake.php +++ b/src/Snowflake.php @@ -42,7 +42,8 @@ public static function generate(int $datacenter = 0, int $workerId = 0): string while (($sequence = self::sequence( $currentTime, $datacenter . $workerId, - 'snowflake' + 'snowflake', + self::$maxSequenceLength )) > (-1 ^ (-1 << self::$maxSequenceLength))) { ++$currentTime; } diff --git a/src/Sonyflake.php b/src/Sonyflake.php index f998fd1..9a8814b 100644 --- a/src/Sonyflake.php +++ b/src/Sonyflake.php @@ -31,7 +31,12 @@ public static function generate(int $machineId = 0): string } $now = (int)(new DateTimeImmutable('now'))->format('Uv'); $elapsedTime = self::elapsedTime(); - while (($sequence = self::sequence($now, $machineId, 'sonyflake')) > (-1 ^ (-1 << self::$maxSequenceLength))) { + while (($sequence = self::sequence( + $now, + $machineId, + 'sonyflake', + self::$maxSequenceLength + )) > (-1 ^ (-1 << self::$maxSequenceLength))) { $nextMillisecond = self::elapsedTime(); while ($nextMillisecond === $elapsedTime) { ++$nextMillisecond; @@ -63,7 +68,7 @@ public static function parse(string $id): array '@' . $time[0] . '.' - . str_pad($time[1], 6, '0', STR_PAD_LEFT) + . str_pad($time[1], 6, '0', STR_PAD_LEFT), ), 'sequence' => bindec(substr($id, -1 * self::$maxSequenceLength)), 'machine_id' => bindec(substr($id, -1 * $length, self::$maxMachineIdLength)), diff --git a/src/TBSL.php b/src/TBSL.php index c871602..70c50d4 100644 --- a/src/TBSL.php +++ b/src/TBSL.php @@ -7,32 +7,56 @@ final class TBSL { + use GetSequence; + + private static int $maxSequenceLength = 10; + /** * Generates a unique identifier using the TBSL algorithm. * - * @param int $machineId 2-digit (0-99) machine identifier. Default is 0. + * @param int $machineId 2-digit (0-99) machine identifier. Default is 0. + * @param bool $sequenced Whether to use sequencing. * @return string The generated unique identifier. * @throws Exception */ - public static function generate(int $machineId = 0): string + public static function generate(int $machineId = 0, bool $sequenced = false): string + { + // Get current microsecond timestamp + [$micro, $seconds] = explode(' ', microtime()); + $timeSequence = $seconds . substr($micro, 2, 6); + + // Convert timeSequence + machineId to base16 + $storeData = base_convert($timeSequence . sprintf('%02d', $machineId), 10, 16); + + return strtoupper(sprintf( + '%015s%05s', + $storeData, + substr(self::sequencedGenerate($machineId, $sequenced, (int)$timeSequence), -1, 5) + )); + } + + /** + * Generates a sequence or random bytes based on the sequencing flag. + * + * @param int $machineId Machine identifier. + * @param bool $enableSequence Whether to enable sequence. + * @param int $timeSequence The timestamp sequence. + * @return string Hexadecimal sequence. + * @throws Exception + */ + private static function sequencedGenerate(int $machineId, bool $enableSequence, int $timeSequence): string { - $timestamp = microtime(); - $storeData = base_convert( - substr($timestamp, 11) . - substr($timestamp, 2, 6) . - sprintf('%02d', $machineId), - 10, - 16 - ); - - return strtoupper(sprintf('%015s%05s', $storeData, substr(bin2hex(random_bytes(3)), 0, 5))); + return match ($enableSequence) { + true => dechex(self::sequence($timeSequence, $machineId, 'tbsl', self::$maxSequenceLength)), + default => bin2hex(random_bytes(3)), + }; } /** - * Parses a TBSL string and returns an array with information about the TBSL. + * Parses a TBSL string and returns an array with its components. * * @param string $tbsl The TBSL string to parse. - * @return array ['isValid', 'time', 'machineId'] + * @return array ['isValid' => bool, 'time' => DateTimeImmutable|null, 'machineId' => int|null] * @throws Exception */ public static function parse(string $tbsl): array @@ -40,7 +64,7 @@ public static function parse(string $tbsl): array $data = [ 'isValid' => preg_match('/^[0-9A-F]{20}$/', $tbsl), 'time' => null, - 'machineId' => null + 'machineId' => null, ]; if (!$data['isValid']) { @@ -53,5 +77,4 @@ public static function parse(string $tbsl): array return $data; } - } diff --git a/src/functions.php b/src/functions.php index 76b3cae..94a768b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,14 +1,15 @@ each->not()->toBeUsed(); -}); - -test('No echo statements', function () { - expect(['echo', 'print'])->each->not()->toBeUsed(); -}); +each->not()->toBeUsed(); +}); + +test('No echo statements', function () { + expect(['echo', 'print'])->each->not()->toBeUsed(); +}); diff --git a/tests/RandomIdTest.php b/tests/RandomIdTest.php index da7a856..0182862 100644 --- a/tests/RandomIdTest.php +++ b/tests/RandomIdTest.php @@ -1,13 +1,13 @@ -toBeString()->not()->toBeEmpty(); -}); - -test('nanoId', function () { - $string = RandomId::nanoId(); - expect($string)->toBeString()->not()->toBeEmpty()->toHaveLength(21); -}); +toBeString()->not()->toBeEmpty(); +}); + +test('nanoId', function () { + $string = RandomId::nanoId(); + expect($string)->toBeString()->not()->toBeEmpty()->toHaveLength(21); +}); diff --git a/tests/SnowflakeTest.php b/tests/SnowflakeTest.php index 619dd85..20b848e 100644 --- a/tests/SnowflakeTest.php +++ b/tests/SnowflakeTest.php @@ -1,12 +1,12 @@ -getTimestamp())->toBeBetween(time() - 1, time()) - ->and($parsed['worker_id'])->toBe(0) - ->and($parsed['datacenter_id'])->toBe(0); -}); - +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 de41874..ede1091 100644 --- a/tests/SonyflakeTest.php +++ b/tests/SonyflakeTest.php @@ -1,11 +1,11 @@ -getTimestamp())->toBeBetween(time() - 1, time()) - ->and($parsed['machine_id'])->toBe(0); -}); - +getTimestamp())->toBeBetween(time() - 1, time()) + ->and($parsed['machine_id'])->toBe(0); +}); + diff --git a/tests/TBSLTest.php b/tests/TBSLTest.php index 53497f4..d5aeb1f 100644 --- a/tests/TBSLTest.php +++ b/tests/TBSLTest.php @@ -1,11 +1,11 @@ -getTimestamp())->toBeBetween(time() - 1, time()) - ->and($parsed['machineId'])->toBe('00'); -}); - +getTimestamp())->toBeBetween(time() - 1, time()) + ->and($parsed['machineId'])->toBe('00'); +}); + diff --git a/tests/ULIDTest.php b/tests/ULIDTest.php index a01857c..e39bc35 100644 --- a/tests/ULIDTest.php +++ b/tests/ULIDTest.php @@ -1,10 +1,10 @@ -toBeString() - ->and(ULID::isValid($ulid))->toBeTrue() - ->and(ULID::getTime($ulid)->getTimestamp())->toBeBetween(time() - 1, time()); -}); +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 3e344cc..e560bd8 100644 --- a/tests/UUIDTest.php +++ b/tests/UUIDTest.php @@ -1,90 +1,90 @@ -toBeString(); - $parsed = UUID::parse($uid); - expect($parsed['isValid'])->toBeTrue() - ->and($parsed['version'])->toBe(1) - ->and($parsed['time'])->not()->toBeNull() - ->and($parsed['node'])->toBeString()->not()->toBeNull() - ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); -}); - -$ns = UUID::v4(); - -test('UUID v3', function () use ($ns) { - $uid = UUID::v3($ns, 'my-string'); - expect($uid)->toBeString(); - $parsed = UUID::parse($uid); - expect($parsed['isValid'])->toBeTrue() - ->and($parsed['version'])->toBe(3) - ->and($parsed['time'])->toBeNull() - ->and($parsed['node'])->toBeString()->not()->toBeNull(); -}); - -test('UUID v4', function () { - $uid = UUID::v4(); - 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(); -}); - -test('UUID v5', function () use ($ns) { - $uid = UUID::v5($ns, 'my-string'); - expect($uid)->toBeString(); - $parsed = UUID::parse($uid); - expect($parsed['isValid'])->toBeTrue() - ->and($parsed['version'])->toBe(5) - ->and($parsed['time'])->toBeNull() - ->and($parsed['node'])->toBeString()->not()->toBeNull(); -}); - -test('UUID v6', function () { - $uid = UUID::v6(); - expect($uid)->toBeString(); - $parsed = UUID::parse($uid); - expect($parsed['isValid'])->toBeTrue() - ->and($parsed['version'])->toBe(6) - ->and($parsed['time'])->not()->toBeNull() - ->and($parsed['node'])->toBeString()->not()->toBeNull() - ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); -}); - -test('UUID v7', function () { - $uid = UUID::v7(); - expect($uid)->toBeString(); - $parsed = UUID::parse($uid); - expect($parsed['isValid'])->toBeTrue() - ->and($parsed['version'])->toBe(7) - ->and($parsed['time'])->not()->toBeNull() - ->and($parsed['node'])->toBeString()->not()->toBeNull() - ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); -}); - -test('UUID v8', function () { - $uid = UUID::v8(); - expect($uid)->toBeString(); - $parsed = UUID::parse($uid); - expect($parsed['isValid'])->toBeTrue() - ->and($parsed['version'])->toBe(8) - ->and($parsed['time'])->not()->toBeNull() - ->and($parsed['node'])->toBeString()->not()->toBeNull() - ->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(); -}); - +toBeString(); + $parsed = UUID::parse($uid); + expect($parsed['isValid'])->toBeTrue() + ->and($parsed['version'])->toBe(1) + ->and($parsed['time'])->not()->toBeNull() + ->and($parsed['node'])->toBeString()->not()->toBeNull() + ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); +}); + +$ns = UUID::v4(); + +test('UUID v3', function () use ($ns) { + $uid = UUID::v3($ns, 'my-string'); + expect($uid)->toBeString(); + $parsed = UUID::parse($uid); + expect($parsed['isValid'])->toBeTrue() + ->and($parsed['version'])->toBe(3) + ->and($parsed['time'])->toBeNull() + ->and($parsed['node'])->toBeString()->not()->toBeNull(); +}); + +test('UUID v4', function () { + $uid = UUID::v4(); + 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(); +}); + +test('UUID v5', function () use ($ns) { + $uid = UUID::v5($ns, 'my-string'); + expect($uid)->toBeString(); + $parsed = UUID::parse($uid); + expect($parsed['isValid'])->toBeTrue() + ->and($parsed['version'])->toBe(5) + ->and($parsed['time'])->toBeNull() + ->and($parsed['node'])->toBeString()->not()->toBeNull(); +}); + +test('UUID v6', function () { + $uid = UUID::v6(); + expect($uid)->toBeString(); + $parsed = UUID::parse($uid); + expect($parsed['isValid'])->toBeTrue() + ->and($parsed['version'])->toBe(6) + ->and($parsed['time'])->not()->toBeNull() + ->and($parsed['node'])->toBeString()->not()->toBeNull() + ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); +}); + +test('UUID v7', function () { + $uid = UUID::v7(); + expect($uid)->toBeString(); + $parsed = UUID::parse($uid); + expect($parsed['isValid'])->toBeTrue() + ->and($parsed['version'])->toBe(7) + ->and($parsed['time'])->not()->toBeNull() + ->and($parsed['node'])->toBeString()->not()->toBeNull() + ->and($parsed['time']->getTimestamp())->toBeBetween(time() - 1, time()); +}); + +test('UUID v8', function () { + $uid = UUID::v8(); + expect($uid)->toBeString(); + $parsed = UUID::parse($uid); + expect($parsed['isValid'])->toBeTrue() + ->and($parsed['version'])->toBe(8) + ->and($parsed['time'])->not()->toBeNull() + ->and($parsed['node'])->toBeString()->not()->toBeNull() + ->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(); +}); +