diff --git a/README.md b/README.md index d6c358b..9bfaa47 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Cycle ORM Entity Behavior Identifier -[![Latest Stable Version](https://poser.pugx.org/cycle/entity-behavior-Identifier/version)](https://packagist.org/packages/cycle/entity-behavior-identifier) +[![Latest Stable Version](https://poser.pugx.org/cycle/entity-behavior-identifier/version)](https://packagist.org/packages/cycle/entity-behavior-identifier) [![Build Status](https://github.com/cycle/entity-behavior-identifier/workflows/build/badge.svg)](https://github.com/cycle/entity-behavior-identifier/actions) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/cycle/entity-behavior-identifier/badges/quality-score.png?b=1.x)](https://scrutinizer-ci.com/g/cycle/entity-behavior-identifier/?branch=1.x) [![Codecov](https://codecov.io/gh/cycle/entity-behavior-identifier/graph/badge.svg)](https://codecov.io/gh/cycle/entity-behavior) @@ -19,13 +18,100 @@ composer require cycle/entity-behavior-identifier ## Snowflake Examples -**Snowflake:** A distributed ID generation system developed by Twitter that produces 64-bit unique, sortable identifiers. Each ID encodes a timestamp, machine ID, and sequence number, enabling high-throughput, ordered ID creation suitable for large-scale distributed applications. +### Generic +A flexible Snowflake format that can use a node identifier and any epoch offset, suitable for various applications requiring unique identifiers. Default values for `node` and `epochOffset` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeGeneric::setDefaults()` method. -> **Note:** Support for Snowflake identifiers will arrive soon, stay tuned. +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Snowflake; + +#[Entity] +#[Identifier\SnowflakeGeneric(field: 'id', node: 1, epochOffset: 1738265600000)] +class User +{ + #[Column(type: 'snowflake', primary: true)] + private Snowflake $id; +} +``` + +### Discord +Snowflake identifier for Discord's platform (voice, text, video), starting from epoch `2015-01-01`. Can incorporate a worker and process ID's to generate distinct Snowflakes. Default values for `workerId` and `processId` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeDiscord::setDefaults()` method. + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Snowflake; + +#[Entity] +#[Identifier\SnowflakeDiscord(field: 'id', workerId: 12, processId: 24)] +class User +{ + #[Column(type: 'snowflake', primary: true)] + private Snowflake $id; +} +``` + +### Instagram +Snowflake identifier for Instagram's photo and video sharing platform, with an epoch starting at `2011-08-24`. Can incorporate a shard ID to generate distinct Snowflakes. Default values for `shardId` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeInstagram::setDefaults()` method. + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Snowflake; + +#[Entity] +#[Identifier\SnowflakeInstagram(field: 'id', shardId: 16)] +class User +{ + #[Column(type: 'snowflake', primary: true)] + private Snowflake $id; +} +``` + +### Mastodon +Snowflake identifier for Mastodon's decentralized social network, generated within a database to ensure uniqueness and approximate order within 1ms. Can include a table name for distinct sequences per table; IDs are unique on a single database but not guaranteed across multiple machines. Default values for `tableName` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeMastodon::setDefaults()` method. + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Snowflake; + +#[Entity] +#[Identifier\SnowflakeMastodon(field: 'id', tableName: 'users')] +class User +{ + #[Column(type: 'snowflake', primary: true)] + private Snowflake $id; +} +``` + +### Twitter +Snowflake identifier for Twitter (X), beginning from `2010-11-04`. Can incorporate a machine ID to generate distinct Snowflakes. Default values for `machineId` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeTwitter::setDefaults()` method. + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Snowflake; + +#[Entity] +#[Identifier\SnowflakeTwitter(field: 'id', machineId: 30)] +class User +{ + #[Column(type: 'snowflake', primary: true)] + private Snowflake $id; +} +``` ## ULID Examples -**ULID (Universally Unique Lexicographically Sortable Identifier):** A 128-bit identifier designed for high uniqueness and lexicographical sortability. It combines a timestamp component with random data, allowing for ordered IDs that can be generated rapidly and are human-readable, making it ideal for databases and distributed systems. +### ULID (Universally Unique Lexicographically Sortable Identifier) +A 128-bit identifier designed for high uniqueness and lexicographical sortability. It combines a timestamp component with random data, allowing for ordered IDs that can be generated rapidly and are human-readable, making it ideal for databases and distributed systems. ```php use Cycle\Annotated\Annotation\Column; @@ -44,7 +130,8 @@ class User ## UUID Examples -**UUID Version 1 (Time-based):** Generated using the current timestamp and the MAC address of the computer, ensuring unique identification based on time and hardware. +### UUID Version 1 (Time-based) +Generated using the current timestamp and the MAC address of the computer, ensuring unique identification based on time and hardware. Default values for `node` and `clockSeq` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\Uuid1::setDefaults()` method. ```php use Cycle\Annotated\Annotation\Column; @@ -61,7 +148,8 @@ class User } ``` -**UUID Version 2 (DCE Security):** Similar to version 1 but includes a local identifier such as a user ID or group ID, primarily used in DCE security contexts. +### UUID Version 2 (DCE Security) +Similar to version 1 but includes a local identifier such as a user ID or group ID, primarily used in DCE security contexts. Default values for `localDomain`, `localIdentifier`, `node` and `clockSeq` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\Uuid2::setDefaults()` method. ```php use Cycle\Annotated\Annotation\Column; @@ -78,7 +166,8 @@ class User } ``` -**UUID Version 3 (Name-based, MD5):** Created by hashing a namespace identifier and name using MD5, resulting in a deterministic UUID based on input data. +### UUID Version 3 (Name-based, MD5) +Created by hashing a namespace identifier and name using MD5, resulting in a deterministic UUID based on input data. ```php use Cycle\Annotated\Annotation\Column; @@ -99,7 +188,8 @@ class User } ``` -**UUID Version 4 (Random):** Generated entirely from random or pseudo-random numbers, offering high unpredictability and uniqueness. +### UUID Version 4 (Random) +Generated entirely from random or pseudo-random numbers, offering high unpredictability and uniqueness. ```php use Cycle\Annotated\Annotation\Column; @@ -116,7 +206,8 @@ class User } ``` -**UUID Version 5 (Name-based, SHA-1):** Similar to version 3 but uses SHA-1 hashing, providing a different deterministic UUID based on namespace and name. +### UUID Version 5 (Name-based, SHA-1) +Similar to version 3 but uses SHA-1 hashing, providing a different deterministic UUID based on namespace and name. ```php use Cycle\Annotated\Annotation\Column; @@ -137,7 +228,8 @@ class User } ``` -**UUID Version 6 (Draft/Upcoming):** An experimental or proposed version focused on improving time-based UUIDs with more sortable properties (not yet widely adopted). +### UUID Version 6 (Draft/Upcoming) +An experimental or proposed version focused on improving time-based UUIDs with more sortable properties (not yet widely adopted). Default values for `node` and `clockSeq` can be defined globally via the `\Cycle\ORM\Entity\Behavior\Identifier\Listener\Uuid6::setDefaults()` method. ```php use Cycle\Annotated\Annotation\Column; @@ -154,7 +246,8 @@ class User } ``` -**UUID Version 7 (Draft/Upcoming):** A newer proposal designed to incorporate sortable features based on Unix timestamp, enhancing performance in database indexing. +### UUID Version 7 (Draft/Upcoming) +A newer proposal designed to incorporate sortable features based on Unix timestamp, enhancing performance in database indexing. ```php use Cycle\Annotated\Annotation\Column; @@ -171,7 +264,33 @@ class User } ``` -You can find more information about Entity behavior UUID [here](https://cycle-orm.dev/docs/entity-behaviors-identifier). +## Global Configuration + +Some listener classes provide static functions allowing you to define global default values for various attributes. This approach helps you to: + +* Initialize defaults at a suitable point in your application's lifecycle. +* Customize defaults dynamically based on environment-specific conditions. +* Minimize redundancy by setting shared attribute values once, instead of repeatedly specifying them across entities. + +**Sample code:** + +```php +use Cycle\ORM\Entity\Behavior\Identifier\Listener; + +Listener\SnowflakeGeneric::setDefaults($node, $epochOffset); +Listener\SnowflakeDiscord::setDefaults($workerId, $processId); +Listener\SnowflakeInstagram::setDefaults($shardId); +Listener\SnowflakeMastodon::setDefaults($tableName); +Listener\SnowflakeTwitter::setDefaults($machineId); + +Listener\Uuid1::setDefaults($node, $clockSeq); +Listener\Uuid2::setDefaults($localDomain, $localIdentifier, $node, $clockSeq); +Listener\Uuid3::setDefaults($namespace, $name); +Listener\Uuid5::setDefaults($namespace, $name); +Listener\Uuid6::setDefaults($node, $clockSeq); +```` + +You can find more information about Entity behavior Identifier [here](https://cycle-orm.dev/docs/entity-behaviors-identifier). ## License: diff --git a/composer.json b/composer.json index 748ed8c..09c2f78 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ }, "require": { "php": ">=8.2", - "cycle/entity-behavior": "^1.6", + "cycle/entity-behavior": "^1.7", "ramsey/identifier": "^0.1" }, "require-dev": { diff --git a/composer.lock b/composer.lock index dffb64d..7771c10 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "06cc0fca7390f2687878ee8869f101ea", + "content-hash": "1218be50127a6d1fb899d3ee50820934", "packages": [ { "name": "brick/math", @@ -68,16 +68,16 @@ }, { "name": "cycle/database", - "version": "2.14.0", + "version": "2.15.0", "source": { "type": "git", "url": "https://github.com/cycle/database.git", - "reference": "876fbc2bc0d068f047388c0bd9b354e4d891af07" + "reference": "3d7ee3524b299c5897e2b03dc51bad2ddd609a90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/database/zipball/876fbc2bc0d068f047388c0bd9b354e4d891af07", - "reference": "876fbc2bc0d068f047388c0bd9b354e4d891af07", + "url": "https://api.github.com/repos/cycle/database/zipball/3d7ee3524b299c5897e2b03dc51bad2ddd609a90", + "reference": "3d7ee3524b299c5897e2b03dc51bad2ddd609a90", "shasum": "" }, "require": { @@ -157,20 +157,20 @@ "type": "github" } ], - "time": "2025-07-14T11:36:41+00:00" + "time": "2025-07-22T05:27:52+00:00" }, { "name": "cycle/entity-behavior", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/cycle/entity-behavior.git", - "reference": "49b0c71485855f16193b0720d637d822d65f688c" + "reference": "0c8d84fb3eaa50ec426f336a158d62ad2b4a83b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/entity-behavior/zipball/49b0c71485855f16193b0720d637d822d65f688c", - "reference": "49b0c71485855f16193b0720d637d822d65f688c", + "url": "https://api.github.com/repos/cycle/entity-behavior/zipball/0c8d84fb3eaa50ec426f336a158d62ad2b4a83b6", + "reference": "0c8d84fb3eaa50ec426f336a158d62ad2b4a83b6", "shasum": "" }, "require": { @@ -232,20 +232,20 @@ "type": "github" } ], - "time": "2025-07-14T19:37:04+00:00" + "time": "2025-07-22T05:27:05+00:00" }, { "name": "cycle/orm", - "version": "v2.10.1", + "version": "v2.11.0", "source": { "type": "git", "url": "https://github.com/cycle/orm.git", - "reference": "0b659067c00c3ffbee05109fa17812754acc2525" + "reference": "d712c79eab82a2393863c67c15e37b89fd64b555" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/orm/zipball/0b659067c00c3ffbee05109fa17812754acc2525", - "reference": "0b659067c00c3ffbee05109fa17812754acc2525", + "url": "https://api.github.com/repos/cycle/orm/zipball/d712c79eab82a2393863c67c15e37b89fd64b555", + "reference": "d712c79eab82a2393863c67c15e37b89fd64b555", "shasum": "" }, "require": { @@ -319,7 +319,7 @@ "type": "github" } ], - "time": "2025-03-31T19:41:17+00:00" + "time": "2025-09-09T09:42:43+00:00" }, { "name": "cycle/schema-builder", @@ -1232,16 +1232,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", - "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", "shasum": "" }, "require": { @@ -1288,7 +1288,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" }, "funding": [ { @@ -1299,12 +1299,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-07-08T02:45:35+00:00" }, { "name": "yiisoft/friendly-exception", @@ -1440,16 +1444,16 @@ "packages-dev": [ { "name": "amphp/amp", - "version": "v3.1.0", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9" + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", - "reference": "7cf7fef3d667bfe4b2560bc87e67d5387a7bcde9", + "url": "https://api.github.com/repos/amphp/amp/zipball/fa0ab33a6f47a82929c38d03ca47ebb71086a93f", + "reference": "fa0ab33a6f47a82929c38d03ca47ebb71086a93f", "shasum": "" }, "require": { @@ -1509,7 +1513,7 @@ ], "support": { "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v3.1.0" + "source": "https://github.com/amphp/amp/tree/v3.1.1" }, "funding": [ { @@ -1517,7 +1521,7 @@ "type": "github" } ], - "time": "2025-01-26T16:07:39+00:00" + "time": "2025-08-27T21:42:00+00:00" }, { "name": "amphp/byte-stream", @@ -1750,16 +1754,16 @@ }, { "name": "amphp/parallel", - "version": "v2.3.1", + "version": "v2.3.2", "source": { "type": "git", "url": "https://github.com/amphp/parallel.git", - "reference": "5113111de02796a782f5d90767455e7391cca190" + "reference": "321b45ae771d9c33a068186b24117e3cd1c48dce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/parallel/zipball/5113111de02796a782f5d90767455e7391cca190", - "reference": "5113111de02796a782f5d90767455e7391cca190", + "url": "https://api.github.com/repos/amphp/parallel/zipball/321b45ae771d9c33a068186b24117e3cd1c48dce", + "reference": "321b45ae771d9c33a068186b24117e3cd1c48dce", "shasum": "" }, "require": { @@ -1822,7 +1826,7 @@ ], "support": { "issues": "https://github.com/amphp/parallel/issues", - "source": "https://github.com/amphp/parallel/tree/v2.3.1" + "source": "https://github.com/amphp/parallel/tree/v2.3.2" }, "funding": [ { @@ -1830,7 +1834,7 @@ "type": "github" } ], - "time": "2024-12-21T01:56:09+00:00" + "time": "2025-08-27T21:55:40+00:00" }, { "name": "amphp/parser", @@ -2246,70 +2250,6 @@ ], "time": "2024-08-03T19:31:26+00:00" }, - { - "name": "clue/ndjson-react", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/clue/reactphp-ndjson.git", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/stream": "^1.2" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Clue\\React\\NDJson\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - } - ], - "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", - "homepage": "https://github.com/clue/reactphp-ndjson", - "keywords": [ - "NDJSON", - "json", - "jsonlines", - "newline", - "reactphp", - "streaming" - ], - "support": { - "issues": "https://github.com/clue/reactphp-ndjson/issues", - "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" - }, - "funding": [ - { - "url": "https://clue.engineering/support", - "type": "custom" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2022-12-23T10:58:28+00:00" - }, { "name": "composer/pcre", "version": "3.3.2", @@ -2391,16 +2331,16 @@ }, { "name": "composer/semver", - "version": "3.4.3", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", "shasum": "" }, "require": { @@ -2452,7 +2392,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" + "source": "https://github.com/composer/semver/tree/3.4.4" }, "funding": [ { @@ -2462,13 +2402,9 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2024-09-19T14:15:21+00:00" + "time": "2025-08-20T19:15:30+00:00" }, { "name": "composer/xdebug-handler", @@ -2538,19 +2474,20 @@ }, { "name": "cycle/annotated", - "version": "v4.3.0", + "version": "v4.3.1", "source": { "type": "git", "url": "https://github.com/cycle/annotated.git", - "reference": "35890d8fe16b6a7a29cbacef5715d31b13b78212" + "reference": "f996d3ee0c22aa8f2c03dca5d693408f8b7fdbbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/annotated/zipball/35890d8fe16b6a7a29cbacef5715d31b13b78212", - "reference": "35890d8fe16b6a7a29cbacef5715d31b13b78212", + "url": "https://api.github.com/repos/cycle/annotated/zipball/f996d3ee0c22aa8f2c03dca5d693408f8b7fdbbe", + "reference": "f996d3ee0c22aa8f2c03dca5d693408f8b7fdbbe", "shasum": "" }, "require": { + "cycle/database": "^2.15", "cycle/orm": "^2.9.2", "cycle/schema-builder": "^2.11.1", "doctrine/inflector": "^2.0", @@ -2607,7 +2544,7 @@ "type": "github" } ], - "time": "2025-05-14T14:48:40+00:00" + "time": "2025-07-22T06:19:06+00:00" }, { "name": "danog/advanced-json-rpc", @@ -2816,6 +2753,7 @@ "issues": "https://github.com/doctrine/annotations/issues", "source": "https://github.com/doctrine/annotations/tree/2.0.2" }, + "abandoned": true, "time": "2024-09-05T10:17:24+00:00" }, { @@ -2868,33 +2806,32 @@ }, { "name": "doctrine/inflector", - "version": "2.0.10", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", - "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11.0", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.3", - "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25 || ^5.4" + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + "Doctrine\\Inflector\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2939,7 +2876,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.10" + "source": "https://github.com/doctrine/inflector/tree/2.1.0" }, "funding": [ { @@ -2955,7 +2892,7 @@ "type": "tidelift" } ], - "time": "2024-02-18T20:23:39+00:00" + "time": "2025-08-10T19:31:58+00:00" }, { "name": "doctrine/lexer", @@ -3034,53 +2971,6 @@ ], "time": "2024-02-05T11:56:58+00:00" }, - { - "name": "evenement/evenement", - "version": "v3.0.2", - "source": { - "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", - "shasum": "" - }, - "require": { - "php": ">=7.0" - }, - "require-dev": { - "phpunit/phpunit": "^9 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Evenement\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - } - ], - "description": "Événement is a very simple event dispatching library for PHP", - "keywords": [ - "event-dispatcher", - "event-emitter" - ], - "support": { - "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/v3.0.2" - }, - "time": "2023-08-08T05:53:35+00:00" - }, { "name": "felixfbecker/language-server-protocol", "version": "v1.5.3", @@ -3139,16 +3029,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", "shasum": "" }, "require": { @@ -3158,10 +3048,10 @@ "fidry/makefile": "^0.2.0", "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, @@ -3188,7 +3078,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" }, "funding": [ { @@ -3196,112 +3086,7 @@ "type": "github" } ], - "time": "2024-08-06T10:04:20+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.83.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "b83916e79a6386a1ec43fdd72391aeb13b63282f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b83916e79a6386a1ec43fdd72391aeb13b63282f", - "reference": "b83916e79a6386a1ec43fdd72391aeb13b63282f", - "shasum": "" - }, - "require": { - "clue/ndjson-react": "^1.0", - "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.5", - "ext-filter": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.2", - "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.6", - "react/event-loop": "^1.0", - "react/promise": "^2.11 || ^3.0", - "react/socket": "^1.0", - "react/stream": "^1.0", - "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", - "symfony/console": "^5.4.45 || ^6.4.13 || ^7.0", - "symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0", - "symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0", - "symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0", - "symfony/options-resolver": "^5.4.45 || ^6.4.16 || ^7.0", - "symfony/polyfill-mbstring": "^1.32", - "symfony/polyfill-php80": "^1.32", - "symfony/polyfill-php81": "^1.32", - "symfony/process": "^5.4.47 || ^6.4.20 || ^7.2", - "symfony/stopwatch": "^5.4.45 || ^6.4.19 || ^7.0" - }, - "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.6", - "infection/infection": "^0.29.14", - "justinrainbow/json-schema": "^5.3 || ^6.4", - "keradus/cli-executor": "^2.2", - "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.8", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", - "phpunit/phpunit": "^9.6.23 || ^10.5.47 || ^11.5.25", - "symfony/polyfill-php84": "^1.32", - "symfony/var-dumper": "^5.4.48 || ^6.4.23 || ^7.3.1", - "symfony/yaml": "^5.4.45 || ^6.4.23 || ^7.3.1" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, - "exclude-from-classmap": [ - "src/Fixer/Internal/*" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "keywords": [ - "Static code analysis", - "fixer", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.83.0" - }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2025-07-14T15:41:41+00:00" + "time": "2025-08-14T07:29:31+00:00" }, { "name": "kelunik/certificate", @@ -3537,16 +3322,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.3", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", - "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -3585,7 +3370,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -3593,7 +3378,7 @@ "type": "tidelift" } ], - "time": "2025-07-05T12:25:42+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "netresearch/jsonmapper", @@ -3648,16 +3433,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.5.0", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", - "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { @@ -3676,7 +3461,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -3700,9 +3485,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2025-05-31T08:24:38+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "phar-io/manifest", @@ -3822,6 +3607,58 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-cs-fixer/shim", + "version": "v3.89.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "69182624902af6b2adafb30e2a092f0e6dd89fab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/69182624902af6b2adafb30e2a092f0e6dd89fab", + "reference": "69182624902af6b2adafb30e2a092f0e6dd89fab", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "replace": { + "friendsofphp/php-cs-fixer": "self.version" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer", + "php-cs-fixer.phar" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/PHP-CS-Fixer/shim/issues", + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.89.1" + }, + "time": "2025-10-24T12:05:38+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -3877,16 +3714,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.2", + "version": "5.6.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62" + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/92dde6a5919e34835c506ac8c523ef095a95ed62", - "reference": "92dde6a5919e34835c506ac8c523ef095a95ed62", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94f8051919d1b0369a6bcc7931d679a511c03fe9", + "reference": "94f8051919d1b0369a6bcc7931d679a511c03fe9", "shasum": "" }, "require": { @@ -3935,9 +3772,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.2" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.3" }, - "time": "2025-04-13T19:20:35+00:00" + "time": "2025-08-01T19:43:32+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -3999,16 +3836,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8" + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8", - "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495", + "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495", "shasum": "" }, "require": { @@ -4040,9 +3877,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0" }, - "time": "2025-07-13T07:04:09+00:00" + "time": "2025-08-30T15:50:23+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4365,16 +4202,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.23", + "version": "9.6.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", - "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", "shasum": "" }, "require": { @@ -4385,7 +4222,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -4396,11 +4233,11 @@ "phpunit/php-timer": "^5.0.3", "sebastian/cli-parser": "^1.0.2", "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.8", + "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", - "sebastian/exporter": "^4.0.6", - "sebastian/global-state": "^5.0.7", + "sebastian/exporter": "^4.0.8", + "sebastian/global-state": "^5.0.8", "sebastian/object-enumerator": "^4.0.4", "sebastian/resource-operations": "^3.0.4", "sebastian/type": "^3.2.1", @@ -4448,7 +4285,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" }, "funding": [ { @@ -4472,7 +4309,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T06:40:34+00:00" + "time": "2025-09-24T06:29:11+00:00" }, { "name": "psr/cache", @@ -4632,30 +4469,37 @@ "time": "2023-04-04T09:54:51+00:00" }, { - "name": "react/cache", - "version": "v1.2.0", + "name": "revolt/event-loop", + "version": "v1.0.7", "source": { "type": "git", - "url": "https://github.com/reactphp/cache.git", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", + "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", "shasum": "" }, "require": { - "php": ">=5.3.0", - "react/promise": "^3.0 || ^2.0 || ^1.1" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, "autoload": { "psr-4": { - "React\\Cache\\": "src/" + "Revolt\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4664,605 +4508,72 @@ ], "authors": [ { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" }, { "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" + "email": "ceesjank@gmail.com" }, { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" + "name": "Christian Lück", + "email": "christian@clue.engineering" }, { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "Async, Promise-based cache interface for ReactPHP", + "description": "Rock-solid event loop for concurrent PHP applications.", "keywords": [ - "cache", - "caching", - "promise", - "reactphp" + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" ], "support": { - "issues": "https://github.com/reactphp/cache/issues", - "source": "https://github.com/reactphp/cache/tree/v1.2.0" + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2022-11-30T15:59:55+00:00" + "time": "2025-01-25T19:27:39+00:00" }, { - "name": "react/child-process", - "version": "v0.6.6", + "name": "sebastian/cli-parser", + "version": "1.0.2", "source": { "type": "git", - "url": "https://github.com/reactphp/child-process.git", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", "shasum": "" }, "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/event-loop": "^1.2", - "react/stream": "^1.4" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/socket": "^1.16", - "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + "phpunit/phpunit": "^9.3" }, "type": "library", - "autoload": { - "psr-4": { - "React\\ChildProcess\\": "src/" + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven library for executing child processes with ReactPHP.", - "keywords": [ - "event-driven", - "process", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.6" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-01-01T16:37:48+00:00" - }, - { - "name": "react/dns", - "version": "v1.13.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/cache": "^1.0 || ^0.6 || ^0.5", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.7 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3 || ^2", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Dns\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async DNS resolver for ReactPHP", - "keywords": [ - "async", - "dns", - "dns-resolver", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.13.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-13T14:18:03+00:00" - }, - { - "name": "react/event-loop", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "suggest": { - "ext-pcntl": "For signal handling support when using the StreamSelectLoop" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\EventLoop\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", - "keywords": [ - "asynchronous", - "event-loop" - ], - "support": { - "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2023-11-13T13:48:05+00:00" - }, - { - "name": "react/promise", - "version": "v3.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", - "shasum": "" - }, - "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-05-24T10:39:05+00:00" - }, - { - "name": "react/socket", - "version": "v1.16.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/dns": "^1.13", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.6 || ^1.2.1", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3.3 || ^2", - "react/promise-stream": "^1.4", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Socket\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", - "keywords": [ - "Connection", - "Socket", - "async", - "reactphp", - "stream" - ], - "support": { - "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.16.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-07-26T10:38:09+00:00" - }, - { - "name": "react/stream", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.8", - "react/event-loop": "^1.2" - }, - "require-dev": { - "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", - "keywords": [ - "event-driven", - "io", - "non-blocking", - "pipe", - "reactphp", - "readable", - "stream", - "writable" - ], - "support": { - "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.4.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-11T12:45:25+00:00" - }, - { - "name": "revolt/event-loop", - "version": "v1.0.7", - "source": { - "type": "git", - "url": "https://github.com/revoltphp/event-loop.git", - "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/09bf1bf7f7f574453efe43044b06fafe12216eb3", - "reference": "09bf1bf7f7f574453efe43044b06fafe12216eb3", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^9", - "psalm/phar": "^5.15" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Revolt\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "ceesjank@gmail.com" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "Rock-solid event loop for concurrent PHP applications.", - "keywords": [ - "async", - "asynchronous", - "concurrency", - "event", - "event-loop", - "non-blocking", - "scheduler" - ], - "support": { - "issues": "https://github.com/revoltphp/event-loop/issues", - "source": "https://github.com/revoltphp/event-loop/tree/v1.0.7" - }, - "time": "2025-01-25T19:27:39+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" + "BSD-3-Clause" ], "authors": [ { @@ -5398,16 +4709,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "4.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", "shasum": "" }, "require": { @@ -5460,15 +4771,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2025-08-10T06:51:50+00:00" }, { "name": "sebastian/complexity", @@ -5658,16 +4981,16 @@ }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/14c6ba52f95a36c3d27c835d65efc7123c446e8c", + "reference": "14c6ba52f95a36c3d27c835d65efc7123c446e8c", "shasum": "" }, "require": { @@ -5723,28 +5046,40 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2025-09-24T06:03:27+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.7", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b6781316bdcd28260904e7cc18ec983d0d2ef4f6", + "reference": "b6781316bdcd28260904e7cc18ec983d0d2ef4f6", "shasum": "" }, "require": { @@ -5787,15 +5122,27 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2024-03-02T06:35:11+00:00" + "time": "2025-08-10T07:10:35+00:00" }, { "name": "sebastian/lines-of-code", @@ -5968,16 +5315,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/539c6691e0623af6dc6f9c20384c120f963465a0", + "reference": "539c6691e0623af6dc6f9c20384c120f963465a0", "shasum": "" }, "require": { @@ -6019,15 +5366,27 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.6" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2025-08-10T06:57:39+00:00" }, { "name": "sebastian/resource-operations", @@ -6344,21 +5703,21 @@ }, { "name": "spiral/code-style", - "version": "v2.2.2", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/spiral/code-style.git", - "reference": "3803c38baf6cda714e9ebbc7e515622b22ea798d" + "reference": "a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spiral/code-style/zipball/3803c38baf6cda714e9ebbc7e515622b22ea798d", - "reference": "3803c38baf6cda714e9ebbc7e515622b22ea798d", + "url": "https://api.github.com/repos/spiral/code-style/zipball/a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72", + "reference": "a0407ffcb300c9d2977cb3d1c36af0d13b2ffb72", "shasum": "" }, "require": { - "friendsofphp/php-cs-fixer": "^3.64", - "php": ">=8.0" + "php": ">=8.0", + "php-cs-fixer/shim": "^3.64" }, "require-dev": { "phpunit/phpunit": "^10.5", @@ -6388,7 +5747,7 @@ "description": "Code style and static analysis tools rulesets collection", "homepage": "https://github.com/spiral/code-style", "support": { - "source": "https://github.com/spiral/code-style/tree/v2.2.2" + "source": "https://github.com/spiral/code-style/tree/v2.3.1" }, "funding": [ { @@ -6396,7 +5755,7 @@ "type": "github" } ], - "time": "2025-01-24T07:31:21+00:00" + "time": "2025-10-06T17:24:48+00:00" }, { "name": "spiral/logger", @@ -6547,16 +5906,16 @@ }, { "name": "symfony/console", - "version": "v7.3.1", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101" + "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101", - "reference": "9e27aecde8f506ba0fd1d9989620c04a87697101", + "url": "https://api.github.com/repos/symfony/console/zipball/cdb80fa5869653c83cfe1a9084a673b6daf57ea7", + "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7", "shasum": "" }, "require": { @@ -6621,7 +5980,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.1" + "source": "https://github.com/symfony/console/tree/v7.3.5" }, "funding": [ { @@ -6632,12 +5991,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-27T19:55:54+00:00" + "time": "2025-10-14T15:46:26+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6706,174 +6069,18 @@ ], "time": "2024-09-25T14:21:43+00:00" }, - { - "name": "symfony/event-dispatcher", - "version": "v7.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-04-22T09:11:45+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, { "name": "symfony/filesystem", - "version": "v7.3.0", + "version": "v7.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", - "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", "shasum": "" }, "require": { @@ -6910,7 +6117,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.2" }, "funding": [ { @@ -6921,25 +6128,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-10-25T15:15:23+00:00" + "time": "2025-07-07T08:17:47+00:00" }, { "name": "symfony/finder", - "version": "v7.3.0", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", - "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", "shasum": "" }, "require": { @@ -6971,77 +6182,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-30T19:00:26+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v7.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/afb9a8038025e5dbc657378bfab9198d75f10fca", - "reference": "afb9a8038025e5dbc657378bfab9198d75f10fca", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.0" + "source": "https://github.com/symfony/finder/tree/v7.3.5" }, "funding": [ { @@ -7052,16 +6196,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-04T13:12:05+00:00" + "time": "2025-10-15T18:45:57+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -7120,7 +6268,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -7131,6 +6279,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -7140,16 +6292,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", - "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { @@ -7198,7 +6350,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -7209,16 +6361,20 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2025-06-27T09:58:17+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", @@ -7279,7 +6435,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -7290,6 +6446,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -7299,7 +6459,7 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", @@ -7360,87 +6520,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-23T08:48:59+00:00" - }, - { - "name": "symfony/polyfill-php80", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -7452,79 +6532,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-01-02T08:10:11+00:00" - }, - { - "name": "symfony/polyfill-php81", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -7532,20 +6540,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php84", - "version": "v1.32.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "000df7860439609837bbe28670b0be15783b7fbf" + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", - "reference": "000df7860439609837bbe28670b0be15783b7fbf", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", "shasum": "" }, "require": { @@ -7592,7 +6600,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" }, "funding": [ { @@ -7604,64 +6612,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-02-20T12:04:08+00:00" - }, - { - "name": "symfony/process", - "version": "v7.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Executes commands in sub-processes", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -7669,7 +6620,7 @@ "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2025-06-24T13:30:11+00:00" }, { "name": "symfony/service-contracts", @@ -7754,80 +6705,18 @@ ], "time": "2025-04-25T09:37:31+00:00" }, - { - "name": "symfony/stopwatch", - "version": "v7.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/service-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-02-24T10:49:57+00:00" - }, { "name": "symfony/string", - "version": "v7.3.0", + "version": "v7.3.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + "reference": "f96476035142921000338bad71e5247fbc138872" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", - "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", + "url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872", + "reference": "f96476035142921000338bad71e5247fbc138872", "shasum": "" }, "require": { @@ -7842,7 +6731,6 @@ }, "require-dev": { "symfony/emoji": "^7.1", - "symfony/error-handler": "^6.4|^7.0", "symfony/http-client": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", @@ -7885,7 +6773,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.0" + "source": "https://github.com/symfony/string/tree/v7.3.4" }, "funding": [ { @@ -7896,12 +6784,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-20T20:19:01+00:00" + "time": "2025-09-11T14:36:48+00:00" }, { "name": "theseer/tokenizer", @@ -7955,16 +6847,16 @@ }, { "name": "vimeo/psalm", - "version": "6.13.0", + "version": "6.13.1", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "70cdf647255a1362b426bb0f522a85817b8c791c" + "reference": "1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/70cdf647255a1362b426bb0f522a85817b8c791c", - "reference": "70cdf647255a1362b426bb0f522a85817b8c791c", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51", + "reference": "1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51", "shasum": "" }, "require": { @@ -8069,32 +6961,32 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2025-07-14T09:59:17+00:00" + "time": "2025-08-06T10:10:28+00:00" }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { @@ -8125,9 +7017,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-10-29T15:56:20+00:00" } ], "aliases": [], diff --git a/src/Listener/BaseUuid.php b/src/Listener/BaseUuid.php new file mode 100644 index 0000000..c7aaa76 --- /dev/null +++ b/src/Listener/BaseUuid.php @@ -0,0 +1,38 @@ +factory = new UuidFactory(); + } + + #[Listen(OnCreate::class)] + public function __invoke(OnCreate $event): void + { + if ($this->nullable || isset($event->state->getData()[$this->field])) { + return; + } + + $event->state->register($this->field, $this->createValue()); + } + + abstract protected function createValue(): Uuid; +} diff --git a/src/Listener/Snowflake.php b/src/Listener/Snowflake.php new file mode 100644 index 0000000..9f2adaf --- /dev/null +++ b/src/Listener/Snowflake.php @@ -0,0 +1,32 @@ +nullable || isset($event->state->getData()[$this->field])) { + return; + } + + $event->state->register($this->field, $this->createValue()); + } + + abstract protected function createValue(): \Ramsey\Identifier\Snowflake; +} diff --git a/src/Listener/SnowflakeDiscord.php b/src/Listener/SnowflakeDiscord.php new file mode 100644 index 0000000..87fd9e0 --- /dev/null +++ b/src/Listener/SnowflakeDiscord.php @@ -0,0 +1,77 @@ + */ + private static int $defaultWorkerId = 0; + + /** @var null|int<0, 281474976710655> */ + private static ?int $defaultProcessId = null; + + private DiscordSnowflakeFactory $factory; + + /** + * @param non-empty-string $field The name of the field to store the Snowflake identifier + * @param bool $nullable Indicates whether the Snowflake identifier can be null + * @param int<0, 281474976710655>|null $workerId A worker identifier to use when creating Snowflakes + * @param int<0, 281474976710655>|null $processId A process identifier to use when creating Snowflakes + */ + public function __construct( + string $field, + bool $nullable = false, + ?int $workerId = null, + ?int $processId = null, + ) { + $workerId ??= self::$defaultWorkerId; + $processId ??= $this->getProcessId(); + $this->factory = new DiscordSnowflakeFactory($workerId, $processId); + parent::__construct($field, $nullable); + } + + /** + * Set default worker and process IDs for Snowflake generation. + * + * @param null|int<0, 281474976710655> $workerId The worker ID to set. Null to use the default (0). + * @param null|int<0, 281474976710655> $processId The process ID to set. Null to use the current process ID. + */ + public static function setDefaults(?int $workerId, ?int $processId): void + { + if ($workerId !== null && ($workerId < 0 || $workerId > 281474976710655)) { + throw new \InvalidArgumentException('Worker ID must be between 0 and 281474976710655.'); + } + if ($processId !== null && ($processId < 0 || $processId > 281474976710655)) { + throw new \InvalidArgumentException('Process ID must be between 0 and 281474976710655.'); + } + + self::$defaultWorkerId = (int) $workerId; + self::$defaultProcessId = $processId; + } + + #[\Override] + protected function createValue(): DiscordSnowflake + { + return $this->factory->create(); + } + + /** + * Get the current process ID. + * + * @return int<0, 281474976710655> + */ + private function getProcessId(): int + { + $processId = self::$defaultProcessId ?? \intval(\getmypid()); + return \min(\max($processId, 0), 281474976710655); + } +} diff --git a/src/Listener/SnowflakeGeneric.php b/src/Listener/SnowflakeGeneric.php new file mode 100644 index 0000000..170e133 --- /dev/null +++ b/src/Listener/SnowflakeGeneric.php @@ -0,0 +1,72 @@ + */ + private static int $node = 0; + + private static Epoch|int $epochOffset = 0; + private GenericSnowflakeFactory $factory; + + /** + * @param non-empty-string $field The name of the field to store the Snowflake identifier + * @param bool $nullable Indicates whether the Snowflake identifier can be null + * @param int<0, 1023>|null $node A node identifier to use when creating Snowflakes + * @param Epoch|int|null $epochOffset The offset from the Unix Epoch in milliseconds + */ + public function __construct( + string $field, + bool $nullable = false, + ?int $node = null, + Epoch|int|null $epochOffset = null, + ) { + $node ??= self::$node; + $epochOffset ??= self::$epochOffset; + $this->factory = new GenericSnowflakeFactory($node, $epochOffset); + parent::__construct($field, $nullable); + } + + /** + * Set default node and epoch offset for Snowflake generation. + * + * @param null|int<0, 1023> $node The node ID to set. Null to use the default (0). + * @param Epoch|int|null $epochOffset The epoch offset to set. Null to use the default (0). + */ + public static function setDefaults(?int $node, Epoch|int|null $epochOffset): void + { + if ($node !== null && ($node < 0 || $node > 1023)) { + throw new \InvalidArgumentException('Node ID must be between 0 and 1023.'); + } + + self::$node = (int) $node; + if ($epochOffset !== null) { + self::$epochOffset = $epochOffset; + } + } + + /** + * Get default epoch offset. + */ + public static function getEpochOffset(): Epoch|int + { + return self::$epochOffset; + } + + #[\Override] + protected function createValue(): Snowflake + { + return $this->factory->create(); + } +} diff --git a/src/Listener/SnowflakeInstagram.php b/src/Listener/SnowflakeInstagram.php new file mode 100644 index 0000000..000a0ad --- /dev/null +++ b/src/Listener/SnowflakeInstagram.php @@ -0,0 +1,55 @@ + */ + private static int $shardId = 0; + + private InstagramSnowflakeFactory $factory; + + /** + * @param non-empty-string $field The name of the field to store the Snowflake identifier + * @param bool $nullable Indicates whether the Snowflake identifier can be null + * @param int<0, 1023>|null $shardId A shard identifier to use when creating Snowflakes + */ + public function __construct( + string $field, + bool $nullable = false, + ?int $shardId = null, + ) { + $shardId ??= self::$shardId; + $this->factory = new InstagramSnowflakeFactory($shardId); + parent::__construct($field, $nullable); + } + + /** + * Set default shard ID for Snowflake generation. + * + * @param null|int<0, 1023> $shardId The shard ID to set. Null to use the default (0). + */ + public static function setDefaults(?int $shardId): void + { + if ($shardId !== null && ($shardId < 0 || $shardId > 1023)) { + throw new \InvalidArgumentException('Shard ID must be between 0 and 1023.'); + } + + self::$shardId = (int) $shardId; + } + + #[\Override] + protected function createValue(): InstagramSnowflake + { + return $this->factory->create(); + } +} diff --git a/src/Listener/SnowflakeMastodon.php b/src/Listener/SnowflakeMastodon.php new file mode 100644 index 0000000..235c39d --- /dev/null +++ b/src/Listener/SnowflakeMastodon.php @@ -0,0 +1,51 @@ +factory = new MastodonSnowflakeFactory($tableName); + parent::__construct($field, $nullable); + } + + /** + * Set default table name for Snowflake generation. + * + * @param non-empty-string|null $tableName The table name to set. Null to use the default (null). + */ + public static function setDefaults(?string $tableName): void + { + self::$tableName = $tableName; + } + + #[\Override] + protected function createValue(): MastodonSnowflake + { + return $this->factory->create(); + } +} diff --git a/src/Listener/SnowflakeTwitter.php b/src/Listener/SnowflakeTwitter.php new file mode 100644 index 0000000..ce6a942 --- /dev/null +++ b/src/Listener/SnowflakeTwitter.php @@ -0,0 +1,55 @@ + */ + private static int $machineId = 0; + + private TwitterSnowflakeFactory $factory; + + /** + * @param non-empty-string $field The name of the field to store the Snowflake identifier + * @param bool $nullable Indicates whether the Snowflake identifier can be null + * @param int<0, 1023>|null $machineId A machine identifier to use when creating Snowflakes + */ + public function __construct( + string $field, + bool $nullable = false, + ?int $machineId = null, + ) { + $machineId ??= self::$machineId; + $this->factory = new TwitterSnowflakeFactory($machineId); + parent::__construct($field, $nullable); + } + + /** + * Set default machine ID for Snowflake generation. + * + * @param null|int<0, 1023> $machineId The machine ID to set. Null to use the default (0). + */ + public static function setDefaults(?int $machineId): void + { + if ($machineId !== null && ($machineId < 0 || $machineId > 1023)) { + throw new \InvalidArgumentException('Machine ID must be between 0 and 1023.'); + } + + self::$machineId = (int) $machineId; + } + + #[\Override] + protected function createValue(): TwitterSnowflake + { + return $this->factory->create(); + } +} diff --git a/src/Listener/Ulid.php b/src/Listener/Ulid.php index 763b3bf..df8e417 100644 --- a/src/Listener/Ulid.php +++ b/src/Listener/Ulid.php @@ -6,6 +6,7 @@ use Cycle\ORM\Entity\Behavior\Attribute\Listen; use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate; +use Ramsey\Identifier\Ulid\Ulid as UlidIdentifier; use Ramsey\Identifier\Ulid\UlidFactory; final class Ulid @@ -22,8 +23,11 @@ public function __invoke(OnCreate $event): void return; } - $identifier = (new UlidFactory())->create(); + $event->state->register($this->field, $this->createValue()); + } - $event->state->register($this->field, $identifier); + protected function createValue(): UlidIdentifier + { + return (new UlidFactory())->create(); } } diff --git a/src/Listener/Uuid1.php b/src/Listener/Uuid1.php index e24a0e3..f7238a9 100644 --- a/src/Listener/Uuid1.php +++ b/src/Listener/Uuid1.php @@ -4,34 +4,51 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Listener; -use Cycle\ORM\Entity\Behavior\Attribute\Listen; -use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate; -use Ramsey\Identifier\Uuid\UuidFactory; +use Ramsey\Identifier\Uuid\UuidV1; -final class Uuid1 +/** + * Generates UUIDv1 identifiers for entities. + * You can set default node and clock sequence using the {@see setDefaults()} method. + */ +final class Uuid1 extends BaseUuid { + /** @var int<0, 281474976710655>|non-empty-string|null */ + private static int|string|null $defaultNode = null; + + private static ?int $defaultClockSeq = null; + /** - * @param int<0, 281474976710655>|non-empty-string|null $node + * @param non-empty-string $field The name of the field to store the UUID + * @param bool $nullable Indicates whether the UUID can be null + * @param int<0, 281474976710655>|non-empty-string|null $node A 48-bit integer or hexadecimal string representing the hardware address + * @param int|null $clockSeq A number used to help avoid duplicates that could arise when the clock is set backwards in time */ public function __construct( - private string $field = 'uuid', - private int|string|null $node = null, - private ?int $clockSeq = null, - private bool $nullable = false, - ) {} - - #[Listen(OnCreate::class)] - public function __invoke(OnCreate $event): void - { - if ($this->nullable || isset($event->state->getData()[$this->field])) { - return; - } + string $field, + bool $nullable = false, + private readonly int|string|null $node = null, + private readonly ?int $clockSeq = null, + ) { + parent::__construct($field, $nullable); + } - $identifier = (new UuidFactory())->v1( - $this->node, - $this->clockSeq, - ); + /** + * Set default node and clock sequence for UUIDv1 generation. + * + * @param int<0, 281474976710655>|non-empty-string|null $node The node to set + * @param int|null $clockSeq The clock sequence to set + */ + public static function setDefaults(int|string|null $node, ?int $clockSeq): void + { + self::$defaultNode = $node; + self::$defaultClockSeq = $clockSeq; + } - $event->state->register($this->field, $identifier); + #[\Override] + protected function createValue(): UuidV1 + { + $node = $this->node ?? self::$defaultNode; + $clockSeq = $this->clockSeq ?? self::$defaultClockSeq; + return $this->factory->v1($node, $clockSeq); } } diff --git a/src/Listener/Uuid2.php b/src/Listener/Uuid2.php index 8d771f6..ba00a3b 100644 --- a/src/Listener/Uuid2.php +++ b/src/Listener/Uuid2.php @@ -4,42 +4,74 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Listener; -use Cycle\ORM\Entity\Behavior\Attribute\Listen; -use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate; use Ramsey\Identifier\Uuid\DceDomain; -use Ramsey\Identifier\Uuid\UuidFactory; +use Ramsey\Identifier\Uuid\UuidV2; -final class Uuid2 +/** + * Generates UUIDv2 (DCE Security) identifiers for entities. + * You can set default values using the {@see setDefaults()} method. + */ +final class Uuid2 extends BaseUuid { + private static DceDomain|int $defaultLocalDomain = DceDomain::Person; + + /** @var int<0, 4294967295>|null */ + private static ?int $defaultLocalIdentifier = null; + + /** @var int<0, 281474976710655>|non-empty-string|null */ + private static int|string|null $defaultNode = null; + + private static ?int $defaultClockSeq = null; + /** - * @param int<0, 4294967295>| null $localIdentifier $localIdentifier - * @param int<0, 281474976710655>|non-empty-string|null $node + * @param non-empty-string $field The name of the field to store the UUID + * @param bool $nullable Indicates whether the UUID can be null + * @param DceDomain|int|null $localDomain The local domain to which the local identifier belongs + * @param int<0, 4294967295>|null $localIdentifier A 32-bit local identifier belonging to the local domain + * @param int<0, 281474976710655>|non-empty-string|null $node A 48-bit integer or hexadecimal string representing the hardware address + * @param int|null $clockSeq A number used to help avoid duplicates that could arise when the clock is set backwards in time */ public function __construct( - private string $field = 'uuid', - private DceDomain|int $localDomain = 0, - private ?int $localIdentifier = null, - private int|string|null $node = null, - private ?int $clockSeq = null, - private bool $nullable = false, - ) {} - - #[Listen(OnCreate::class)] - public function __invoke(OnCreate $event): void - { - if ($this->nullable || isset($event->state->getData()[$this->field])) { - return; - } + string $field, + bool $nullable = false, + private readonly DceDomain|int|null $localDomain = null, + private readonly ?int $localIdentifier = null, + private readonly int|string|null $node = null, + private readonly ?int $clockSeq = null, + ) { + parent::__construct($field, $nullable); + } - $this->localDomain = \is_int($this->localDomain) ? DceDomain::from($this->localDomain) : $this->localDomain; + /** + * Set default values for UUIDv2 generation. + * + * @param DceDomain|int|null $localDomain The local domain + * @param int<0, 4294967295>|null $localIdentifier The local identifier + * @param int<0, 281474976710655>|non-empty-string|null $node The node + * @param int|null $clockSeq The clock sequence + */ + public static function setDefaults( + DceDomain|int|null $localDomain, + ?int $localIdentifier, + int|string|null $node, + ?int $clockSeq, + ): void { + self::$defaultLocalDomain = $localDomain; + self::$defaultLocalIdentifier = $localIdentifier; + self::$defaultNode = $node; + self::$defaultClockSeq = $clockSeq; + } + + #[\Override] + protected function createValue(): UuidV2 + { + $localDomain = $this->localDomain ?? self::$defaultLocalDomain; + $localIdentifier = $this->localIdentifier ?? self::$defaultLocalIdentifier; + $node = $this->node ?? self::$defaultNode; + $clockSeq = $this->clockSeq ?? self::$defaultClockSeq; - $identifier = (new UuidFactory())->v2( - $this->localDomain, - $this->localIdentifier, - $this->node, - $this->clockSeq, - ); + $localDomain = \is_int($localDomain) ? DceDomain::from($localDomain) : $localDomain; - $event->state->register($this->field, $identifier); + return $this->factory->v2($localDomain, $localIdentifier, $node, $clockSeq); } } diff --git a/src/Listener/Uuid3.php b/src/Listener/Uuid3.php index 523eb3e..225825f 100644 --- a/src/Listener/Uuid3.php +++ b/src/Listener/Uuid3.php @@ -4,33 +4,59 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Listener; -use Cycle\ORM\Entity\Behavior\Attribute\Listen; -use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate; +use Cycle\ORM\Entity\Behavior\Identifier\Listener\BaseUuid as Base; use Ramsey\Identifier\Uuid; use Ramsey\Identifier\Uuid\NamespaceId; -use Ramsey\Identifier\Uuid\UuidFactory; +use Ramsey\Identifier\Uuid\UuidV3; -final class Uuid3 +/** + * Generates UUIDv3 (name-based with MD5 hashing) identifiers for entities. + */ +final class Uuid3 extends Base { + private static NamespaceId|Uuid|string|null $defaultNamespace = null; + private static ?string $defaultName = null; + + /** + * @param non-empty-string $field The name of the field to store the UUID + * @param bool $nullable Indicates whether the UUID can be null + * @param NamespaceId|Uuid|string|null $namespace The namespace UUID + * @param string|null $name The name to hash + */ public function __construct( - private NamespaceId|Uuid|string $namespace, - private string $name, - private string $field = 'uuid', - private bool $nullable = false, - ) {} - - #[Listen(OnCreate::class)] - public function __invoke(OnCreate $event): void + string $field, + bool $nullable = false, + private readonly NamespaceId|Uuid|string|null $namespace = null, + private readonly ?string $name = null, + ) { + parent::__construct($field, $nullable); + } + + /** + * Set default values for UUIDv3 generation. + */ + public static function setDefaults( + NamespaceId|Uuid|string|null $namespace, + ?string $name, + ): void { + self::$defaultNamespace = $namespace; + self::$defaultName = $name; + } + + #[\Override] + protected function createValue(): UuidV3 { - if ($this->nullable || isset($event->state->getData()[$this->field])) { - return; + $namespace = $this->namespace ?? self::$defaultNamespace; + $name = $this->name ?? self::$defaultName; + + if ($namespace === null) { + throw new \InvalidArgumentException('Namespace must be specified.'); } - $identifier = (new UuidFactory())->v3( - $this->namespace, - $this->name, - ); + if ($name === null) { + throw new \InvalidArgumentException('Name must be specified.'); + } - $event->state->register($this->field, $identifier); + return $this->factory->v3($namespace, $name); } } diff --git a/src/Listener/Uuid4.php b/src/Listener/Uuid4.php index 5d8771f..b1e41c2 100644 --- a/src/Listener/Uuid4.php +++ b/src/Listener/Uuid4.php @@ -4,26 +4,27 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Listener; -use Cycle\ORM\Entity\Behavior\Attribute\Listen; -use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate; -use Ramsey\Identifier\Uuid\UuidFactory; +use Ramsey\Identifier\Uuid\UuidV4; -final class Uuid4 +/** + * Generates UUIDv4 (random) identifiers for entities. + */ +final class Uuid4 extends BaseUuid { + /** + * @param non-empty-string $field The name of the field to store the UUID + * @param bool $nullable Indicates whether the UUID can be null + */ public function __construct( - private string $field = 'uuid', - private bool $nullable = false, - ) {} + string $field, + bool $nullable = false, + ) { + parent::__construct($field, $nullable); + } - #[Listen(OnCreate::class)] - public function __invoke(OnCreate $event): void + #[\Override] + protected function createValue(): UuidV4 { - if ($this->nullable || isset($event->state->getData()[$this->field])) { - return; - } - - $identifier = (new UuidFactory())->v4(); - - $event->state->register($this->field, $identifier); + return $this->factory->v4(); } } diff --git a/src/Listener/Uuid5.php b/src/Listener/Uuid5.php index 1fc8df3..e070696 100644 --- a/src/Listener/Uuid5.php +++ b/src/Listener/Uuid5.php @@ -4,33 +4,60 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Listener; -use Cycle\ORM\Entity\Behavior\Attribute\Listen; -use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate; +use Cycle\ORM\Entity\Behavior\Identifier\Listener\BaseUuid as Base; use Ramsey\Identifier\Uuid; use Ramsey\Identifier\Uuid\NamespaceId; -use Ramsey\Identifier\Uuid\UuidFactory; +use Ramsey\Identifier\Uuid\UuidV5; -final class Uuid5 +/** + * Generates UUIDv5 (name-based with SHA-1 hashing) identifiers for entities. + */ +final class Uuid5 extends Base { + private static NamespaceId|Uuid|string|null $defaultNamespace = null; + private static ?string $defaultName = null; + + /** + * @param non-empty-string $field The name of the field to store the UUID + * @param bool $nullable Indicates whether the UUID can be null + * @param NamespaceId|Uuid|string|null $namespace The namespace UUID + * @param string|null $name The name to hash + */ public function __construct( - private NamespaceId|Uuid|string $namespace, - private string $name, - private string $field = 'uuid', - private bool $nullable = false, - ) {} - - #[Listen(OnCreate::class)] - public function __invoke(OnCreate $event): void + string $field, + bool $nullable = false, + private readonly NamespaceId|Uuid|string|null $namespace = null, + private readonly ?string $name = null, + ) { + parent::__construct($field, $nullable); + } + + /** + * Set default values for UUIDv3 generation. + * + */ + public static function setDefaults( + NamespaceId|Uuid|string|null $namespace, + ?string $name, + ): void { + self::$defaultNamespace = $namespace; + self::$defaultName = $name; + } + + #[\Override] + protected function createValue(): UuidV5 { - if ($this->nullable || isset($event->state->getData()[$this->field])) { - return; + $namespace = $this->namespace ?? self::$defaultNamespace; + $name = $this->name ?? self::$defaultName; + + if ($namespace === null) { + throw new \InvalidArgumentException('Namespace must be specified.'); } - $identifier = (new UuidFactory())->v5( - $this->namespace, - $this->name, - ); + if ($name === null) { + throw new \InvalidArgumentException('Name must be specified.'); + } - $event->state->register($this->field, $identifier); + return $this->factory->v5($namespace, $name); } } diff --git a/src/Listener/Uuid6.php b/src/Listener/Uuid6.php index c60f2b1..cb24e6c 100644 --- a/src/Listener/Uuid6.php +++ b/src/Listener/Uuid6.php @@ -4,37 +4,53 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Listener; -use Cycle\ORM\Entity\Behavior\Attribute\Listen; -use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate; use Ramsey\Identifier\Service\Nic\Nic; -use Ramsey\Identifier\Uuid\UuidFactory; +use Ramsey\Identifier\Uuid\UuidV6; -final class Uuid6 +/** + * Generates UUIDv6 (ordered-time) identifiers for entities. + * You can set default node and clock sequence using the {@see setDefaults()} method. + */ +final class Uuid6 extends BaseUuid { + /** @var int<0, 281474976710655>|non-empty-string|null */ + private static int|string|null $defaultNode = null; + + private static ?int $defaultClockSeq = null; + /** - * @param int<0, 281474976710655>|non-empty-string|null $node + * @param non-empty-string $field The name of the field to store the UUID + * @param bool $nullable Indicates whether the UUID can be null + * @param int<0, 281474976710655>|non-empty-string|null $node A 48-bit integer or hexadecimal string representing the hardware address + * @param int|null $clockSeq A number used to help avoid duplicates that could arise when the clock is set backwards in time */ public function __construct( - private string $field = 'uuid', - private int|string|null $node = null, - private ?int $clockSeq = null, - private bool $nullable = false, - ) {} - - #[Listen(OnCreate::class)] - public function __invoke(OnCreate $event): void - { - if ($this->nullable || isset($event->state->getData()[$this->field])) { - return; - } - - $this->node = $this->node instanceof Nic ? $this->node->address() : $this->node; + string $field, + bool $nullable = false, + private readonly int|string|null $node = null, + private readonly ?int $clockSeq = null, + ) { + parent::__construct($field, $nullable); + } - $identifier = (new UuidFactory())->v6( - $this->node, - $this->clockSeq, - ); + /** + * Set default node and clock sequence for UUIDv6 generation. + * + * @param int<0, 281474976710655>|non-empty-string|null $node The node to set + * @param int|null $clockSeq The clock sequence to set + */ + public static function setDefaults(int|string|null $node, ?int $clockSeq): void + { + self::$defaultNode = $node; + self::$defaultClockSeq = $clockSeq; + } - $event->state->register($this->field, $identifier); + #[\Override] + protected function createValue(): UuidV6 + { + $node = $this->node ?? self::$defaultNode; + $clockSeq = $this->clockSeq ?? self::$defaultClockSeq; + $node = $node instanceof Nic ? $node->address() : $node; + return $this->factory->v6($node, $clockSeq); } } diff --git a/src/Listener/Uuid7.php b/src/Listener/Uuid7.php index d71cf7d..3a82d21 100644 --- a/src/Listener/Uuid7.php +++ b/src/Listener/Uuid7.php @@ -4,26 +4,27 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Listener; -use Cycle\ORM\Entity\Behavior\Attribute\Listen; -use Cycle\ORM\Entity\Behavior\Event\Mapper\Command\OnCreate; -use Ramsey\Identifier\Uuid\UuidFactory; +use Ramsey\Identifier\Uuid\UuidV7; -final class Uuid7 +/** + * Generates UUIDv7 (time-ordered with random data) identifiers for entities. + */ +final class Uuid7 extends BaseUuid { + /** + * @param non-empty-string $field The name of the field to store the UUID + * @param bool $nullable Indicates whether the UUID can be null + */ public function __construct( - private string $field = 'uuid', - private bool $nullable = false, - ) {} + string $field, + bool $nullable = false, + ) { + parent::__construct($field, $nullable); + } - #[Listen(OnCreate::class)] - public function __invoke(OnCreate $event): void + #[\Override] + protected function createValue(): UuidV7 { - if ($this->nullable || isset($event->state->getData()[$this->field])) { - return; - } - - $identifier = (new UuidFactory())->v7(); - - $event->state->register($this->field, $identifier); + return $this->factory->v7(); } } diff --git a/src/Snowflake.php b/src/Snowflake.php new file mode 100644 index 0000000..f311c09 --- /dev/null +++ b/src/Snowflake.php @@ -0,0 +1,65 @@ +role); + /** @var non-empty-string column */ + $this->column = $modifier->findColumnName($this->field, $this->column); + if (\is_string($this->column) && $this->column !== '') { + $modifier->addSnowflakeColumn( + $this->column, + $this->field, + $this->nullable ? null : GeneratedField::BEFORE_INSERT, + )->nullable($this->nullable); + + $modifier->setTypecast( + $registry->getEntity($this->role)->getFields()->get($this->field), + $this->getTypecast(), + ); + } + } + + #[\Override] + public function render(Registry $registry): void + { + $modifier = new RegistryModifier($registry, $this->role); + /** @var non-empty-string column */ + $this->column = $modifier->findColumnName($this->field, $this->column) ?? $this->field; + + $modifier->addSnowflakeColumn( + $this->column, + $this->field, + $this->nullable ? null : GeneratedField::BEFORE_INSERT, + )->nullable($this->nullable); + + $modifier->setTypecast( + $registry->getEntity($this->role)->getFields()->get($this->field), + $this->getTypecast(), + ); + } + + /** + * @return array{0: class-string, 1: non-empty-string, 2?: non-empty-array} + */ + abstract protected function getTypecast(): array; +} diff --git a/src/SnowflakeDiscord.php b/src/SnowflakeDiscord.php new file mode 100644 index 0000000..77ef82f --- /dev/null +++ b/src/SnowflakeDiscord.php @@ -0,0 +1,85 @@ +|null $workerId A worker identifier to use when creating Snowflakes + * @param int<0, 281474976710655>|null $processId A process identifier to use when creating Snowflakes + * @param bool $nullable Indicates whether to generate a new Snowflake or not + */ + public function __construct( + string $field = 'snowflake', + ?string $column = null, + private readonly ?int $workerId = null, + private readonly ?int $processId = null, + bool $nullable = false, + ) { + $this->field = $field; + $this->column = $column; + $this->nullable = $nullable; + } + + /** + * Create a new Discord Snowflake instance from an existing identifier value. + * + * @param int<0, max>|numeric-string $identifier The identifier to create the Snowflake from + * + * @see DiscordSnowflakeFactory::create() + */ + public static function create( + int|string $identifier, + ): DiscordSnowflake { + return new DiscordSnowflake($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; + } + + #[\Override] + protected function getListenerClass(): string + { + return Listener::class; + } + + /** + * @return array{ + * field: non-empty-string, + * workerId: null|int<0, 281474976710655>, + * processId: null|int<0, 281474976710655>, + * nullable: bool + * } + */ + #[\Override] + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + 'workerId' => $this->workerId, + 'processId' => $this->processId, + 'nullable' => $this->nullable, + ]; + } +} diff --git a/src/SnowflakeGeneric.php b/src/SnowflakeGeneric.php new file mode 100644 index 0000000..17d19de --- /dev/null +++ b/src/SnowflakeGeneric.php @@ -0,0 +1,94 @@ +|null $node A node identifier to use when creating Snowflakes + * @param Epoch|int|null $epochOffset The offset from the Unix Epoch in milliseconds + * @param bool $nullable Indicates whether to generate a new Snowflake or not + */ + public function __construct( + string $field = 'snowflake', + ?string $column = null, + private readonly ?int $node = null, + private readonly Epoch|int|null $epochOffset = null, + bool $nullable = false, + ) { + $this->field = $field; + $this->column = $column; + $this->nullable = $nullable; + } + + /** + * Create a new Generic Snowflake instance from an existing identifier value. + * + * @param int<0, max>|numeric-string $identifier The identifier to create the Snowflake from + * @param int $epochOffset The offset from the Unix Epoch in milliseconds + * + * @see GenericSnowflakeFactory::create() + */ + public static function create( + int|string $identifier, + int $epochOffset, + ): GenericSnowflake { + return new GenericSnowflake($identifier, $epochOffset); + } + + #[\Override] + protected function getTypecast(): array + { + $epochOffset = $this->epochOffset ?? Listener::getEpochOffset(); + $epochOffset instanceof Epoch and $epochOffset = $epochOffset->value; + + return [self::class, 'create', [$epochOffset]]; + } + + #[\Override] + protected function getListenerClass(): string + { + return Listener::class; + } + + /** + * @return array{ + * field: non-empty-string, + * node: null|int<0, 1023>, + * epochOffset: Epoch|int|null, + * nullable: bool + * } + */ + #[\Override] + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + 'node' => $this->node, + 'epochOffset' => $this->epochOffset, + 'nullable' => $this->nullable, + ]; + } +} diff --git a/src/SnowflakeInstagram.php b/src/SnowflakeInstagram.php new file mode 100644 index 0000000..b0623a2 --- /dev/null +++ b/src/SnowflakeInstagram.php @@ -0,0 +1,81 @@ +|null $shardId A shard identifier to use when creating Snowflakes + * @param bool $nullable Indicates whether to generate a new Snowflake or not + */ + public function __construct( + string $field = 'snowflake', + ?string $column = null, + private readonly ?int $shardId = null, + bool $nullable = false, + ) { + $this->field = $field; + $this->column = $column; + $this->nullable = $nullable; + } + + /** + * Create a new Instagram Snowflake instance from an existing identifier value. + * + * @param int<0, max>|numeric-string $identifier The identifier to create the Snowflake from + * + * @see InstagramSnowflakeFactory::create() + */ + public static function create( + int|string $identifier, + ): InstagramSnowflake { + return new InstagramSnowflake($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; + } + + #[\Override] + protected function getListenerClass(): string + { + return Listener::class; + } + + /** + * @return array{ + * field: non-empty-string, + * shardId: null|int<0, 1023>, + * nullable: bool + * } + */ + #[\Override] + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + 'shardId' => $this->shardId, + 'nullable' => $this->nullable, + ]; + } +} diff --git a/src/SnowflakeMastodon.php b/src/SnowflakeMastodon.php new file mode 100644 index 0000000..031fbac --- /dev/null +++ b/src/SnowflakeMastodon.php @@ -0,0 +1,81 @@ +field = $field; + $this->column = $column; + $this->nullable = $nullable; + } + + /** + * Create a new Mastodon Snowflake instance from an existing identifier value. + * + * @param int<0, max>|numeric-string $identifier The identifier to create the Snowflake from + * + * @see MastodonSnowflakeFactory::create() + */ + public static function create( + int|string $identifier, + ): MastodonSnowflake { + return new MastodonSnowflake($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; + } + + #[\Override] + protected function getListenerClass(): string + { + return Listener::class; + } + + /** + * @return array{ + * field: non-empty-string, + * tableName: non-empty-string|null, + * nullable: bool + * } + */ + #[\Override] + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + 'tableName' => $this->tableName, + 'nullable' => $this->nullable, + ]; + } +} diff --git a/src/SnowflakeTwitter.php b/src/SnowflakeTwitter.php new file mode 100644 index 0000000..1c3f177 --- /dev/null +++ b/src/SnowflakeTwitter.php @@ -0,0 +1,83 @@ +|null $machineId A machine identifier to use when creating Snowflakes + * @param bool $nullable Indicates whether to generate a new Snowflake or not + * + * @see \Ramsey\Identifier\Snowflake\TwitterSnowflakeFactory::create() + */ + public function __construct( + string $field = 'snowflake', + ?string $column = null, + private readonly ?int $machineId = null, + bool $nullable = false, + ) { + $this->field = $field; + $this->column = $column; + $this->nullable = $nullable; + } + + /** + * Create a new Twitter Snowflake instance from an existing identifier value. + * + * @param int<0, max>|numeric-string $identifier The identifier to create the Snowflake from + * + * @see TwitterSnowflakeFactory::create() + */ + public static function create( + int|string $identifier, + ): TwitterSnowflake { + return new TwitterSnowflake($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; + } + + #[\Override] + protected function getListenerClass(): string + { + return Listener::class; + } + + /** + * @return array{ + * field: non-empty-string, + * machineId: null|int<0, 1023>, + * nullable: bool + * } + */ + #[\Override] + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + 'machineId' => $this->machineId, + 'nullable' => $this->nullable, + ]; + } +} diff --git a/src/Ulid.php b/src/Ulid.php index 55b0c13..4a1be1a 100644 --- a/src/Ulid.php +++ b/src/Ulid.php @@ -13,7 +13,7 @@ use JetBrains\PhpStorm\ArrayShape; use Ramsey\Identifier\Ulid\MaxUlid; use Ramsey\Identifier\Ulid\NilUlid; -use Ramsey\Identifier\Ulid\Ulid as UlidInterface; +use Ramsey\Identifier\Ulid\Ulid as UlidIdentifier; use Ramsey\Identifier\Ulid\UlidFactory; /** @@ -30,8 +30,6 @@ final class Ulid extends BaseModifier * @param non-empty-string $field Ulid property name * @param string|null $column Ulid column name * @param bool $nullable Indicates whether to generate a new Ulid or not - * - * @see \Ramsey\Identifier\Ulid\UlidFactory::create() */ public function __construct( private string $field = 'ulid', @@ -40,11 +38,15 @@ public function __construct( ) {} /** - * @param non-empty-string $value + * Create a new Ulid instance from an existing identifier value. + * + * @param non-empty-string $identifier The identifier to create the Ulid from + * + * @see UlidFactory::create() */ - public static function fromString(string $value): MaxUlid|NilUlid|UlidInterface + public static function create(string $identifier): MaxUlid|NilUlid|UlidIdentifier { - return (new UlidFactory())->createFromString($value); + return (new UlidFactory())->createFromString($identifier); } #[\Override] @@ -52,7 +54,7 @@ public function compute(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); $this->column = $modifier->findColumnName($this->field, $this->column); - if ($this->column !== null) { + if (\is_string($this->column) && $this->column !== '') { $modifier->addUlidColumn( $this->column, $this->field, @@ -61,7 +63,7 @@ public function compute(Registry $registry): void $modifier->setTypecast( $registry->getEntity($this->role)->getFields()->get($this->field), - [self::class, 'fromString'], + $this->getTypecast(), ); } } @@ -70,6 +72,7 @@ public function compute(Registry $registry): void public function render(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); + /** @var non-empty-string column */ $this->column = $modifier->findColumnName($this->field, $this->column) ?? $this->field; $modifier->addUlidColumn( @@ -80,10 +83,18 @@ public function render(Registry $registry): void $modifier->setTypecast( $registry->getEntity($this->role)->getFields()->get($this->field), - [self::class, 'fromString'], + $this->getTypecast(), ); } + /** + * @return array{0: class-string, 1: non-empty-string, 2?: non-empty-array} + */ + protected function getTypecast(): array + { + return [self::class, 'create']; + } + #[\Override] protected function getListenerClass(): string { diff --git a/src/Uuid.php b/src/Uuid.php index 8bc81d9..ec19d76 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -8,8 +8,6 @@ use Cycle\ORM\Entity\Behavior\Schema\RegistryModifier; use Cycle\ORM\Schema\GeneratedField; use Cycle\Schema\Registry; -use Ramsey\Identifier\Uuid\UntypedUuid; -use Ramsey\Identifier\Uuid\UuidFactory; abstract class Uuid extends BaseModifier { @@ -17,20 +15,12 @@ abstract class Uuid extends BaseModifier protected string $field; protected bool $nullable = false; - /** - * @param non-empty-string $value - */ - public static function fromString(string $value): UntypedUuid - { - return (new UuidFactory())->createFromString($value); - } - #[\Override] public function compute(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); $this->column = $modifier->findColumnName($this->field, $this->column); - if ($this->column !== null) { + if (\is_string($this->column) && $this->column !== '') { $modifier->addUuidColumn( $this->column, $this->field, @@ -39,7 +29,7 @@ public function compute(Registry $registry): void $modifier->setTypecast( $registry->getEntity($this->role)->getFields()->get($this->field), - [self::class, 'fromString'], + $this->getTypecast(), ); } } @@ -48,6 +38,7 @@ public function compute(Registry $registry): void public function render(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); + /** @var non-empty-string column */ $this->column = $modifier->findColumnName($this->field, $this->column) ?? $this->field; $modifier->addUuidColumn( @@ -58,7 +49,12 @@ public function render(Registry $registry): void $modifier->setTypecast( $registry->getEntity($this->role)->getFields()->get($this->field), - [self::class, 'fromString'], + $this->getTypecast(), ); } + + /** + * @return array{0: class-string, 1: non-empty-string, 2?: non-empty-array} + */ + abstract protected function getTypecast(): array; } diff --git a/src/Uuid1.php b/src/Uuid1.php index efcb70e..611bad8 100644 --- a/src/Uuid1.php +++ b/src/Uuid1.php @@ -9,6 +9,8 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use JetBrains\PhpStorm\ArrayShape; use Ramsey\Identifier\Service\Nic\Nic; +use Ramsey\Identifier\Uuid\UuidV1; +use Ramsey\Identifier\Uuid\UuidV1Factory; /** * Uses a version 1 (time-based) UUID from a host ID, sequence number, @@ -21,6 +23,13 @@ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid1 extends BaseUuid { + /** + * @var Nic|int<0, 281474976710655>|non-empty-string|null $node + */ + private Nic|int|string|null $node = null; + + private ?int $clockSeq = null; + /** * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name @@ -36,13 +45,31 @@ final class Uuid1 extends BaseUuid public function __construct( string $field = 'uuid', ?string $column = null, - private Nic|int|string|null $node = null, - private ?int $clockSeq = null, + Nic|int|string|null $node = null, + ?int $clockSeq = null, bool $nullable = false, ) { $this->field = $field; $this->column = $column; $this->nullable = $nullable; + $this->node = $node; + $this->clockSeq = $clockSeq; + } + + /** + * Create a new UUIDv1 instance from an existing identifier value. + * + * @param non-empty-string $identifier The identifier to create the Uuid from + */ + public static function create(string $identifier): UuidV1 + { + return (new UuidV1Factory())->createFromString($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; } #[\Override] diff --git a/src/Uuid2.php b/src/Uuid2.php index c3e1868..197dceb 100644 --- a/src/Uuid2.php +++ b/src/Uuid2.php @@ -10,6 +10,8 @@ use JetBrains\PhpStorm\ArrayShape; use Ramsey\Identifier\Service\Nic\Nic; use Ramsey\Identifier\Uuid\DceDomain; +use Ramsey\Identifier\Uuid\UuidV2; +use Ramsey\Identifier\Uuid\UuidV2Factory; /** * Uses a version 2 (DCE Security) UUID from a local domain, local @@ -22,10 +24,20 @@ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid2 extends BaseUuid { + private DceDomain|int $localDomain; + private ?int $localIdentifier; + + /** + * @var Nic|int<0, 281474976710655>|non-empty-string|null $node + */ + private Nic|int|string|null $node; + + private ?int $clockSeq; + /** * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name - * @param DceDomain|int $localDomain The local domain to which the local identifier belongs; this defaults to "Person" + * @param DceDomain|int|null $localDomain The local domain to which the local identifier belongs; this defaults to "Person" * and if $localIdentifier is not provided, the factory will attempt to get a suitable local ID for the domain * (e.g., the UID or GID of the user running the script). * @param int<0, 4294967295> | null $localIdentifier A 32-bit local identifier belonging to the local domain @@ -43,15 +55,35 @@ final class Uuid2 extends BaseUuid public function __construct( string $field = 'uuid', ?string $column = null, - private DceDomain|int $localDomain = 0, - private ?int $localIdentifier = null, - private Nic|int|string|null $node = null, - private ?int $clockSeq = null, + DceDomain|int|null $localDomain = null, + ?int $localIdentifier = null, + Nic|int|string|null $node = null, + ?int $clockSeq = null, bool $nullable = false, ) { $this->field = $field; $this->column = $column; $this->nullable = $nullable; + $this->localDomain = $localDomain ?? DceDomain::Person; + $this->localIdentifier = $localIdentifier; + $this->node = $node; + $this->clockSeq = $clockSeq; + } + + /** + * Create a new UUIDv2 instance from an existing identifier value. + * + * @param non-empty-string $identifier The identifier to create the Uuid from + */ + public static function create(string $identifier): UuidV2 + { + return (new UuidV2Factory())->createFromString($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; } #[\Override] @@ -73,7 +105,7 @@ protected function getListenerArgs(): array { return [ 'field' => $this->field, - 'localDomain' => \is_int($this->localDomain) ? DceDomain::from($this->localDomain) : $this->localDomain, + 'localDomain' => $this->localDomain instanceof DceDomain ? $this->localDomain->value : $this->localDomain, 'localIdentifier' => $this->localIdentifier, 'node' => $this->node instanceof Nic ? $this->node->address() : $this->node, 'clockSeq' => $this->clockSeq, diff --git a/src/Uuid3.php b/src/Uuid3.php index 2e3a5c8..1559365 100644 --- a/src/Uuid3.php +++ b/src/Uuid3.php @@ -10,6 +10,8 @@ use JetBrains\PhpStorm\ArrayShape; use Ramsey\Identifier\Uuid; use Ramsey\Identifier\Uuid\NamespaceId; +use Ramsey\Identifier\Uuid\UuidV3; +use Ramsey\Identifier\Uuid\UuidV3Factory; /** * Uses a version 3 (name-based) UUID based on the MD5 hash of a @@ -23,19 +25,19 @@ final class Uuid3 extends BaseUuid { /** - * @param NamespaceId|Uuid|string $namespace The UUID namespace to use when creating this version 3 identifier - * @param non-empty-string $name The name used to create the version 3 identifier in the given namespace * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name + * @param NamespaceId|Uuid|string|null $namespace The UUID namespace to use when creating this version 3 identifier + * @param non-empty-string|null $name The name used to create the version 3 identifier in the given namespace * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Identifier\Uuid\UuidFactory::v3() */ public function __construct( - private NamespaceId|Uuid|string $namespace, - private string $name, string $field = 'uuid', ?string $column = null, + private readonly NamespaceId|Uuid|string|null $namespace = null, + private readonly ?string $name = null, bool $nullable = false, ) { $this->field = $field; @@ -43,6 +45,22 @@ public function __construct( $this->nullable = $nullable; } + /** + * Create a new UUIDv3 instance from an existing identifier value. + * + * @param non-empty-string $identifier The identifier to create the Uuid from + */ + public static function create(string $identifier): UuidV3 + { + return (new UuidV3Factory())->createFromString($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; + } + #[\Override] protected function getListenerClass(): string { @@ -51,8 +69,8 @@ protected function getListenerClass(): string #[ArrayShape([ 'field' => 'string', - 'namespace' => 'NamespaceId|Uuid|string', - 'name' => 'string', + 'namespace' => 'NamespaceId|Uuid|string|null', + 'name' => 'string|null', 'nullable' => 'bool', ])] #[\Override] @@ -60,7 +78,7 @@ protected function getListenerArgs(): array { return [ 'field' => $this->field, - 'namespace' => $this->namespace, + 'namespace' => $this->namespace instanceof NamespaceId ? $this->namespace->value : $this->namespace, 'name' => $this->name, 'nullable' => $this->nullable, ]; diff --git a/src/Uuid4.php b/src/Uuid4.php index 176aa47..34c8a4b 100644 --- a/src/Uuid4.php +++ b/src/Uuid4.php @@ -8,6 +8,8 @@ use Cycle\ORM\Entity\Behavior\Identifier\Uuid as BaseUuid; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use JetBrains\PhpStorm\ArrayShape; +use Ramsey\Identifier\Uuid\UuidV4; +use Ramsey\Identifier\Uuid\UuidV4Factory; /** * Uses a version 4 (random) UUID @@ -36,6 +38,22 @@ public function __construct( $this->nullable = $nullable; } + /** + * Create a new UUIDv4 instance from an existing identifier value. + * + * @param non-empty-string $identifier The identifier to create the Uuid from + */ + public static function create(string $identifier): UuidV4 + { + return (new UuidV4Factory())->createFromString($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; + } + #[\Override] protected function getListenerClass(): string { diff --git a/src/Uuid5.php b/src/Uuid5.php index 04fb1b4..6296add 100644 --- a/src/Uuid5.php +++ b/src/Uuid5.php @@ -10,6 +10,8 @@ use JetBrains\PhpStorm\ArrayShape; use Ramsey\Identifier\Uuid; use Ramsey\Identifier\Uuid\NamespaceId; +use Ramsey\Identifier\Uuid\UuidV5; +use Ramsey\Identifier\Uuid\UuidV5Factory; /** * Uses a version 5 (name-based) UUID based on the SHA-1 hash of a @@ -23,19 +25,19 @@ final class Uuid5 extends BaseUuid { /** - * @param NamespaceId|Uuid|string $namespace The UUID namespace to use when creating this version 5 identifier - * @param non-empty-string $name The name used to create the version 5 identifier in the given namespace * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name + * @param NamespaceId|Uuid|string|null $namespace The UUID namespace to use when creating this version 5 identifier + * @param non-empty-string|null $name The name used to create the version 5 identifier in the given namespace * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Identifier\Uuid\UuidFactory::v5() */ public function __construct( - private NamespaceId|Uuid|string $namespace, - private string $name, string $field = 'uuid', ?string $column = null, + private readonly NamespaceId|Uuid|string|null $namespace = null, + private readonly ?string $name = null, bool $nullable = false, ) { $this->field = $field; @@ -43,6 +45,22 @@ public function __construct( $this->nullable = $nullable; } + /** + * Create a new UUIDv5 instance from an existing identifier value. + * + * @param non-empty-string $identifier The identifier to create the Uuid from + */ + public static function create(string $identifier): UuidV5 + { + return (new UuidV5Factory())->createFromString($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; + } + #[\Override] protected function getListenerClass(): string { @@ -51,8 +69,8 @@ protected function getListenerClass(): string #[ArrayShape([ 'field' => 'string', - 'namespace' => 'NamespaceId|Uuid|string', - 'name' => 'string', + 'namespace' => 'NamespaceId|Uuid|string|null', + 'name' => 'string|null', 'nullable' => 'bool', ])] #[\Override] diff --git a/src/Uuid6.php b/src/Uuid6.php index 92d83c5..b4a1711 100644 --- a/src/Uuid6.php +++ b/src/Uuid6.php @@ -9,6 +9,8 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use JetBrains\PhpStorm\ArrayShape; use Ramsey\Identifier\Service\Nic\Nic; +use Ramsey\Identifier\Uuid\UuidV6; +use Ramsey\Identifier\Uuid\UuidV6Factory; /** * Uses a version 6 (ordered-time) UUID from a host ID, sequence number, @@ -21,6 +23,13 @@ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid6 extends BaseUuid { + /** + * @var Nic|int<0, 281474976710655>|non-empty-string|null $node + */ + private Nic|int|string|null $node = null; + + private ?int $clockSeq = null; + /** * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name @@ -36,13 +45,31 @@ final class Uuid6 extends BaseUuid public function __construct( string $field = 'uuid', ?string $column = null, - private Nic|int|string|null $node = null, - private ?int $clockSeq = null, + Nic|int|string|null $node = null, + ?int $clockSeq = null, bool $nullable = false, ) { $this->field = $field; $this->column = $column; $this->nullable = $nullable; + $this->node = $node; + $this->clockSeq = $clockSeq; + } + + /** + * Create a new UUIDv6 instance from an existing identifier value. + * + * @param non-empty-string $identifier The identifier to create the Uuid from + */ + public static function create(string $identifier): UuidV6 + { + return (new UuidV6Factory())->createFromString($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; } #[\Override] diff --git a/src/Uuid7.php b/src/Uuid7.php index df5f505..3355564 100644 --- a/src/Uuid7.php +++ b/src/Uuid7.php @@ -8,6 +8,8 @@ use Cycle\ORM\Entity\Behavior\Identifier\Uuid as BaseUuid; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use JetBrains\PhpStorm\ArrayShape; +use Ramsey\Identifier\Uuid\UuidV7; +use Ramsey\Identifier\Uuid\UuidV7Factory; /** * Uses a version 7 (Unix Epoch Time) UUID @@ -36,6 +38,22 @@ public function __construct( $this->nullable = $nullable; } + /** + * Create a new UUIDv7 instance from an existing identifier value. + * + * @param non-empty-string $identifier The identifier to create the Uuid from + */ + public static function create(string $identifier): UuidV7 + { + return (new UuidV7Factory())->createFromString($identifier); + } + + #[\Override] + protected function getTypecast(): array + { + return [self::class, 'create']; + } + #[\Override] protected function getListenerClass(): string { diff --git a/tests/Identifier/Fixtures/Combined/MultipleIdentifiers.php b/tests/Identifier/Fixtures/Combined/MultipleIdentifiers.php index 08193fe..ad70b03 100644 --- a/tests/Identifier/Fixtures/Combined/MultipleIdentifiers.php +++ b/tests/Identifier/Fixtures/Combined/MultipleIdentifiers.php @@ -6,23 +6,27 @@ use Cycle\Annotated\Annotation\Column; use Cycle\Annotated\Annotation\Entity; -use Cycle\ORM\Entity\Behavior\Identifier\Ulid; -use Cycle\ORM\Entity\Behavior\Identifier\Uuid4; -use Ramsey\Identifier\Ulid as UlidInterface; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Snowflake; +use Ramsey\Identifier\Ulid; use Ramsey\Identifier\Uuid; /** * @Entity - * @Uuid4 - * @Uuid4(field="uuidNullable", column="uuid_nullable", nullable=true) - * @Ulid(field="ulid") - * @Ulid(field="ulidNullable", column="ulid_nullable", nullable=true) + * @Identifier\Uuid4 + * @Identifier\Uuid4(field="uuidNullable", column="uuid_nullable", nullable=true) + * @Identifier\Ulid(field="ulid") + * @Identifier\Ulid(field="ulidNullable", column="ulid_nullable", nullable=true) + * @Identifier\SnowflakeGeneric(field="snowflake") + * @Identifier\SnowflakeGeneric(field="snowflakeNullable", column="snowflake_nullable", nullable=true) */ #[Entity] -#[Uuid4] -#[Uuid4(field: 'uuidNullable', column: 'uuid_nullable', nullable: true)] -#[Ulid(field: 'ulid')] -#[Uuid4(field: 'ulidNullable', column: 'ulid_nullable', nullable: true)] +#[Identifier\Uuid4] +#[Identifier\Uuid4(field: 'uuidNullable', column: 'uuid_nullable', nullable: true)] +#[Identifier\Ulid(field: 'ulid')] +#[Identifier\Uuid4(field: 'ulidNullable', column: 'ulid_nullable', nullable: true)] +#[Identifier\SnowflakeGeneric(field: 'snowflake')] +#[Identifier\SnowflakeGeneric(field: 'snowflakeNullable', column: 'snowflake_nullable', nullable: true)] class MultipleIdentifiers { /** @@ -41,11 +45,23 @@ class MultipleIdentifiers * @Column(type="ulid") */ #[Column(type: 'ulid')] - public UlidInterface $ulid; + public Ulid $ulid; /** * @Column(type="ulid", nullable=true) */ #[Column(type: 'ulid')] - public ?UlidInterface $ulidNullable = null; + public ?Ulid $ulidNullable = null; + + /** + * @Column(type="snowflake") + */ + #[Column(type: 'snowflake')] + public Snowflake $snowflake; + + /** + * @Column(type="snowflake", nullable=true) + */ + #[Column(type: 'snowflake')] + public ?Snowflake $snowflakeNullable = null; } diff --git a/tests/Identifier/Fixtures/Snowflake/AllSnowflake.php b/tests/Identifier/Fixtures/Snowflake/AllSnowflake.php new file mode 100644 index 0000000..cb56860 --- /dev/null +++ b/tests/Identifier/Fixtures/Snowflake/AllSnowflake.php @@ -0,0 +1,63 @@ +assertTrue($fields->has('uuid')); $this->assertTrue($fields->hasColumn('uuid')); $this->assertSame('uuid', $fields->get('uuid')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('uuid')->getTypecast()); + $this->assertSame([Uuid::class, 'create'], $fields->get('uuid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('uuid')->getGenerated()); $this->assertTrue($fields->has('uuidNullable')); $this->assertTrue($fields->hasColumn('uuid_nullable')); $this->assertSame('uuid', $fields->get('uuidNullable')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('uuidNullable')->getTypecast()); + $this->assertSame([Uuid::class, 'create'], $fields->get('uuidNullable')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('uuidNullable')->getGenerated()); $this->assertTrue($fields->has('ulid')); $this->assertTrue($fields->hasColumn('ulid')); $this->assertSame('ulid', $fields->get('ulid')->getType()); - $this->assertSame([Ulid::class, 'fromString'], $fields->get('ulid')->getTypecast()); + $this->assertSame([Ulid::class, 'create'], $fields->get('ulid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('ulid')->getGenerated()); $this->assertTrue($fields->has('ulidNullable')); $this->assertTrue($fields->hasColumn('ulid_nullable')); $this->assertSame('ulid', $fields->get('ulidNullable')->getType()); - $this->assertSame([Ulid::class, 'fromString'], $fields->get('ulidNullable')->getTypecast()); + $this->assertSame([Ulid::class, 'create'], $fields->get('ulidNullable')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('ulidNullable')->getGenerated()); } + #[\Override] public function setUp(): void { parent::setUp(); diff --git a/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php b/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php index 95322c3..74e9bfe 100644 --- a/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php +++ b/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php @@ -7,6 +7,7 @@ use Cycle\ORM\Entity\Behavior\Identifier\Tests\Fixtures\Combined\MultipleIdentifiers; use Cycle\ORM\Entity\Behavior\Identifier\Tests\Functional\Driver\Common\BaseTest; use Cycle\ORM\Entity\Behavior\Identifier\Tests\Traits\TableTrait; +use Cycle\ORM\Entity\Behavior\Identifier\Listener\SnowflakeGeneric as SnowflakeGenericListener; use Cycle\ORM\Entity\Behavior\Identifier\Listener\Ulid as UlidListener; use Cycle\ORM\Entity\Behavior\Identifier\Listener\Uuid4 as Uuid4Listener; use Cycle\ORM\Entity\Behavior\Identifier\Ulid; @@ -15,6 +16,8 @@ use Cycle\ORM\Schema; use Cycle\ORM\SchemaInterface; use Cycle\ORM\Select; +use Ramsey\Identifier\Snowflake\GenericSnowflakeFactory; +use Ramsey\Identifier\Snowflake as SnowflakeInterface; use Ramsey\Identifier\Ulid as UlidInterface; use Ramsey\Identifier\Ulid\UlidFactory; use Ramsey\Identifier\Uuid\UntypedUuid; @@ -29,13 +32,16 @@ public function testAssignManually(): void $this->withListeners([ Uuid4Listener::class, UlidListener::class, + SnowflakeGenericListener::class, ]); $identifiers = new MultipleIdentifiers(); $identifiers->uuid = (new UuidFactory())->v4(); $identifiers->ulid = (new UlidFactory())->create(); + $identifiers->snowflake = (new GenericSnowflakeFactory(0, 0))->create(); $uuidBytes = $identifiers->uuid->toBytes(); $ulidBytes = $identifiers->ulid->toBytes(); + $snowflakeBytes = $identifiers->snowflake->toBytes(); $this->save($identifiers); @@ -44,6 +50,7 @@ public function testAssignManually(): void $this->assertSame($uuidBytes, $data->uuid->toBytes()); $this->assertSame($ulidBytes, $data->ulid->toBytes()); + $this->assertSame($snowflakeBytes, $data->snowflake->toBytes()); } public function testWithNullableTrue(): void @@ -51,11 +58,13 @@ public function testWithNullableTrue(): void $this->withListeners([ Uuid4Listener::class, UlidListener::class, + SnowflakeGenericListener::class, ]); $identifiers = new MultipleIdentifiers(); $identifiers->uuid = (new UuidFactory())->v4(); $identifiers->ulid = (new UlidFactory())->create(); + $identifiers->snowflake = (new GenericSnowflakeFactory(0, 0))->create(); $this->save($identifiers); @@ -64,6 +73,7 @@ public function testWithNullableTrue(): void $this->assertNull($data[0]['uuid_nullable']); $this->assertNull($data[0]['ulid_nullable']); + $this->assertNull($data[0]['snowflake_nullable']); } public function testCombined(): void @@ -71,6 +81,7 @@ public function testCombined(): void $this->withListeners([ Uuid4Listener::class, UlidListener::class, + SnowflakeGenericListener::class, ]); $identifiers = new MultipleIdentifiers(); @@ -81,12 +92,16 @@ public function testCombined(): void $this->assertInstanceOf(UntypedUuid::class, $data->uuid); $this->assertInstanceOf(UlidInterface::class, $data->ulid); + $this->assertInstanceOf(SnowflakeInterface::class, $data->snowflake); $this->assertNull($data->uuidNullable); $this->assertNull($data->ulidNullable); + $this->assertNull($data->snowflakeNullable); $this->assertIsString($data->uuid->toBytes()); $this->assertIsString($data->uuid->toString()); $this->assertIsString($data->ulid->toBytes()); $this->assertIsString($data->ulid->toString()); + $this->assertIsString($data->snowflake->toBytes()); + $this->assertIsString($data->snowflake->toString()); } public function testComparison(): void @@ -94,6 +109,7 @@ public function testComparison(): void $this->withListeners([ Uuid4Listener::class, UlidListener::class, + SnowflakeGenericListener::class, ]); $expectedDate = '2025-06-17 03:24:36.160 +00:00'; @@ -101,6 +117,7 @@ public function testComparison(): void $identifiers = new MultipleIdentifiers(); $identifiers->uuid = (new UuidFactory())->createFromString('01977bea-d1c0-7154-87bb-6550974155c2'); $identifiers->ulid = (new UlidFactory())->createFromString('01JXXYNME0E5A8FEV5A2BM2NE2'); + $identifiers->snowflake = (new GenericSnowflakeFactory(0, 0))->createFromInteger(7340580095540599922); $this->save($identifiers); @@ -109,31 +126,45 @@ public function testComparison(): void $this->assertSame($expectedDate, $data->uuid->getDateTime()->format('Y-m-d H:i:s.v P')); $this->assertSame($expectedDate, $data->ulid->getDateTime()->format('Y-m-d H:i:s.v P')); + $this->assertSame($expectedDate, $data->snowflake->getDateTime()->format('Y-m-d H:i:s.v P')); $this->assertTrue($data->uuid->equals($data->ulid)); + $this->assertFalse($data->uuid->equals($data->snowflake)); } public function withListeners(array|string $listeners): void { + $factory = new GenericSnowflakeFactory(0, 0); + $this->withSchema(new Schema([ MultipleIdentifiers::class => [ SchemaInterface::ROLE => 'multiple_identifier', SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'multiple_identifiers', SchemaInterface::PRIMARY_KEY => 'ulid', - SchemaInterface::COLUMNS => ['uuid', 'uuid_nullable', 'ulid', 'ulid_nullable'], + SchemaInterface::COLUMNS => [ + 'uuid', + 'uuid_nullable', + 'ulid', + 'ulid_nullable', + 'snowflake', + 'snowflake_nullable', + ], SchemaInterface::LISTENERS => [$listeners], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], SchemaInterface::TYPECAST => [ - 'uuid' => [Uuid::class, 'fromString'], - 'uuid_nullable' => [Uuid::class, 'fromString'], - 'ulid' => [Ulid::class, 'fromString'], - 'ulid_nullable' => [Ulid::class, 'fromString'], + 'uuid' => [Uuid::class, 'create'], + 'uuid_nullable' => [Uuid::class, 'create'], + 'ulid' => [Ulid::class, 'create'], + 'ulid_nullable' => [Ulid::class, 'create'], + 'snowflake' => [$factory, 'createFromInteger'], + 'snowflake_nullable' => [$factory, 'createFromInteger'], ], ], ])); } + #[\Override] public function setUp(): void { parent::setUp(); @@ -145,6 +176,8 @@ public function setUp(): void 'uuid_nullable' => 'string,nullable', 'ulid' => 'string', 'ulid_nullable' => 'string,nullable', + 'snowflake' => 'snowflake', + 'snowflake_nullable' => 'snowflake,nullable', ], ); } diff --git a/tests/Identifier/Functional/Driver/Common/Snowflake/ListenerTest.php b/tests/Identifier/Functional/Driver/Common/Snowflake/ListenerTest.php new file mode 100644 index 0000000..f35337f --- /dev/null +++ b/tests/Identifier/Functional/Driver/Common/Snowflake/ListenerTest.php @@ -0,0 +1,324 @@ + [ + 'listeners' => [ + SnowflakeGenericListener::class, + [ + 'field' => 'generic', + 'node' => 10, + 'epochOffset' => 1662744255000, + ], + ], + 'field' => 'generic', + 'expectedClass' => GenericSnowflake::class, + 'expectedLength' => 18, + ], + 'discord' => [ + 'listeners' => [ + SnowflakeDiscordListener::class, + [ + 'field' => 'discord', + 'workerId' => 10, + 'processId' => 20, + ], + ], + 'field' => 'discord', + 'expectedClass' => DiscordSnowflake::class, + 'expectedLength' => 19, + ], + 'discord-omit-process-id' => [ + 'listeners' => [ + SnowflakeDiscordListener::class, + [ + 'field' => 'discord', + 'workerId' => 10, + ], + ], + 'field' => 'discord', + 'expectedClass' => DiscordSnowflake::class, + 'expectedLength' => 19, + ], + 'instagram' => [ + 'listeners' => [ + SnowflakeInstagramListener::class, + [ + 'field' => 'instagram', + 'shardId' => 10, + ], + ], + 'field' => 'instagram', + 'expectedClass' => InstagramSnowflake::class, + 'expectedLength' => 19, + ], + 'mastodon' => [ + 'listeners' => [ + SnowflakeMastodonListener::class, + [ + 'field' => 'mastodon', + 'tableName' => 'foo', + ], + ], + 'field' => 'mastodon', + 'expectedClass' => MastodonSnowflake::class, + 'expectedLength' => 18, + ], + 'twitter' => [ + 'listeners' => [ + SnowflakeTwitterListener::class, + [ + 'field' => 'twitter', + 'machineId' => 10, + ], + ], + 'field' => 'twitter', + 'expectedClass' => TwitterSnowflake::class, + 'expectedLength' => 19, + ], + ]; + } + + public static function snowflakeExceptionDataProvider(): array + { + return [ + 'generic-node-minimum' => [ + 'listenerClass' => SnowflakeGenericListener::class, + 'defaults' => [-1, 1662744250000], + 'expectedException' => \InvalidArgumentException::class, + ], + 'generic-node-maximum' => [ + 'listenerClass' => SnowflakeGenericListener::class, + 'defaults' => [1024, 1662744250000], + 'expectedException' => \InvalidArgumentException::class, + ], + 'discord-worker-id-minimum' => [ + 'listenerClass' => SnowflakeDiscordListener::class, + 'defaults' => [-1, 30], + 'expectedException' => \InvalidArgumentException::class, + ], + 'discord-worker-id-maximum' => [ + 'listenerClass' => SnowflakeDiscordListener::class, + 'defaults' => [281474976710656, 30], + 'expectedException' => \InvalidArgumentException::class, + ], + 'discord-process-id-minimum' => [ + 'listenerClass' => SnowflakeDiscordListener::class, + 'defaults' => [20, -1], + 'expectedException' => \InvalidArgumentException::class, + ], + 'discord-process-id-maximum' => [ + 'listenerClass' => SnowflakeDiscordListener::class, + 'defaults' => [20, 281474976710656], + 'expectedException' => \InvalidArgumentException::class, + ], + 'instagram-shard-id-minimum' => [ + 'listenerClass' => SnowflakeInstagramListener::class, + 'defaults' => [-1], + 'expectedException' => \InvalidArgumentException::class, + ], + 'instagram-shard-id-maximum' => [ + 'listenerClass' => SnowflakeInstagramListener::class, + 'defaults' => [1024], + 'expectedException' => \InvalidArgumentException::class, + ], + 'twitter-machine-id-minimum' => [ + 'listenerClass' => SnowflakeTwitterListener::class, + 'defaults' => [-1], + 'expectedException' => \InvalidArgumentException::class, + ], + 'twitter-machine-id-maximum' => [ + 'listenerClass' => SnowflakeTwitterListener::class, + 'defaults' => [1024], + 'expectedException' => \InvalidArgumentException::class, + ], + ]; + } + + public function testNullable(): void + { + $this->withListeners([ + SnowflakeGenericListener::class, + [ + 'field' => 'generic', + 'nullable' => true, + ], + SnowflakeGenericListener::class, + [ + 'field' => 'discord', + 'nullable' => true, + ], + SnowflakeGenericListener::class, + [ + 'field' => 'instagram', + 'nullable' => true, + ], + SnowflakeGenericListener::class, + [ + 'field' => 'mastodon', + 'nullable' => true, + ], + SnowflakeGenericListener::class, + [ + 'field' => 'twitter', + 'nullable' => true, + ], + ]); + + $entity = new AllSnowflake(); + $this->save($entity); + + $select = new Select($this->orm->with(heap: new Heap()), AllSnowflake::class); + $data = $select->fetchOne(); + + $this->assertNull($data->generic); + $this->assertNull($data->discord); + $this->assertNull($data->instagram); + $this->assertNull($data->mastodon); + $this->assertNull($data->twitter); + } + + public function testAssignManually(): void + { + $this->withListeners(); + + $entity = new AllSnowflake(); + $entity->generic = (new GenericSnowflakeFactory(10, 1662744255000))->create(); + $entity->discord = (new DiscordSnowflakeFactory(10, 20))->create(); + $entity->instagram = (new InstagramSnowflakeFactory(10))->create(); + $entity->mastodon = (new MastodonSnowflakeFactory('users'))->create(); + $entity->twitter = (new TwitterSnowflakeFactory(10))->create(); + + $this->save($entity); + + $select = new Select($this->orm->with(heap: new Heap()), AllSnowflake::class); + $data = $select->fetchOne(); + + $this->assertSame($entity->generic->toString(), $data->generic->toString()); + $this->assertSame($entity->discord->toString(), $data->discord->toString()); + $this->assertSame($entity->instagram->toString(), $data->instagram->toString()); + $this->assertSame($entity->mastodon->toString(), $data->mastodon->toString()); + $this->assertSame($entity->twitter->toString(), $data->twitter->toString()); + } + + /** + * @dataProvider snowflakeGenerationDataProvider + */ + public function testSnowflakeGeneration( + array $listeners, + string $field, + string $expectedClass, + int $expectedLength, + ): void { + $this->withListeners($listeners); + + $entity = new AllSnowflake(); + $this->save($entity); + + $select = new Select($this->orm->with(heap: new Heap()), AllSnowflake::class); + $data = $select->fetchOne(); + + $snowflake = $data->$field; + $this->assertInstanceOf($expectedClass, $snowflake); + $this->assertIsString($snowflake->toString()); + $this->assertSame($expectedLength, \strlen($snowflake->toString())); + } + + /** + * @dataProvider snowflakeExceptionDataProvider + */ + public function testSnowflakeException( + string $listenerClass, + array $defaults, + string $expectedException, + ): void { + $this->expectException($expectedException); + + if (\class_exists($listenerClass) && \method_exists($listenerClass, 'setDefaults')) { + $listenerClass::setDefaults(...$defaults); + } + } + + public function withListeners(array|string|null $listeners = null): void + { + $this->withSchema(new Schema([ + AllSnowflake::class => [ + SchemaInterface::ROLE => 'all_snowflake', + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'all_snowflakes', + SchemaInterface::PRIMARY_KEY => 'id', + SchemaInterface::COLUMNS => ['id', 'generic', 'discord', 'instagram', 'mastodon', 'twitter'], + SchemaInterface::LISTENERS => $listeners ? [$listeners] : [], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [], + SchemaInterface::TYPECAST => [ + 'generic' => [SnowflakeGeneric::class, 'create', [10]], + 'discord' => [SnowflakeDiscord::class, 'create'], + 'instagram' => [SnowflakeInstagram::class, 'create'], + 'mastodon' => [SnowflakeMastodon::class, 'create'], + 'twitter' => [SnowflakeTwitter::class, 'create'], + ], + ], + ])); + } + + #[\Override] + public function setUp(): void + { + parent::setUp(); + + SnowflakeGenericListener::setDefaults(0, 0); + SnowflakeDiscordListener::setDefaults(0, null); + SnowflakeInstagramListener::setDefaults(0); + SnowflakeTwitterListener::setDefaults(0); + + $this->makeTable( + 'all_snowflakes', + [ + 'id' => 'integer', + 'generic' => 'snowflake,nullable', + 'discord' => 'snowflake,nullable', + 'instagram' => 'snowflake,nullable', + 'mastodon' => 'snowflake,nullable', + 'twitter' => 'snowflake,nullable', + ], + ); + } +} diff --git a/tests/Identifier/Functional/Driver/Common/Snowflake/SnowflakeTest.php b/tests/Identifier/Functional/Driver/Common/Snowflake/SnowflakeTest.php new file mode 100644 index 0000000..142db35 --- /dev/null +++ b/tests/Identifier/Functional/Driver/Common/Snowflake/SnowflakeTest.php @@ -0,0 +1,122 @@ +compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(User::class)->getFields(); + + $this->assertTrue($fields->has('snowflake')); + $this->assertTrue($fields->hasColumn('snowflake')); + $this->assertSame('snowflake', $fields->get('snowflake')->getType()); + $this->assertIsArray($fields->get('snowflake')->getTypecast()); + $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('snowflake')->getGenerated()); + $this->assertSame(1, $fields->count()); + } + + /** + * @dataProvider readersDataProvider + */ + public function testAddColumn(ReaderInterface $reader): void + { + $this->compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(Post::class)->getFields(); + + $this->assertTrue($fields->has('customSnowflake')); + $this->assertTrue($fields->hasColumn('custom_snowflake')); + $this->assertSame('snowflake', $fields->get('customSnowflake')->getType()); + $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('customSnowflake')->getGenerated()); + } + + /** + * @dataProvider readersDataProvider + */ + public function testMultipleSnowflake(ReaderInterface $reader): void + { + $this->compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(MultipleSnowflake::class)->getFields(); + + $this->assertTrue($fields->has('snowflake')); + $this->assertTrue($fields->hasColumn('snowflake')); + $this->assertSame('snowflake', $fields->get('snowflake')->getType()); + $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('snowflake')->getGenerated()); + + $this->assertTrue($fields->has('discord')); + $this->assertTrue($fields->hasColumn('discord')); + $this->assertSame('snowflake', $fields->get('discord')->getType()); + $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('discord')->getGenerated()); + + $this->assertTrue($fields->has('instagram')); + $this->assertTrue($fields->hasColumn('instagram')); + $this->assertSame('snowflake', $fields->get('instagram')->getType()); + $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('instagram')->getGenerated()); + + $this->assertTrue($fields->has('mastodon')); + $this->assertTrue($fields->hasColumn('mastodon')); + $this->assertSame('snowflake', $fields->get('mastodon')->getType()); + $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('mastodon')->getGenerated()); + + $this->assertTrue($fields->has('twitter')); + $this->assertTrue($fields->hasColumn('twitter')); + $this->assertSame('snowflake', $fields->get('twitter')->getType()); + $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('twitter')->getGenerated()); + } + + /** + * @dataProvider readersDataProvider + */ + public function testAddNullableColumn(ReaderInterface $reader): void + { + $this->compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(NullableSnowflake::class)->getFields(); + + $this->assertTrue($fields->has('notDefinedSnowflake')); + $this->assertTrue($fields->hasColumn('not_defined_snowflake')); + $this->assertSame('snowflake', $fields->get('notDefinedSnowflake')->getType()); + $this->assertTrue( + $this->registry + ->getTableSchema($this->registry->getEntity(NullableSnowflake::class)) + ->column('not_defined_snowflake') + ->isNullable(), + ); + $this->assertNull($fields->get('notDefinedSnowflake')->getGenerated()); + } + + #[\Override] + public function setUp(): void + { + parent::setUp(); + + $locator = new ClassLocator((new Finder())->files()->in([\dirname(__DIR__, 4) . '/Fixtures/Snowflake'])); + $reader = new AttributeReader(); + $this->tokenizer = new TokenizerEntityLocator($locator, $reader); + } +} diff --git a/tests/Identifier/Functional/Driver/Common/Ulid/ListenerTest.php b/tests/Identifier/Functional/Driver/Common/Ulid/ListenerTest.php index 8786353..0ef4ed0 100644 --- a/tests/Identifier/Functional/Driver/Common/Ulid/ListenerTest.php +++ b/tests/Identifier/Functional/Driver/Common/Ulid/ListenerTest.php @@ -4,7 +4,7 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Tests\Functional\Driver\Common\Ulid; -use Cycle\ORM\Entity\Behavior\Identifier\Tests\Fixtures\Ulid\User; +use Cycle\ORM\Entity\Behavior\Identifier\Tests\Fixtures\Ulid\AllUlid; use Cycle\ORM\Entity\Behavior\Identifier\Tests\Functional\Driver\Common\BaseTest; use Cycle\ORM\Entity\Behavior\Identifier\Tests\Traits\TableTrait; use Cycle\ORM\Entity\Behavior\Identifier\Listener\Ulid as UlidListener; @@ -20,87 +20,89 @@ abstract class ListenerTest extends BaseTest { use TableTrait; - public function testAssignManually(): void + public function testNullable(): void { - $this->withListeners(UlidListener::class); - - $user = new User(); - $user->ulid = (new UlidFactory())->create(); - $bytes = $user->ulid->toBytes(); + $this->withListeners([ + UlidListener::class, + [ + 'field' => 'ulid', + 'nullable' => true, + ], + ]); - $this->save($user); + $entity = new AllUlid(); + $this->save($entity); - $select = new Select($this->orm->with(heap: new Heap()), User::class); + $select = new Select($this->orm->with(heap: new Heap()), AllUlid::class); $data = $select->fetchOne(); - $this->assertSame($bytes, $data->ulid->toBytes()); + $this->assertNull($data->ulid); } - public function testWithNullableTrue(): void + public function testAssignManually(): void { - $this->withListeners([ - UlidListener::class, - [ - 'field' => 'foo_ulid', - 'nullable' => true, - ], - ]); + $this->withListeners(); - $user = new User(); - $user->ulid = (new UlidFactory())->create(); + $entity = new AllUlid(); + $entity->ulid = (new UlidFactory())->create(); - $this->save($user); + $this->save($entity); - $select = new Select($this->orm->with(heap: new Heap()), User::class); - $data = $select->fetchData(); + $select = new Select($this->orm->with(heap: new Heap()), AllUlid::class); + $data = $select->fetchOne(); - $this->assertNull($data[0]['foo_ulid']); + $this->assertSame($entity->ulid->toString(), $data->ulid->toString()); } public function testUlid(): void { - $this->withListeners(UlidListener::class); + $this->withListeners([ + UlidListener::class, + [ + 'field' => 'ulid', + ], + ]); - $user = new User(); - $this->save($user); + $entity = new AllUlid(); + $this->save($entity); - $select = new Select($this->orm->with(heap: new Heap()), User::class); + $select = new Select($this->orm->with(heap: new Heap()), AllUlid::class); $data = $select->fetchOne(); $this->assertInstanceOf(UlidInterface::class, $data->ulid); - $this->assertIsString($data->ulid->toBytes()); $this->assertIsString($data->ulid->toString()); + $this->assertSame(26, \strlen($data->ulid->toString())); } - public function withListeners(array|string $listeners): void + public function withListeners(array|string|null $listeners = null): void { $this->withSchema(new Schema([ - User::class => [ - SchemaInterface::ROLE => 'user', + AllUlid::class => [ + SchemaInterface::ROLE => 'all_ulid', SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'users', - SchemaInterface::PRIMARY_KEY => 'ulid', - SchemaInterface::COLUMNS => ['ulid', 'foo_ulid'], - SchemaInterface::LISTENERS => [$listeners], + SchemaInterface::TABLE => 'all_ulids', + SchemaInterface::PRIMARY_KEY => 'id', + SchemaInterface::COLUMNS => ['id', 'ulid'], + SchemaInterface::LISTENERS => $listeners ? [$listeners] : [], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], SchemaInterface::TYPECAST => [ - 'ulid' => [Ulid::class, 'fromString'], - 'foo_ulid' => [Ulid::class, 'fromString'], + 'ulid' => [Ulid::class, 'create'], ], ], ])); } + #[\Override] public function setUp(): void { parent::setUp(); $this->makeTable( - 'users', + 'all_ulids', [ - 'ulid' => 'string', - 'foo_ulid' => 'string,nullable', + 'id' => 'integer', + 'ulid' => 'ulid,nullable', ], ); } diff --git a/tests/Identifier/Functional/Driver/Common/Ulid/UlidTest.php b/tests/Identifier/Functional/Driver/Common/Ulid/UlidTest.php index b35a08a..6938383 100644 --- a/tests/Identifier/Functional/Driver/Common/Ulid/UlidTest.php +++ b/tests/Identifier/Functional/Driver/Common/Ulid/UlidTest.php @@ -35,7 +35,7 @@ public function testColumnExist(ReaderInterface $reader): void $this->assertTrue($fields->has('ulid')); $this->assertTrue($fields->hasColumn('ulid')); $this->assertSame('ulid', $fields->get('ulid')->getType()); - $this->assertSame([Ulid::class, 'fromString'], $fields->get('ulid')->getTypecast()); + $this->assertSame([Ulid::class, 'create'], $fields->get('ulid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('ulid')->getGenerated()); $this->assertSame(1, $fields->count()); } @@ -52,7 +52,7 @@ public function testAddColumn(ReaderInterface $reader): void $this->assertTrue($fields->has('customUlid')); $this->assertTrue($fields->hasColumn('custom_ulid')); $this->assertSame('ulid', $fields->get('customUlid')->getType()); - $this->assertSame([Ulid::class, 'fromString'], $fields->get('customUlid')->getTypecast()); + $this->assertSame([Ulid::class, 'create'], $fields->get('customUlid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('customUlid')->getGenerated()); } @@ -68,19 +68,19 @@ public function testMultipleUlid(ReaderInterface $reader): void $this->assertTrue($fields->has('ulid')); $this->assertTrue($fields->hasColumn('ulid')); $this->assertSame('ulid', $fields->get('ulid')->getType()); - $this->assertSame([Ulid::class, 'fromString'], $fields->get('ulid')->getTypecast()); + $this->assertSame([Ulid::class, 'create'], $fields->get('ulid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('ulid')->getGenerated()); $this->assertTrue($fields->has('fooUlid')); $this->assertTrue($fields->hasColumn('foo_ulid')); $this->assertSame('ulid', $fields->get('fooUlid')->getType()); - $this->assertSame([Ulid::class, 'fromString'], $fields->get('fooUlid')->getTypecast()); + $this->assertSame([Ulid::class, 'create'], $fields->get('fooUlid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('fooUlid')->getGenerated()); $this->assertTrue($fields->has('bar')); $this->assertTrue($fields->hasColumn('bar')); $this->assertSame('ulid', $fields->get('bar')->getType()); - $this->assertSame([Ulid::class, 'fromString'], $fields->get('bar')->getTypecast()); + $this->assertSame([Ulid::class, 'create'], $fields->get('bar')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('bar')->getGenerated()); } @@ -96,7 +96,7 @@ public function testAddNullableColumn(ReaderInterface $reader): void $this->assertTrue($fields->has('notDefinedUlid')); $this->assertTrue($fields->hasColumn('not_defined_ulid')); $this->assertSame('ulid', $fields->get('notDefinedUlid')->getType()); - $this->assertSame([Ulid::class, 'fromString'], $fields->get('notDefinedUlid')->getTypecast()); + $this->assertSame([Ulid::class, 'create'], $fields->get('notDefinedUlid')->getTypecast()); $this->assertTrue( $this->registry ->getTableSchema($this->registry->getEntity(NullableUlid::class)) @@ -106,6 +106,7 @@ public function testAddNullableColumn(ReaderInterface $reader): void $this->assertNull($fields->get('notDefinedUlid')->getGenerated()); } + #[\Override] public function setUp(): void { parent::setUp(); diff --git a/tests/Identifier/Functional/Driver/Common/Uuid/ListenerTest.php b/tests/Identifier/Functional/Driver/Common/Uuid/ListenerTest.php index f995299..d493fb3 100644 --- a/tests/Identifier/Functional/Driver/Common/Uuid/ListenerTest.php +++ b/tests/Identifier/Functional/Driver/Common/Uuid/ListenerTest.php @@ -4,7 +4,7 @@ namespace Cycle\ORM\Entity\Behavior\Identifier\Tests\Functional\Driver\Common\Uuid; -use Cycle\ORM\Entity\Behavior\Identifier\Tests\Fixtures\Uuid\User; +use Cycle\ORM\Entity\Behavior\Identifier\Tests\Fixtures\Uuid\AllUuid; use Cycle\ORM\Entity\Behavior\Identifier\Tests\Functional\Driver\Common\BaseTest; use Cycle\ORM\Entity\Behavior\Identifier\Tests\Traits\TableTrait; use Cycle\ORM\Entity\Behavior\Identifier\Listener\Uuid1 as Uuid1Listener; @@ -14,286 +14,340 @@ use Cycle\ORM\Entity\Behavior\Identifier\Listener\Uuid5 as Uuid5Listener; use Cycle\ORM\Entity\Behavior\Identifier\Listener\Uuid6 as Uuid6Listener; use Cycle\ORM\Entity\Behavior\Identifier\Listener\Uuid7 as Uuid7Listener; -use Cycle\ORM\Entity\Behavior\Identifier\Uuid; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid1; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid2; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid3; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid4; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid5; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid6; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid7; use Cycle\ORM\Heap\Heap; use Cycle\ORM\Schema; use Cycle\ORM\SchemaInterface; use Cycle\ORM\Select; use Ramsey\Identifier\Uuid\DceDomain; use Ramsey\Identifier\Uuid\NamespaceId; -use Ramsey\Identifier\Uuid\UntypedUuid; use Ramsey\Identifier\Uuid\UuidFactory; +use Ramsey\Identifier\Uuid\UuidV1; +use Ramsey\Identifier\Uuid\UuidV2; +use Ramsey\Identifier\Uuid\UuidV3; +use Ramsey\Identifier\Uuid\UuidV4; +use Ramsey\Identifier\Uuid\UuidV5; +use Ramsey\Identifier\Uuid\UuidV6; +use Ramsey\Identifier\Uuid\UuidV7; abstract class ListenerTest extends BaseTest { use TableTrait; - public static function nullableTrueDataProvider(): \Traversable + private UuidFactory $factory; + + public static function uuidGenerationDataProvider(): array { - yield [ - [ - Uuid1Listener::class, - [ - 'nullable' => true, - 'field' => 'optional_uuid', - 'node' => '00000fffffff', - 'clockSeq' => 0xffff, + return [ + 'uuid1' => [ + 'listeners' => [ + Uuid1Listener::class, + [ + 'field' => 'uuid1', + 'node' => '00000fffffff', + 'clockSeq' => 0xffff, + ], ], + 'field' => 'uuid1', + 'expectedClass' => UuidV1::class, + 'expectedVersion' => 1, ], - ]; - yield [ - [ - Uuid2Listener::class, - [ - 'nullable' => true, - 'field' => 'optional_uuid', - 'localDomain' => DceDomain::Person, - 'localIdentifier' => 12345678, + 'uuid2' => [ + 'listeners' => [ + Uuid2Listener::class, + [ + 'field' => 'uuid2', + 'localDomain' => DceDomain::Person, + 'localIdentifier' => 12345678, + 'node' => '3c1239b4f540', + ], ], + 'field' => 'uuid2', + 'expectedClass' => UuidV2::class, + 'expectedVersion' => 2, ], - ]; - yield [ - [ - Uuid3Listener::class, - [ - 'nullable' => true, - 'field' => 'optional_uuid', - 'namespace' => NamespaceId::Url, - 'name' => 'https://example.com/foo', + 'uuid3' => [ + 'listeners' => [ + Uuid3Listener::class, + [ + 'field' => 'uuid3', + 'namespace' => NamespaceId::Url, + 'name' => 'https://example.com/foo', + ], ], + 'field' => 'uuid3', + 'expectedClass' => UuidV3::class, + 'expectedVersion' => 3, ], - ]; - yield [ - [ - Uuid4Listener::class, - [ - 'nullable' => true, - 'field' => 'optional_uuid', + 'uuid4' => [ + 'listeners' => [ + Uuid4Listener::class, + [ + 'field' => 'uuid4', + ], ], + 'field' => 'uuid4', + 'expectedClass' => UuidV4::class, + 'expectedVersion' => 4, ], - ]; - yield [ - [ - Uuid5Listener::class, - [ - 'nullable' => true, - 'field' => 'optional_uuid', - 'namespace' => NamespaceId::Url, - 'name' => 'https://example.com/foo', + 'uuid5' => [ + 'listeners' => [ + Uuid5Listener::class, + [ + 'field' => 'uuid5', + 'namespace' => NamespaceId::Url, + 'name' => 'https://example.com/foo', + ], ], + 'field' => 'uuid5', + 'expectedClass' => UuidV5::class, + 'expectedVersion' => 5, ], - ]; - yield [ - [ - Uuid6Listener::class, - [ - 'nullable' => true, - 'field' => 'optional_uuid', - 'node' => '00000fffffff', - 'clockSeq' => 0x1669, + 'uuid6' => [ + 'listeners' => [ + Uuid6Listener::class, + [ + 'field' => 'uuid6', + 'node' => '00000fffffff', + 'clockSeq' => 0xffff, + ], ], + 'field' => 'uuid6', + 'expectedClass' => UuidV6::class, + 'expectedVersion' => 6, ], - ]; - yield [ - [ - Uuid7Listener::class, - [ - 'nullable' => true, - 'field' => 'optional_uuid', + 'uuid7' => [ + 'listeners' => [ + Uuid7Listener::class, + [ + 'field' => 'uuid7', + ], ], + 'field' => 'uuid7', + 'expectedClass' => UuidV7::class, + 'expectedVersion' => 7, ], ]; } - public function testAssignManually(): void - { - $this->withListeners(Uuid4Listener::class); - - $user = new User(); - $user->uuid = (new UuidFactory())->v4(); - $bytes = $user->uuid->toBytes(); - - $this->save($user); - - $select = new Select($this->orm->with(heap: new Heap()), User::class); - $data = $select->fetchOne(); - - $this->assertSame($bytes, $data->uuid->toBytes()); - } - - /** - * @dataProvider nullableTrueDataProvider - */ - public function testWithNullableTrue(array $listener): void + public static function uuidExceptionDataProvider(): array { - $this->withListeners($listener); - - $user = new User(); - $user->uuid = (new UuidFactory())->v4(); - - $this->save($user); - - $select = new Select($this->orm->with(heap: new Heap()), User::class); - $data = $select->fetchData(); - - $this->assertNull($data[0]['optional_uuid']); + return [ + 'uuid3-undefined-namespace' => [ + 'listeners' => [ + Uuid3Listener::class, + [ + 'field' => 'uuid3', + 'name' => 'https://example.com/foo', + ], + ], + 'expectedException' => \InvalidArgumentException::class, + ], + 'uuid3-undefined-name' => [ + 'listeners' => [ + Uuid3Listener::class, + [ + 'field' => 'uuid3', + 'namespace' => NamespaceId::Url, + ], + ], + 'expectedException' => \InvalidArgumentException::class, + ], + 'uuid5-undefined-namespace' => [ + 'listeners' => [ + Uuid5Listener::class, + [ + 'field' => 'uuid5', + 'name' => 'https://example.com/foo', + ], + ], + 'expectedException' => \InvalidArgumentException::class, + ], + 'uuid5-undefined-name' => [ + 'listeners' => [ + Uuid5Listener::class, + [ + 'field' => 'uuid5', + 'namespace' => NamespaceId::Url, + ], + ], + 'expectedException' => \InvalidArgumentException::class, + ], + ]; } - public function testUuid1(): void + public function testNullable(): void { $this->withListeners([ Uuid1Listener::class, [ - 'node' => '00000fffffff', - 'clockSeq' => 0xffff, + 'field' => 'uuid1', + 'nullable' => true, ], - ]); - - $user = new User(); - $this->save($user); - - $select = new Select($this->orm->with(heap: new Heap()), User::class); - $data = $select->fetchOne(); - - $this->assertInstanceOf(UntypedUuid::class, $data->uuid); - $this->assertSame(1, $data->uuid->getVersion()->value); - $this->assertIsString($data->uuid->toString()); - } - - public function testUuid2(): void - { - $this->withListeners([ Uuid2Listener::class, [ - 'localDomain' => DceDomain::Person, - 'localIdentifier' => 12345678, + 'field' => 'uuid2', + 'nullable' => true, ], - ]); - - $user = new User(); - $this->save($user); - - $select = new Select($this->orm->with(heap: new Heap()), User::class); - $data = $select->fetchOne(); - - $this->assertInstanceOf(UntypedUuid::class, $data->uuid); - $this->assertSame(2, $data->uuid->getVersion()->value); - $this->assertIsString($data->uuid->toString()); - } - - public function testUuid3(): void - { - $this->withListeners([ Uuid3Listener::class, [ - 'namespace' => NamespaceId::Url, - 'name' => 'https://example.com/foo', + 'field' => 'uuid3', + 'nullable' => true, + ], + Uuid4Listener::class, + [ + 'field' => 'uuid4', + 'nullable' => true, + ], + Uuid5Listener::class, + [ + 'field' => 'uuid5', + 'nullable' => true, + ], + Uuid6Listener::class, + [ + 'field' => 'uuid6', + 'nullable' => true, + ], + Uuid7Listener::class, + [ + 'field' => 'uuid7', + 'nullable' => true, ], ]); - $user = new User(); - $this->save($user); + $entity = new AllUuid(); + $this->save($entity); - $select = new Select($this->orm->with(heap: new Heap()), User::class); + $select = new Select($this->orm->with(heap: new Heap()), AllUuid::class); $data = $select->fetchOne(); - $this->assertInstanceOf(UntypedUuid::class, $data->uuid); - $this->assertSame(3, $data->uuid->getVersion()->value); - $this->assertIsString($data->uuid->toString()); + $this->assertNull($data->uuid1); + $this->assertNull($data->uuid2); + $this->assertNull($data->uuid3); + $this->assertNull($data->uuid4); + $this->assertNull($data->uuid5); + $this->assertNull($data->uuid6); + $this->assertNull($data->uuid7); } - public function testUuid4(): void + public function testAssignManually(): void { - $this->withListeners(Uuid4Listener::class); + $this->withListeners(); - $user = new User(); - $this->save($user); + $entity = new AllUuid(); + $entity->uuid1 = $this->factory->v1(); + $entity->uuid2 = $this->factory->v2(); + $entity->uuid3 = $this->factory->v3(NamespaceId::Url, 'https://cycle-orm.dev'); + $entity->uuid4 = $this->factory->v4(); + $entity->uuid5 = $this->factory->v5(NamespaceId::Url, 'https://cycle-orm.dev'); + $entity->uuid6 = $this->factory->v6(); + $entity->uuid7 = $this->factory->v7(); - $select = new Select($this->orm->with(heap: new Heap()), User::class); - $data = $select->fetchOne(); - - $this->assertInstanceOf(UntypedUuid::class, $data->uuid); - $this->assertSame(4, $data->uuid->getVersion()->value); - $this->assertIsString($data->uuid->toString()); - } + $this->save($entity); - public function testUuid5(): void - { - $this->withListeners([ - Uuid5Listener::class, - ['namespace' => NamespaceId::Url, 'name' => 'https://example.com/foo'], - ]); - - $user = new User(); - $this->save($user); - - $select = new Select($this->orm->with(heap: new Heap()), User::class); + $select = new Select($this->orm->with(heap: new Heap()), AllUuid::class); $data = $select->fetchOne(); - $this->assertInstanceOf(UntypedUuid::class, $data->uuid); - $this->assertSame(5, $data->uuid->getVersion()->value); - $this->assertIsString($data->uuid->toString()); + $this->assertSame($entity->uuid1->toString(), $data->uuid1->toString()); + $this->assertSame($entity->uuid2->toString(), $data->uuid2->toString()); + $this->assertSame($entity->uuid3->toString(), $data->uuid3->toString()); + $this->assertSame($entity->uuid4->toString(), $data->uuid4->toString()); + $this->assertSame($entity->uuid5->toString(), $data->uuid5->toString()); + $this->assertSame($entity->uuid6->toString(), $data->uuid6->toString()); + $this->assertSame($entity->uuid7->toString(), $data->uuid7->toString()); } - public function testUuid6(): void - { - $this->withListeners([Uuid6Listener::class, ['node' => '00000fffffff', 'clockSeq' => 0x1669]]); - - $user = new User(); - $this->save($user); - - $select = new Select($this->orm->with(heap: new Heap()), User::class); + /** + * @dataProvider uuidGenerationDataProvider + */ + public function testUuidGeneration( + array $listeners, + string $field, + string $expectedClass, + int $expectedVersion, + ): void { + $this->withListeners($listeners); + + $entity = new AllUuid(); + $this->save($entity); + + $select = new Select($this->orm->with(heap: new Heap()), AllUuid::class); $data = $select->fetchOne(); - $this->assertInstanceOf(UntypedUuid::class, $data->uuid); - $this->assertSame(6, $data->uuid->getVersion()->value); - $this->assertIsString($data->uuid->toString()); + $uuid = $data->$field; + $this->assertInstanceOf($expectedClass, $uuid); + $this->assertSame($expectedVersion, $uuid->getVersion()->value); + $this->assertIsString($uuid->toString()); } - public function testUuid7(): void - { - $this->withListeners(Uuid7Listener::class); - - $user = new User(); - $this->save($user); + /** + * @dataProvider uuidExceptionDataProvider + */ + public function testUuidException( + array $listeners, + string $expectedException, + ): void { + $this->expectException($expectedException); - $select = new Select($this->orm->with(heap: new Heap()), User::class); - $data = $select->fetchOne(); + $this->withListeners($listeners); - $this->assertInstanceOf(UntypedUuid::class, $data->uuid); - $this->assertSame(7, $data->uuid->getVersion()->value); - $this->assertIsString($data->uuid->toString()); + $entity = new AllUuid(); + $this->save($entity); } - public function withListeners(array|string $listeners): void + public function withListeners(array|string|null $listeners = null): void { $this->withSchema(new Schema([ - User::class => [ - SchemaInterface::ROLE => 'user', + AllUuid::class => [ + SchemaInterface::ROLE => 'all_uuid', SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'users', - SchemaInterface::PRIMARY_KEY => 'uuid', - SchemaInterface::COLUMNS => ['uuid', 'optional_uuid'], - SchemaInterface::LISTENERS => [$listeners], + SchemaInterface::TABLE => 'all_uuids', + SchemaInterface::PRIMARY_KEY => 'id', + SchemaInterface::COLUMNS => ['id', 'uuid1', 'uuid2', 'uuid3', 'uuid4', 'uuid5', 'uuid6', 'uuid7'], + SchemaInterface::LISTENERS => $listeners ? [$listeners] : [], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], SchemaInterface::TYPECAST => [ - 'uuid' => [Uuid::class, 'fromString'], - 'optional_uuid' => [Uuid::class, 'fromString'], + 'uuid1' => [Uuid1::class, 'create'], + 'uuid2' => [Uuid2::class, 'create'], + 'uuid3' => [Uuid3::class, 'create'], + 'uuid4' => [Uuid4::class, 'create'], + 'uuid5' => [Uuid5::class, 'create'], + 'uuid6' => [Uuid6::class, 'create'], + 'uuid7' => [Uuid7::class, 'create'], ], ], ])); } + #[\Override] public function setUp(): void { parent::setUp(); + Uuid3Listener::setDefaults(null, null); + Uuid5Listener::setDefaults(null, null); + + $this->factory = new UuidFactory(); + $this->makeTable( - 'users', + 'all_uuids', [ - 'uuid' => 'string', - 'optional_uuid' => 'string,nullable', + 'id' => 'integer', + 'uuid1' => 'uuid,nullable', + 'uuid2' => 'uuid,nullable', + 'uuid3' => 'uuid,nullable', + 'uuid4' => 'uuid,nullable', + 'uuid5' => 'uuid,nullable', + 'uuid6' => 'uuid,nullable', + 'uuid7' => 'uuid,nullable', ], ); } diff --git a/tests/Identifier/Functional/Driver/Common/Uuid/UuidTest.php b/tests/Identifier/Functional/Driver/Common/Uuid/UuidTest.php index b4bcf96..f821c14 100644 --- a/tests/Identifier/Functional/Driver/Common/Uuid/UuidTest.php +++ b/tests/Identifier/Functional/Driver/Common/Uuid/UuidTest.php @@ -10,7 +10,9 @@ use Cycle\ORM\Entity\Behavior\Identifier\Tests\Fixtures\Uuid\Post; use Cycle\ORM\Entity\Behavior\Identifier\Tests\Fixtures\Uuid\User; use Cycle\ORM\Entity\Behavior\Identifier\Tests\Functional\Driver\Common\BaseTest; -use Cycle\ORM\Entity\Behavior\Identifier\Uuid; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid1; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid4; +use Cycle\ORM\Entity\Behavior\Identifier\Uuid7; use Cycle\ORM\Schema\GeneratedField; use Cycle\Schema\Registry; use Spiral\Attributes\AttributeReader; @@ -35,7 +37,7 @@ public function testColumnExist(ReaderInterface $reader): void $this->assertTrue($fields->has('uuid')); $this->assertTrue($fields->hasColumn('uuid')); $this->assertSame('uuid', $fields->get('uuid')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('uuid')->getTypecast()); + $this->assertSame([Uuid1::class, 'create'], $fields->get('uuid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('uuid')->getGenerated()); $this->assertSame(1, $fields->count()); } @@ -52,7 +54,7 @@ public function testAddColumn(ReaderInterface $reader): void $this->assertTrue($fields->has('customUuid')); $this->assertTrue($fields->hasColumn('custom_uuid')); $this->assertSame('uuid', $fields->get('customUuid')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('customUuid')->getTypecast()); + $this->assertSame([Uuid4::class, 'create'], $fields->get('customUuid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('customUuid')->getGenerated()); } @@ -68,25 +70,25 @@ public function testMultipleUuid(ReaderInterface $reader): void $this->assertTrue($fields->has('uuid')); $this->assertTrue($fields->hasColumn('uuid')); $this->assertSame('uuid', $fields->get('uuid')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('uuid')->getTypecast()); + $this->assertSame([Uuid1::class, 'create'], $fields->get('uuid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('uuid')->getGenerated()); $this->assertTrue($fields->has('otherUuid')); $this->assertTrue($fields->hasColumn('other_uuid')); $this->assertSame('uuid', $fields->get('otherUuid')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('otherUuid')->getTypecast()); + $this->assertSame([Uuid1::class, 'create'], $fields->get('otherUuid')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('otherUuid')->getGenerated()); $this->assertTrue($fields->has('uuid7')); $this->assertTrue($fields->hasColumn('uuid7')); $this->assertSame('uuid', $fields->get('uuid7')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('uuid7')->getTypecast()); + $this->assertSame([Uuid7::class, 'create'], $fields->get('uuid7')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('uuid7')->getGenerated()); $this->assertTrue($fields->has('otherUuid7')); $this->assertTrue($fields->hasColumn('other_uuid7')); $this->assertSame('uuid', $fields->get('otherUuid7')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('otherUuid7')->getTypecast()); + $this->assertSame([Uuid7::class, 'create'], $fields->get('otherUuid7')->getTypecast()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('otherUuid7')->getGenerated()); } @@ -102,7 +104,7 @@ public function testAddNullableColumn(ReaderInterface $reader): void $this->assertTrue($fields->has('notDefinedUuid')); $this->assertTrue($fields->hasColumn('not_defined_uuid')); $this->assertSame('uuid', $fields->get('notDefinedUuid')->getType()); - $this->assertSame([Uuid::class, 'fromString'], $fields->get('notDefinedUuid')->getTypecast()); + $this->assertSame([Uuid1::class, 'create'], $fields->get('notDefinedUuid')->getTypecast()); $this->assertTrue( $this->registry ->getTableSchema($this->registry->getEntity(NullableUuid::class)) @@ -112,6 +114,7 @@ public function testAddNullableColumn(ReaderInterface $reader): void $this->assertNull($fields->get('notDefinedUuid')->getGenerated()); } + #[\Override] public function setUp(): void { parent::setUp(); diff --git a/tests/Identifier/Functional/Driver/MySQL/Snowflake/ListenerTest.php b/tests/Identifier/Functional/Driver/MySQL/Snowflake/ListenerTest.php new file mode 100644 index 0000000..c215723 --- /dev/null +++ b/tests/Identifier/Functional/Driver/MySQL/Snowflake/ListenerTest.php @@ -0,0 +1,17 @@ + [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'workerId' => null, + 'processId' => null, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake'], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'workerId' => null, + 'processId' => null, + 'nullable' => true, + ], + ], + ], + ], + ['custom_snowflake', null, null, null, true], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'workerId' => 3, + 'processId' => 6, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake', null, 3, 6], + ]; + } + + /** + * @dataProvider schemaDataProvider + */ + public function testModifySchema(array $expected, array $args): void + { + $schema = []; + $snowflake = new SnowflakeDiscord(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults(1, 2); + + $args = ['snowflake', null, null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'snowflake', + 'workerId' => null, + 'processId' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $snowflake = new SnowflakeDiscord(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null, null); + + parent::setUp(); + } +} diff --git a/tests/Identifier/Unit/SnowflakeGenericTest.php b/tests/Identifier/Unit/SnowflakeGenericTest.php new file mode 100644 index 0000000..67b3402 --- /dev/null +++ b/tests/Identifier/Unit/SnowflakeGenericTest.php @@ -0,0 +1,113 @@ + [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'node' => null, + 'epochOffset' => null, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake'], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'node' => null, + 'epochOffset' => null, + 'nullable' => true, + ], + ], + ], + ], + ['custom_snowflake', null, null, null, true], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'node' => 3, + 'epochOffset' => 6, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake', null, 3, 6], + ]; + } + + /** + * @dataProvider schemaDataProvider + */ + public function testModifySchema(array $expected, array $args): void + { + $schema = []; + $snowflake = new SnowflakeGeneric(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults(1, 1738265600000); + + $args = ['snowflake', null, null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'snowflake', + 'node' => null, + 'epochOffset' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $snowflake = new SnowflakeGeneric(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null, null); + + parent::setUp(); + } +} diff --git a/tests/Identifier/Unit/SnowflakeInstagramTest.php b/tests/Identifier/Unit/SnowflakeInstagramTest.php new file mode 100644 index 0000000..fcfd2eb --- /dev/null +++ b/tests/Identifier/Unit/SnowflakeInstagramTest.php @@ -0,0 +1,109 @@ + [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'shardId' => null, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake'], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'shardId' => null, + 'nullable' => true, + ], + ], + ], + ], + ['custom_snowflake', null, null, true], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'shardId' => 3, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake', null, 3], + ]; + } + + /** + * @dataProvider schemaDataProvider + */ + public function testModifySchema(array $expected, array $args): void + { + $schema = []; + $snowflake = new SnowflakeInstagram(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults(1); + + $args = ['snowflake', null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'snowflake', + 'shardId' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $snowflake = new SnowflakeInstagram(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null); + + parent::setUp(); + } +} diff --git a/tests/Identifier/Unit/SnowflakeMastodonTest.php b/tests/Identifier/Unit/SnowflakeMastodonTest.php new file mode 100644 index 0000000..7920cc4 --- /dev/null +++ b/tests/Identifier/Unit/SnowflakeMastodonTest.php @@ -0,0 +1,109 @@ + [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'tableName' => null, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake'], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'tableName' => null, + 'nullable' => true, + ], + ], + ], + ], + ['custom_snowflake', null, null, true], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'tableName' => 'users', + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake', null, 'users'], + ]; + } + + /** + * @dataProvider schemaDataProvider + */ + public function testModifySchema(array $expected, array $args): void + { + $schema = []; + $snowflake = new SnowflakeMastodon(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults('users'); + + $args = ['snowflake', null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'snowflake', + 'tableName' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $snowflake = new SnowflakeMastodon(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null); + + parent::setUp(); + } +} diff --git a/tests/Identifier/Unit/SnowflakeTwitterTest.php b/tests/Identifier/Unit/SnowflakeTwitterTest.php new file mode 100644 index 0000000..5d31a18 --- /dev/null +++ b/tests/Identifier/Unit/SnowflakeTwitterTest.php @@ -0,0 +1,109 @@ + [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'machineId' => null, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake'], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'machineId' => null, + 'nullable' => true, + ], + ], + ], + ], + ['custom_snowflake', null, null, true], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_snowflake', + 'machineId' => 3, + 'nullable' => false, + ], + ], + ], + ], + ['custom_snowflake', null, 3], + ]; + } + + /** + * @dataProvider schemaDataProvider + */ + public function testModifySchema(array $expected, array $args): void + { + $schema = []; + $snowflake = new SnowflakeTwitter(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults(1); + + $args = ['snowflake', null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'snowflake', + 'machineId' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $snowflake = new SnowflakeTwitter(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null); + + parent::setUp(); + } +} diff --git a/tests/Identifier/Unit/UlidTest.php b/tests/Identifier/Unit/UlidTest.php index 6775d0c..7dbd6c3 100644 --- a/tests/Identifier/Unit/UlidTest.php +++ b/tests/Identifier/Unit/UlidTest.php @@ -6,7 +6,7 @@ use Cycle\ORM\Entity\Behavior\Dispatcher\ListenerProvider; use Cycle\ORM\Entity\Behavior\Identifier\Ulid; -use Cycle\ORM\Entity\Behavior\Identifier\Listener\Ulid as UlidListener; +use Cycle\ORM\Entity\Behavior\Identifier\Listener\Ulid as Listener; use Cycle\ORM\SchemaInterface; use PHPUnit\Framework\TestCase; @@ -18,7 +18,7 @@ public static function schemaDataProvider(): \Traversable [ SchemaInterface::LISTENERS => [ [ - ListenerProvider::DEFINITION_CLASS => UlidListener::class, + ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'ulid', 'nullable' => false, @@ -32,7 +32,7 @@ public static function schemaDataProvider(): \Traversable [ SchemaInterface::LISTENERS => [ [ - ListenerProvider::DEFINITION_CLASS => UlidListener::class, + ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'custom_ulid', 'nullable' => false, @@ -46,7 +46,7 @@ public static function schemaDataProvider(): \Traversable [ SchemaInterface::LISTENERS => [ [ - ListenerProvider::DEFINITION_CLASS => UlidListener::class, + ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'custom_ulid', 'nullable' => true, diff --git a/tests/Identifier/Unit/Uuid1Test.php b/tests/Identifier/Unit/Uuid1Test.php index ed96b68..936f30f 100644 --- a/tests/Identifier/Unit/Uuid1Test.php +++ b/tests/Identifier/Unit/Uuid1Test.php @@ -107,4 +107,39 @@ public function testModifySchema(array $expected, array $args): void $this->assertSame($expected, $schema); } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults('foo', 1); + + $args = ['uuid', null, null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $snowflake = new Uuid1(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null, null); + + parent::setUp(); + } } diff --git a/tests/Identifier/Unit/Uuid2Test.php b/tests/Identifier/Unit/Uuid2Test.php index 63fbeab..f929947 100644 --- a/tests/Identifier/Unit/Uuid2Test.php +++ b/tests/Identifier/Unit/Uuid2Test.php @@ -22,7 +22,7 @@ public static function schemaDataProvider(): \Traversable ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'uuid', - 'localDomain' => DceDomain::Person, + 'localDomain' => DceDomain::Person->value, 'localIdentifier' => null, 'node' => null, 'clockSeq' => null, @@ -40,7 +40,7 @@ public static function schemaDataProvider(): \Traversable ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'custom_uuid', - 'localDomain' => DceDomain::Person, + 'localDomain' => DceDomain::Person->value, 'localIdentifier' => null, 'node' => null, 'clockSeq' => null, @@ -58,7 +58,7 @@ public static function schemaDataProvider(): \Traversable ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'custom_uuid', - 'localDomain' => DceDomain::Person, + 'localDomain' => DceDomain::Person->value, 'localIdentifier' => 3, 'node' => null, 'clockSeq' => null, @@ -76,7 +76,7 @@ public static function schemaDataProvider(): \Traversable ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'custom_uuid', - 'localDomain' => DceDomain::Person, + 'localDomain' => DceDomain::Person->value, 'localIdentifier' => 3, 'node' => 'bar', 'clockSeq' => null, @@ -85,7 +85,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['custom_uuid', null, 0, 3, 'bar'], + ['custom_uuid', null, DceDomain::Person, 3, 'bar'], ]; yield [ [ @@ -94,7 +94,7 @@ public static function schemaDataProvider(): \Traversable ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'custom_uuid', - 'localDomain' => DceDomain::Group, + 'localDomain' => DceDomain::Group->value, 'localIdentifier' => 3, 'node' => 'bar', 'clockSeq' => 4, @@ -103,7 +103,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['custom_uuid', null, 1, 3, 'bar', 4], + ['custom_uuid', null, DceDomain::Group, 3, 'bar', 4], ]; yield [ [ @@ -112,7 +112,7 @@ public static function schemaDataProvider(): \Traversable ListenerProvider::DEFINITION_CLASS => Listener::class, ListenerProvider::DEFINITION_ARGS => [ 'field' => 'custom_uuid', - 'localDomain' => DceDomain::Org, + 'localDomain' => DceDomain::Org->value, 'localIdentifier' => 3, 'node' => 'bar', 'clockSeq' => 4, @@ -121,7 +121,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['custom_uuid', null, 2, 3, 'bar', 4, true], + ['custom_uuid', null, DceDomain::Org, 3, 'bar', 4, true], ]; } @@ -136,4 +136,41 @@ public function testModifySchema(array $expected, array $args): void $this->assertSame($expected, $schema); } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults(DceDomain::Group, 2, 'foo', 3); + + $args = ['uuid', null, null, null, null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'localDomain' => DceDomain::Person->value, + 'localIdentifier' => null, + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $snowflake = new Uuid2(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(DceDomain::Person, null, null, null); + + parent::setUp(); + } } diff --git a/tests/Identifier/Unit/Uuid3Test.php b/tests/Identifier/Unit/Uuid3Test.php index 28bf587..7b60811 100644 --- a/tests/Identifier/Unit/Uuid3Test.php +++ b/tests/Identifier/Unit/Uuid3Test.php @@ -14,6 +14,22 @@ final class Uuid3Test extends TestCase { public static function schemaDataProvider(): \Traversable { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'namespace' => null, + 'name' => null, + 'nullable' => false, + ], + ], + ], + ], + [], + ]; yield [ [ SchemaInterface::LISTENERS => [ @@ -28,7 +44,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['foo', 'bar'], + ['uuid', null, 'foo', 'bar'], ]; yield [ [ @@ -44,7 +60,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['foo', 'bar', 'custom_uuid'], + ['custom_uuid', null, 'foo', 'bar'], ]; yield [ [ @@ -60,7 +76,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['foo', 'bar', 'custom_uuid', null, true], + ['custom_uuid', null, 'foo', 'bar', true], ]; } @@ -75,4 +91,39 @@ public function testModifySchema(array $expected, array $args): void $this->assertSame($expected, $schema); } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults('foo', 'bar'); + + $args = ['uuid', null, null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'namespace' => null, + 'name' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $uuid = new Uuid3(...$args); + $uuid->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null, null); + + parent::setUp(); + } } diff --git a/tests/Identifier/Unit/Uuid5Test.php b/tests/Identifier/Unit/Uuid5Test.php index b77ea73..c5184b5 100644 --- a/tests/Identifier/Unit/Uuid5Test.php +++ b/tests/Identifier/Unit/Uuid5Test.php @@ -14,6 +14,22 @@ final class Uuid5Test extends TestCase { public static function schemaDataProvider(): \Traversable { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'namespace' => null, + 'name' => null, + 'nullable' => false, + ], + ], + ], + ], + [], + ]; yield [ [ SchemaInterface::LISTENERS => [ @@ -28,7 +44,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['foo', 'bar'], + ['uuid', null, 'foo', 'bar'], ]; yield [ [ @@ -44,7 +60,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['foo', 'bar', 'custom_uuid'], + ['custom_uuid', null, 'foo', 'bar'], ]; yield [ [ @@ -60,7 +76,7 @@ public static function schemaDataProvider(): \Traversable ], ], ], - ['foo', 'bar', 'custom_uuid', null, true], + ['custom_uuid', null, 'foo', 'bar', true], ]; } @@ -75,4 +91,39 @@ public function testModifySchema(array $expected, array $args): void $this->assertSame($expected, $schema); } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults('foo', 'bar'); + + $args = ['uuid', null, null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'namespace' => null, + 'name' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $uuid = new Uuid5(...$args); + $uuid->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null, null); + + parent::setUp(); + } } diff --git a/tests/Identifier/Unit/Uuid6Test.php b/tests/Identifier/Unit/Uuid6Test.php index 7c254a0..6c5a76b 100644 --- a/tests/Identifier/Unit/Uuid6Test.php +++ b/tests/Identifier/Unit/Uuid6Test.php @@ -107,4 +107,39 @@ public function testModifySchema(array $expected, array $args): void $this->assertSame($expected, $schema); } + + public function testModifySchemaWithDefaults(): void + { + Listener::setDefaults('foo', 1); + + $args = ['uuid', null, null, null, false]; + + $expected = [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ], + ], + ]; + + $schema = []; + $snowflake = new Uuid6(...$args); + $snowflake->modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + #[\Override] + protected function setUp(): void + { + Listener::setDefaults(null, null); + + parent::setUp(); + } }