diff --git a/.github/workflows/ci-mssql.yml b/.github/workflows/ci-mssql.yml index ad39a85..f3c9fd4 100644 --- a/.github/workflows/ci-mssql.yml +++ b/.github/workflows/ci-mssql.yml @@ -24,7 +24,10 @@ jobs: extensions: pdo, pdo_sqlsrv mssql: 'server:2019-latest' - php: '8.1' - extensions: pdo, pdo_sqlsrv-5.10.0beta2 + extensions: pdo, pdo_sqlsrv-5.11.0 + mssql: 'server:2019-latest' + - php: '8.2' + extensions: pdo, pdo_sqlsrv-5.11.0 mssql: 'server:2019-latest' services: diff --git a/.github/workflows/ci-mysql.yml b/.github/workflows/ci-mysql.yml index b1387fb..c69859b 100644 --- a/.github/workflows/ci-mysql.yml +++ b/.github/workflows/ci-mysql.yml @@ -22,6 +22,7 @@ jobs: php-version: - "8.0" - "8.1" + - "8.2" mysql-version: - "5.7" diff --git a/.github/workflows/ci-pgsql.yml b/.github/workflows/ci-pgsql.yml index 5447b33..38aa820 100644 --- a/.github/workflows/ci-pgsql.yml +++ b/.github/workflows/ci-pgsql.yml @@ -21,6 +21,7 @@ jobs: php-version: - "8.0" - "8.1" + - "8.2" pgsql-version: - "10" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5288b86..19733cd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,6 +16,7 @@ jobs: php-version: - "8.0" - "8.1" + - "8.2" steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 5c63a24..dc61517 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -14,7 +14,7 @@ jobs: - ubuntu-latest php: - - "8.0" + - "8.2" steps: - name: Checkout @@ -44,4 +44,4 @@ jobs: run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - name: Static analysis - run: vendor/bin/psalm --shepherd --stats --output-format=checkstyle \ No newline at end of file + run: vendor/bin/psalm --shepherd --stats --output-format=checkstyle diff --git a/composer.json b/composer.json index 8ff1bec..7112404 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "cycle/annotated": "^3.0", "phpunit/phpunit": "^9.5", "spiral/tokenizer": "^2.8", - "vimeo/psalm": "^4.13" + "vimeo/psalm": "^5.11" }, "license": "MIT", "autoload": { diff --git a/psalm.xml b/psalm.xml index 0efb590..dd215c7 100644 --- a/psalm.xml +++ b/psalm.xml @@ -4,6 +4,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" + findUnusedCode="false" + findUnusedBaselineEntry="true" > diff --git a/src/Listener/Uuid1.php b/src/Listener/Uuid1.php index 54f2869..2e5fa57 100644 --- a/src/Listener/Uuid1.php +++ b/src/Listener/Uuid1.php @@ -14,14 +14,15 @@ final class Uuid1 public function __construct( private string $field = 'uuid', private Hexadecimal|int|string|null $node = null, - private ?int $clockSeq = null + private ?int $clockSeq = null, + private bool $nullable = false ) { } #[Listen(OnCreate::class)] public function __invoke(OnCreate $event): void { - if (!isset($event->state->getData()[$this->field])) { + if (!$this->nullable && !isset($event->state->getData()[$this->field])) { $event->state->register($this->field, Uuid::uuid1($this->node, $this->clockSeq)); } } diff --git a/src/Listener/Uuid2.php b/src/Listener/Uuid2.php index 77b95d9..3c8a302 100644 --- a/src/Listener/Uuid2.php +++ b/src/Listener/Uuid2.php @@ -17,13 +17,18 @@ public function __construct( private string $field = 'uuid', private IntegerObject|string|null $localIdentifier = null, private Hexadecimal|string|null $node = null, - private ?int $clockSeq = 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; + } + if (\is_string($this->localIdentifier)) { $this->localIdentifier = new IntegerObject($this->localIdentifier); } @@ -31,11 +36,9 @@ public function __invoke(OnCreate $event): void $this->node = new Hexadecimal($this->node); } - if (!isset($event->state->getData()[$this->field])) { - $event->state->register( - $this->field, - Uuid::uuid2($this->localDomain, $this->localIdentifier, $this->node, $this->clockSeq) - ); - } + $event->state->register( + $this->field, + Uuid::uuid2($this->localDomain, $this->localIdentifier, $this->node, $this->clockSeq) + ); } } diff --git a/src/Listener/Uuid3.php b/src/Listener/Uuid3.php index 54105d0..fb18300 100644 --- a/src/Listener/Uuid3.php +++ b/src/Listener/Uuid3.php @@ -14,14 +14,15 @@ final class Uuid3 public function __construct( private string|UuidInterface $namespace, private string $name, - private string $field = 'uuid' + private string $field = 'uuid', + private bool $nullable = false ) { } #[Listen(OnCreate::class)] public function __invoke(OnCreate $event): void { - if (!isset($event->state->getData()[$this->field])) { + if (!$this->nullable && !isset($event->state->getData()[$this->field])) { $event->state->register($this->field, Uuid::uuid3($this->namespace, $this->name)); } } diff --git a/src/Listener/Uuid4.php b/src/Listener/Uuid4.php index e3e2a4c..f2a21dc 100644 --- a/src/Listener/Uuid4.php +++ b/src/Listener/Uuid4.php @@ -11,14 +11,15 @@ final class Uuid4 { public function __construct( - private string $field = 'uuid' + private string $field = 'uuid', + private bool $nullable = false ) { } #[Listen(OnCreate::class)] public function __invoke(OnCreate $event): void { - if (!isset($event->state->getData()[$this->field])) { + if (!$this->nullable && !isset($event->state->getData()[$this->field])) { $event->state->register($this->field, Uuid::uuid4()); } } diff --git a/src/Listener/Uuid5.php b/src/Listener/Uuid5.php index 7c14f60..9e1271d 100644 --- a/src/Listener/Uuid5.php +++ b/src/Listener/Uuid5.php @@ -14,14 +14,15 @@ final class Uuid5 public function __construct( private string|UuidInterface $namespace, private string $name, - private string $field = 'uuid' + private string $field = 'uuid', + private bool $nullable = false ) { } #[Listen(OnCreate::class)] public function __invoke(OnCreate $event): void { - if (!isset($event->state->getData()[$this->field])) { + if (!$this->nullable && !isset($event->state->getData()[$this->field])) { $event->state->register($this->field, Uuid::uuid5($this->namespace, $this->name)); } } diff --git a/src/Listener/Uuid6.php b/src/Listener/Uuid6.php index 6d5ae5d..6a39726 100644 --- a/src/Listener/Uuid6.php +++ b/src/Listener/Uuid6.php @@ -14,19 +14,22 @@ final class Uuid6 public function __construct( private string $field = 'uuid', private Hexadecimal|string|null $node = null, - private ?int $clockSeq = 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; + } + if (\is_string($this->node)) { $this->node = new Hexadecimal($this->node); } - if (!isset($event->state->getData()[$this->field])) { - $event->state->register($this->field, Uuid::uuid6($this->node, $this->clockSeq)); - } + $event->state->register($this->field, Uuid::uuid6($this->node, $this->clockSeq)); } } diff --git a/src/Listener/Uuid7.php b/src/Listener/Uuid7.php index 763c590..6782655 100644 --- a/src/Listener/Uuid7.php +++ b/src/Listener/Uuid7.php @@ -11,14 +11,15 @@ final class Uuid7 { public function __construct( - private string $field = 'uuid' + private string $field = 'uuid', + private bool $nullable = false ) { } #[Listen(OnCreate::class)] public function __invoke(OnCreate $event): void { - if (!isset($event->state->getData()[$this->field])) { + if (!$this->nullable && !isset($event->state->getData()[$this->field])) { $event->state->register($this->field, Uuid::uuid7()); } } diff --git a/src/Uuid.php b/src/Uuid.php index 641501c..696d5c7 100644 --- a/src/Uuid.php +++ b/src/Uuid.php @@ -13,14 +13,14 @@ abstract class Uuid extends BaseModifier { protected ?string $column = null; protected string $field; + protected bool $nullable = false; public function compute(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); $this->column = $modifier->findColumnName($this->field, $this->column); - if ($this->column !== null) { - $modifier->addUuidColumn($this->column, $this->field); + $modifier->addUuidColumn($this->column, $this->field)->nullable($this->nullable); $modifier->setTypecast( $registry->getEntity($this->role)->getFields()->get($this->field), [RamseyUuid::class, 'fromString'] @@ -33,7 +33,7 @@ public function render(Registry $registry): void $modifier = new RegistryModifier($registry, $this->role); $this->column = $modifier->findColumnName($this->field, $this->column) ?? $this->field; - $modifier->addUuidColumn($this->column, $this->field); + $modifier->addUuidColumn($this->column, $this->field)->nullable($this->nullable); $modifier->setTypecast( $registry->getEntity($this->role)->getFields()->get($this->field), [RamseyUuid::class, 'fromString'] diff --git a/src/Uuid1.php b/src/Uuid1.php index b6292fe..79cc488 100644 --- a/src/Uuid1.php +++ b/src/Uuid1.php @@ -18,7 +18,7 @@ * @NamedArgumentConstructor() * @Target({"CLASS"}) */ -#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid1 extends Uuid { /** @@ -30,6 +30,7 @@ final class Uuid1 extends Uuid * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes + * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Uuid\UuidFactoryInterface::uuid1() */ @@ -37,10 +38,12 @@ public function __construct( string $field = 'uuid', ?string $column = null, private Hexadecimal|int|string|null $node = null, - private ?int $clockSeq = null + private ?int $clockSeq = null, + bool $nullable = false ) { $this->field = $field; $this->column = $column; + $this->nullable = $nullable; } protected function getListenerClass(): string @@ -48,13 +51,14 @@ protected function getListenerClass(): string return Listener::class; } - #[ArrayShape(['field' => 'string', 'node' => 'int|string|null', 'clockSeq' => 'int|null'])] + #[ArrayShape(['field' => 'string', 'node' => 'int|string|null', 'clockSeq' => 'int|null', 'nullable' => 'bool'])] protected function getListenerArgs(): array { return [ 'field' => $this->field, 'node' => $this->node instanceof Hexadecimal ? (string) $this->node : $this->node, - 'clockSeq' => $this->clockSeq + 'clockSeq' => $this->clockSeq, + 'nullable' => $this->nullable ]; } } diff --git a/src/Uuid2.php b/src/Uuid2.php index 7c3b412..efdc1b9 100644 --- a/src/Uuid2.php +++ b/src/Uuid2.php @@ -19,7 +19,7 @@ * @NamedArgumentConstructor() * @Target({"CLASS"}) */ -#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid2 extends Uuid { /** @@ -36,6 +36,7 @@ final class Uuid2 extends Uuid * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes + * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Uuid\UuidFactoryInterface::uuid2() */ @@ -45,10 +46,12 @@ public function __construct( ?string $column = null, private IntegerObject|string|null $localIdentifier = null, private Hexadecimal|string|null $node = null, - private ?int $clockSeq = null + private ?int $clockSeq = null, + bool $nullable = false ) { $this->field = $field; $this->column = $column; + $this->nullable = $nullable; } protected function getListenerClass(): string @@ -61,7 +64,8 @@ protected function getListenerClass(): string 'localDomain' => 'int', 'localIdentifier' => 'string|null', 'node' => 'string|null', - 'clockSeq' => 'int|null' + 'clockSeq' => 'int|null', + 'nullable' => 'bool' ])] protected function getListenerArgs(): array { @@ -72,7 +76,8 @@ protected function getListenerArgs(): array ? (string) $this->localIdentifier : $this->localIdentifier, 'node' => $this->node instanceof Hexadecimal ? (string) $this->node : $this->node, - 'clockSeq' => $this->clockSeq + 'clockSeq' => $this->clockSeq, + 'nullable' => $this->nullable ]; } } diff --git a/src/Uuid3.php b/src/Uuid3.php index e626d18..706163e 100644 --- a/src/Uuid3.php +++ b/src/Uuid3.php @@ -18,7 +18,7 @@ * @NamedArgumentConstructor() * @Target({"CLASS"}) */ -#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid3 extends Uuid { /** @@ -26,6 +26,7 @@ final class Uuid3 extends Uuid * @param non-empty-string $name The name to use for creating a UUID * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name + * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Uuid\UuidFactoryInterface::uuid3() */ @@ -33,10 +34,12 @@ public function __construct( private string|UuidInterface $namespace, private string $name, string $field = 'uuid', - ?string $column = null + ?string $column = null, + bool $nullable = false ) { $this->field = $field; $this->column = $column; + $this->nullable = $nullable; } protected function getListenerClass(): string @@ -44,13 +47,14 @@ protected function getListenerClass(): string return Listener::class; } - #[ArrayShape(['field' => 'string', 'namespace' => 'string', 'name' => 'string'])] + #[ArrayShape(['field' => 'string', 'namespace' => 'string', 'name' => 'string', 'nullable' => 'bool'])] protected function getListenerArgs(): array { return [ 'field' => $this->field, 'namespace' => $this->namespace instanceof UuidInterface ? (string) $this->namespace : $this->namespace, - 'name' => $this->name + 'name' => $this->name, + 'nullable' => $this->nullable ]; } } diff --git a/src/Uuid4.php b/src/Uuid4.php index 1c3facd..23f690e 100644 --- a/src/Uuid4.php +++ b/src/Uuid4.php @@ -16,21 +16,24 @@ * @NamedArgumentConstructor() * @Target({"CLASS"}) */ -#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid4 extends Uuid { /** * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name + * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Uuid\UuidFactoryInterface::uuid4() */ public function __construct( string $field = 'uuid', - ?string $column = null + ?string $column = null, + bool $nullable = false ) { $this->field = $field; $this->column = $column; + $this->nullable = $nullable; } protected function getListenerClass(): string @@ -38,11 +41,12 @@ protected function getListenerClass(): string return Listener::class; } - #[ArrayShape(['field' => 'string'])] + #[ArrayShape(['field' => 'string', 'nullable' => 'bool'])] protected function getListenerArgs(): array { return [ - 'field' => $this->field + 'field' => $this->field, + 'nullable' => $this->nullable ]; } } diff --git a/src/Uuid5.php b/src/Uuid5.php index a18fe61..bd1d72e 100644 --- a/src/Uuid5.php +++ b/src/Uuid5.php @@ -18,7 +18,7 @@ * @NamedArgumentConstructor() * @Target({"CLASS"}) */ -#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid5 extends Uuid { /** @@ -26,6 +26,7 @@ final class Uuid5 extends Uuid * @param non-empty-string $name The name to use for creating a UUID * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name + * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Uuid\UuidFactoryInterface::uuid5() */ @@ -33,10 +34,12 @@ public function __construct( private string|UuidInterface $namespace, private string $name, string $field = 'uuid', - ?string $column = null + ?string $column = null, + bool $nullable = false ) { $this->field = $field; $this->column = $column; + $this->nullable = $nullable; } protected function getListenerClass(): string @@ -44,13 +47,14 @@ protected function getListenerClass(): string return Listener::class; } - #[ArrayShape(['field' => 'string', 'namespace' => 'string', 'name' => 'string'])] + #[ArrayShape(['field' => 'string', 'namespace' => 'string', 'name' => 'string', 'nullable' => 'bool'])] protected function getListenerArgs(): array { return [ 'field' => $this->field, 'namespace' => $this->namespace instanceof UuidInterface ? (string) $this->namespace : $this->namespace, - 'name' => $this->name + 'name' => $this->name, + 'nullable' => $this->nullable ]; } } diff --git a/src/Uuid6.php b/src/Uuid6.php index 48c502c..1169a27 100644 --- a/src/Uuid6.php +++ b/src/Uuid6.php @@ -18,7 +18,7 @@ * @NamedArgumentConstructor() * @Target({"CLASS"}) */ -#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid6 extends Uuid { /** @@ -28,6 +28,7 @@ final class Uuid6 extends Uuid * @param int|null $clockSeq A 14-bit number used to help avoid duplicates * that could arise when the clock is set backwards in time or if the * node ID changes + * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Uuid\UuidFactoryInterface::uuid6() */ @@ -35,10 +36,12 @@ public function __construct( string $field = 'uuid', ?string $column = null, private Hexadecimal|string|null $node = null, - private ?int $clockSeq = null + private ?int $clockSeq = null, + bool $nullable = false ) { $this->field = $field; $this->column = $column; + $this->nullable = $nullable; } protected function getListenerClass(): string @@ -46,13 +49,14 @@ protected function getListenerClass(): string return Listener::class; } - #[ArrayShape(['field' => 'string', 'node' => 'string|null', 'clockSeq' => 'int|null'])] + #[ArrayShape(['field' => 'string', 'node' => 'string|null', 'clockSeq' => 'int|null', 'nullable' => 'bool'])] protected function getListenerArgs(): array { return [ 'field' => $this->field, 'node' => $this->node instanceof Hexadecimal ? (string) $this->node : $this->node, - 'clockSeq' => $this->clockSeq + 'clockSeq' => $this->clockSeq, + 'nullable' => $this->nullable ]; } } diff --git a/src/Uuid7.php b/src/Uuid7.php index 895ab89..23147eb 100644 --- a/src/Uuid7.php +++ b/src/Uuid7.php @@ -16,21 +16,24 @@ * @NamedArgumentConstructor() * @Target({"CLASS"}) */ -#[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE), NamedArgumentConstructor] final class Uuid7 extends Uuid { /** * @param non-empty-string $field Uuid property name * @param non-empty-string|null $column Uuid column name + * @param bool $nullable Indicates whether to generate a new UUID or not * * @see \Ramsey\Uuid\UuidFactoryInterface::uuid7() */ public function __construct( string $field = 'uuid', - ?string $column = null + ?string $column = null, + bool $nullable = false ) { $this->field = $field; $this->column = $column; + $this->nullable = $nullable; } protected function getListenerClass(): string @@ -38,11 +41,12 @@ protected function getListenerClass(): string return Listener::class; } - #[ArrayShape(['field' => 'string'])] + #[ArrayShape(['field' => 'string', 'nullable' => 'bool'])] protected function getListenerArgs(): array { return [ - 'field' => $this->field + 'field' => $this->field, + 'nullable' => $this->nullable ]; } } diff --git a/tests/Uuid/Fixtures/Uuid/MultipleUuid.php b/tests/Uuid/Fixtures/Uuid/MultipleUuid.php new file mode 100644 index 0000000..7edd512 --- /dev/null +++ b/tests/Uuid/Fixtures/Uuid/MultipleUuid.php @@ -0,0 +1,50 @@ +enableProfiling(); - } - $this->dbal = new DatabaseManager(new DatabaseConfig()); $this->dbal->addDatabase( new Database( @@ -60,6 +60,11 @@ public function setUp(): void $this->getDriver() ) ); + + if (self::$config['debug'] ?? false) { + $this->setUpLogger($this->getDriver()); + $this->enableProfiling(); + } } public function tearDown(): void @@ -127,10 +132,8 @@ public function withSchema(SchemaInterface $schema): ORM return $this->orm; } - public function compileWithTokenizer(Tokenizer $tokenizer): void + public function compileWithTokenizer(Tokenizer $tokenizer, ReaderInterface $reader): void { - $reader = new AttributeReader(); - (new Compiler())->compile($this->registry = new Registry($this->dbal), [ new Entities($tokenizer->classLocator(), $reader), new ResetTables(), @@ -153,10 +156,17 @@ protected function getDatabase(): Database protected function save(object ...$entities): void { - $tr = new Transaction($this->orm); + $em = new EntityManager($this->orm); foreach ($entities as $entity) { - $tr->persist($entity); + $em->persist($entity); } - $tr->run(); + $em->run(); + } + + public static function readersDataProvider(): \Traversable + { + yield [new AnnotationReader()]; + yield [new AttributeReader()]; + yield [new SelectiveReader([new AttributeReader(), new AnnotationReader()])]; } } diff --git a/tests/Uuid/Functional/Driver/Common/Uuid/ListenerTest.php b/tests/Uuid/Functional/Driver/Common/Uuid/ListenerTest.php index 12c3fe1..bb9b9d4 100644 --- a/tests/Uuid/Functional/Driver/Common/Uuid/ListenerTest.php +++ b/tests/Uuid/Functional/Driver/Common/Uuid/ListenerTest.php @@ -35,6 +35,7 @@ public function setUp(): void 'users', [ 'uuid' => 'string', + 'optional_uuid' => 'string,nullable' ] ); } @@ -55,6 +56,24 @@ public function testAssignManually(): void $this->assertSame($bytes, $data->uuid->getBytes()); } + /** + * @dataProvider nullableTrueDataProvider + */ + public function testWithNullableTrue(array $listener): void + { + $this->withListeners($listener); + + $user = new User(); + $user->uuid = Uuid::uuid4(); + + $this->save($user); + + $select = new Select($this->orm->with(heap: new Heap()), User::class); + $data = $select->fetchData(); + + $this->assertNull($data[0]['optional_uuid']); + } + public function testUuid1(): void { $this->withListeners([Uuid1::class, ['node' => '00000fffffff', 'clockSeq' => 0xffff]]); @@ -183,14 +202,75 @@ public function withListeners(array|string $listeners): void SchemaInterface::DATABASE => 'default', SchemaInterface::TABLE => 'users', SchemaInterface::PRIMARY_KEY => 'uuid', - SchemaInterface::COLUMNS => ['uuid'], + SchemaInterface::COLUMNS => ['uuid', 'optional_uuid'], SchemaInterface::LISTENERS => [$listeners], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], SchemaInterface::TYPECAST => [ - 'uuid' => [Uuid::class, 'fromString'] + 'uuid' => [Uuid::class, 'fromString'], + 'optional_uuid' => [Uuid::class, 'fromString'] ] ] ])); } + + public static function nullableTrueDataProvider(): \Traversable + { + yield [ + [ + Uuid1::class, + [ + 'nullable' => true, + 'field' => 'optional_uuid', + 'node' => '00000fffffff', + 'clockSeq' => 0xffff + ] + ] + ]; + yield [ + [ + Uuid2::class, + [ + 'nullable' => true, + 'field' => 'optional_uuid', + 'localDomain' => Uuid::DCE_DOMAIN_PERSON, + 'localIdentifier' => new Integer('12345678') + ] + ] + ]; + yield [ + [ + Uuid3::class, + [ + 'nullable' => true, + 'field' => 'optional_uuid', + 'namespace' => Uuid::NAMESPACE_URL, + 'name' => 'https://example.com/foo' + ] + ] + ]; + yield [[Uuid4::class, ['nullable' => true, 'field' => 'optional_uuid']]]; + yield [ + [ + Uuid5::class, + [ + 'nullable' => true, + 'field' => 'optional_uuid', + 'namespace' => Uuid::NAMESPACE_URL, + 'name' => 'https://example.com/foo' + ] + ] + ]; + yield [ + [ + Uuid6::class, + [ + 'nullable' => true, + 'field' => 'optional_uuid', + 'node' => new Hexadecimal('0800200c9a66'), 'clockSeq' => 0x1669 + ] + ] + ]; + yield [[Uuid7::class, ['nullable' => true, 'field' => 'optional_uuid']]]; + } } diff --git a/tests/Uuid/Functional/Driver/Common/Uuid/UuidTest.php b/tests/Uuid/Functional/Driver/Common/Uuid/UuidTest.php index 3262d81..b4ceb7d 100644 --- a/tests/Uuid/Functional/Driver/Common/Uuid/UuidTest.php +++ b/tests/Uuid/Functional/Driver/Common/Uuid/UuidTest.php @@ -4,30 +4,40 @@ namespace Cycle\ORM\Entity\Behavior\Uuid\Tests\Functional\Driver\Common\Uuid; +use Cycle\ORM\Entity\Behavior\Uuid\Tests\Fixtures\Uuid\MultipleUuid; +use Cycle\ORM\Entity\Behavior\Uuid\Tests\Fixtures\Uuid\NullableUuid; use Cycle\ORM\Entity\Behavior\Uuid\Tests\Fixtures\Uuid\Post; use Cycle\ORM\Entity\Behavior\Uuid\Tests\Fixtures\Uuid\User; use Cycle\ORM\Entity\Behavior\Uuid\Tests\Functional\Driver\Common\BaseTest; use Cycle\Schema\Registry; use Ramsey\Uuid\Uuid; +use Spiral\Attributes\ReaderInterface; use Spiral\Tokenizer\Config\TokenizerConfig; use Spiral\Tokenizer\Tokenizer; abstract class UuidTest extends BaseTest { protected Registry $registry; + protected Tokenizer $tokenizer; public function setUp(): void { parent::setUp(); - $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ + $this->tokenizer = new Tokenizer(new TokenizerConfig([ 'directories' => [dirname(__DIR__, 4) . '/Fixtures/Uuid'], 'exclude' => [], - ]))); + ])); } - public function testColumnExist(): void + + /** + * @dataProvider readersDataProvider + */ + public function testColumnExist(ReaderInterface $reader): void { + $this->compileWithTokenizer($this->tokenizer, $reader); + $fields = $this->registry->getEntity(User::class)->getFields(); $this->assertTrue($fields->has('uuid')); @@ -37,8 +47,13 @@ public function testColumnExist(): void $this->assertSame(1, $fields->count()); } - public function testAddColumn(): void + /** + * @dataProvider readersDataProvider + */ + public function testAddColumn(ReaderInterface $reader): void { + $this->compileWithTokenizer($this->tokenizer, $reader); + $fields = $this->registry->getEntity(Post::class)->getFields(); $this->assertTrue($fields->has('customUuid')); @@ -46,4 +61,55 @@ public function testAddColumn(): void $this->assertSame('uuid', $fields->get('customUuid')->getType()); $this->assertSame([Uuid::class, 'fromString'], $fields->get('customUuid')->getTypecast()); } + + /** + * @dataProvider readersDataProvider + */ + public function testMultipleUuid(ReaderInterface $reader): void + { + $this->compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(MultipleUuid::class)->getFields(); + + $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->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->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->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()); + } + + /** + * @dataProvider readersDataProvider + */ + public function testAddNullableColumn(ReaderInterface $reader): void + { + $this->compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(NullableUuid::class)->getFields(); + + $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->assertTrue( + $this->registry + ->getTableSchema($this->registry->getEntity(NullableUuid::class)) + ->column('not_defined_uuid') + ->isNullable() + ); + } } diff --git a/tests/Uuid/Unit/Uuid1Test.php b/tests/Uuid/Unit/Uuid1Test.php new file mode 100644 index 0000000..fc8ca67 --- /dev/null +++ b/tests/Uuid/Unit/Uuid1Test.php @@ -0,0 +1,110 @@ +modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public static function schemaDataProvider(): \Traversable + { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + [] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + ['custom_uuid'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'node' => 'foo', + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + ['custom_uuid', null, 'foo'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'node' => 'foo', + 'clockSeq' => 3, + 'nullable' => false, + ], + ] + ] + ], + ['custom_uuid', null, 'foo', 3] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'node' => 'foo', + 'clockSeq' => 3, + 'nullable' => true, + ], + ] + ] + ], + ['custom_uuid', null, 'foo', 3, true] + ]; + } +} diff --git a/tests/Uuid/Unit/Uuid2Test.php b/tests/Uuid/Unit/Uuid2Test.php new file mode 100644 index 0000000..7220ff3 --- /dev/null +++ b/tests/Uuid/Unit/Uuid2Test.php @@ -0,0 +1,136 @@ +modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public static function schemaDataProvider(): \Traversable + { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'localDomain' => 3, + 'localIdentifier' => null, + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + [3] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'localDomain' => 3, + 'localIdentifier' => null, + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + [3, 'custom_uuid'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'localDomain' => 3, + 'localIdentifier' => 'foo', + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + [3, 'custom_uuid', null, 'foo'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'localDomain' => 3, + 'localIdentifier' => 'foo', + 'node' => 'bar', + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + [3, 'custom_uuid', null, 'foo', 'bar'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'localDomain' => 3, + 'localIdentifier' => 'foo', + 'node' => 'bar', + 'clockSeq' => 4, + 'nullable' => false, + ], + ] + ] + ], + [3, 'custom_uuid', null, 'foo', 'bar', 4], + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'localDomain' => 3, + 'localIdentifier' => 'foo', + 'node' => 'bar', + 'clockSeq' => 4, + 'nullable' => true, + ], + ] + ] + ], + [3, 'custom_uuid', null, 'foo', 'bar', 4, true] + ]; + } +} diff --git a/tests/Uuid/Unit/Uuid3Test.php b/tests/Uuid/Unit/Uuid3Test.php new file mode 100644 index 0000000..640619a --- /dev/null +++ b/tests/Uuid/Unit/Uuid3Test.php @@ -0,0 +1,78 @@ +modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public static function schemaDataProvider(): \Traversable + { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'namespace' => 'foo', + 'name' => 'bar', + 'nullable' => false, + ], + ] + ] + ], + ['foo', 'bar'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'namespace' => 'foo', + 'name' => 'bar', + 'nullable' => false, + ], + ] + ] + ], + ['foo', 'bar', 'custom_uuid'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'namespace' => 'foo', + 'name' => 'bar', + 'nullable' => true, + ], + ] + ] + ], + ['foo', 'bar', 'custom_uuid', null, true] + ]; + } +} diff --git a/tests/Uuid/Unit/Uuid4Test.php b/tests/Uuid/Unit/Uuid4Test.php new file mode 100644 index 0000000..ac807fe --- /dev/null +++ b/tests/Uuid/Unit/Uuid4Test.php @@ -0,0 +1,72 @@ +modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public static function schemaDataProvider(): \Traversable + { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'nullable' => false, + ], + ] + ] + ], + [] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'nullable' => false, + ], + ] + ] + ], + ['custom_uuid'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'nullable' => true, + ], + ] + ] + ], + ['custom_uuid', null, true] + ]; + } +} diff --git a/tests/Uuid/Unit/Uuid5Test.php b/tests/Uuid/Unit/Uuid5Test.php new file mode 100644 index 0000000..1054b75 --- /dev/null +++ b/tests/Uuid/Unit/Uuid5Test.php @@ -0,0 +1,78 @@ +modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public static function schemaDataProvider(): \Traversable + { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'namespace' => 'foo', + 'name' => 'bar', + 'nullable' => false, + ], + ] + ] + ], + ['foo', 'bar'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'namespace' => 'foo', + 'name' => 'bar', + 'nullable' => false, + ], + ] + ] + ], + ['foo', 'bar', 'custom_uuid'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'namespace' => 'foo', + 'name' => 'bar', + 'nullable' => true, + ], + ] + ] + ], + ['foo', 'bar', 'custom_uuid', null, true] + ]; + } +} diff --git a/tests/Uuid/Unit/Uuid6Test.php b/tests/Uuid/Unit/Uuid6Test.php new file mode 100644 index 0000000..89b6d4d --- /dev/null +++ b/tests/Uuid/Unit/Uuid6Test.php @@ -0,0 +1,110 @@ +modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public static function schemaDataProvider(): \Traversable + { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + [] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'node' => null, + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + ['custom_uuid'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'node' => 'foo', + 'clockSeq' => null, + 'nullable' => false, + ], + ] + ] + ], + ['custom_uuid', null, 'foo'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'node' => 'foo', + 'clockSeq' => 3, + 'nullable' => false, + ], + ] + ] + ], + ['custom_uuid', null, 'foo', 3] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'node' => 'foo', + 'clockSeq' => 3, + 'nullable' => true, + ], + ] + ] + ], + ['custom_uuid', null, 'foo', 3, true] + ]; + } +} diff --git a/tests/Uuid/Unit/Uuid7Test.php b/tests/Uuid/Unit/Uuid7Test.php new file mode 100644 index 0000000..8b1ab03 --- /dev/null +++ b/tests/Uuid/Unit/Uuid7Test.php @@ -0,0 +1,72 @@ +modifySchema($schema); + + $this->assertSame($expected, $schema); + } + + public static function schemaDataProvider(): \Traversable + { + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'uuid', + 'nullable' => false, + ], + ] + ] + ], + [] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'nullable' => false, + ], + ] + ] + ], + ['custom_uuid'] + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => Listener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_uuid', + 'nullable' => true, + ], + ] + ] + ], + ['custom_uuid', null, true] + ]; + } +}