From d05956e9a1ed3f0115f5850dc436d83792760ecd Mon Sep 17 00:00:00 2001 From: Adam Dyson Date: Tue, 15 Jul 2025 02:08:14 +1000 Subject: [PATCH 1/7] Added behavior and listener for ULID identifier --- src/Listener/Ulid.php | 29 ++++++++++++ src/Ulid.php | 105 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 src/Listener/Ulid.php create mode 100644 src/Ulid.php diff --git a/src/Listener/Ulid.php b/src/Listener/Ulid.php new file mode 100644 index 0000000..763b3bf --- /dev/null +++ b/src/Listener/Ulid.php @@ -0,0 +1,29 @@ +nullable || isset($event->state->getData()[$this->field])) { + return; + } + + $identifier = (new UlidFactory())->create(); + + $event->state->register($this->field, $identifier); + } +} diff --git a/src/Ulid.php b/src/Ulid.php new file mode 100644 index 0000000..55b0c13 --- /dev/null +++ b/src/Ulid.php @@ -0,0 +1,105 @@ +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) { + $modifier->addUlidColumn( + $this->column, + $this->field, + $this->nullable ? null : GeneratedField::BEFORE_INSERT, + )->nullable($this->nullable); + + $modifier->setTypecast( + $registry->getEntity($this->role)->getFields()->get($this->field), + [self::class, 'fromString'], + ); + } + } + + #[\Override] + public function render(Registry $registry): void + { + $modifier = new RegistryModifier($registry, $this->role); + $this->column = $modifier->findColumnName($this->field, $this->column) ?? $this->field; + + $modifier->addUlidColumn( + $this->column, + $this->field, + $this->nullable ? null : GeneratedField::BEFORE_INSERT, + )->nullable($this->nullable); + + $modifier->setTypecast( + $registry->getEntity($this->role)->getFields()->get($this->field), + [self::class, 'fromString'], + ); + } + + #[\Override] + protected function getListenerClass(): string + { + return Listener::class; + } + + #[ArrayShape([ + 'field' => 'string', + 'nullable' => 'bool', + ])] + #[\Override] + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + 'nullable' => $this->nullable, + ]; + } +} From 03c58ac57dde69bce0f7d45d0fc7c84dcb3437b5 Mon Sep 17 00:00:00 2001 From: Adam Dyson Date: Tue, 15 Jul 2025 02:09:35 +1000 Subject: [PATCH 2/7] Added tests for ULID identifier --- .../Identifier/Fixtures/Ulid/MultipleUlid.php | 41 ++++++ .../Identifier/Fixtures/Ulid/NullableUlid.php | 27 ++++ tests/Identifier/Fixtures/Ulid/Post.php | 24 ++++ tests/Identifier/Fixtures/Ulid/User.php | 25 ++++ .../Driver/Common/Ulid/ListenerTest.php | 107 ++++++++++++++++ .../Driver/Common/Ulid/UlidTest.php | 117 ++++++++++++++++++ .../Driver/MySQL/Ulid/ListenerTest.php | 17 +++ .../Functional/Driver/MySQL/Ulid/UlidTest.php | 17 +++ .../Driver/Postgres/Ulid/ListenerTest.php | 17 +++ .../Driver/Postgres/Ulid/UlidTest.php | 17 +++ .../Driver/SQLServer/Ulid/ListenerTest.php | 17 +++ .../Driver/SQLServer/Ulid/UlidTest.php | 17 +++ .../Driver/SQLite/Ulid/ListenerTest.php | 17 +++ .../Driver/SQLite/Ulid/UlidTest.php | 17 +++ tests/Identifier/Unit/UlidTest.php | 72 +++++++++++ 15 files changed, 549 insertions(+) create mode 100644 tests/Identifier/Fixtures/Ulid/MultipleUlid.php create mode 100644 tests/Identifier/Fixtures/Ulid/NullableUlid.php create mode 100644 tests/Identifier/Fixtures/Ulid/Post.php create mode 100644 tests/Identifier/Fixtures/Ulid/User.php create mode 100644 tests/Identifier/Functional/Driver/Common/Ulid/ListenerTest.php create mode 100644 tests/Identifier/Functional/Driver/Common/Ulid/UlidTest.php create mode 100644 tests/Identifier/Functional/Driver/MySQL/Ulid/ListenerTest.php create mode 100644 tests/Identifier/Functional/Driver/MySQL/Ulid/UlidTest.php create mode 100644 tests/Identifier/Functional/Driver/Postgres/Ulid/ListenerTest.php create mode 100644 tests/Identifier/Functional/Driver/Postgres/Ulid/UlidTest.php create mode 100644 tests/Identifier/Functional/Driver/SQLServer/Ulid/ListenerTest.php create mode 100644 tests/Identifier/Functional/Driver/SQLServer/Ulid/UlidTest.php create mode 100644 tests/Identifier/Functional/Driver/SQLite/Ulid/ListenerTest.php create mode 100644 tests/Identifier/Functional/Driver/SQLite/Ulid/UlidTest.php create mode 100644 tests/Identifier/Unit/UlidTest.php diff --git a/tests/Identifier/Fixtures/Ulid/MultipleUlid.php b/tests/Identifier/Fixtures/Ulid/MultipleUlid.php new file mode 100644 index 0000000..8f17f84 --- /dev/null +++ b/tests/Identifier/Fixtures/Ulid/MultipleUlid.php @@ -0,0 +1,41 @@ +withListeners(UlidListener::class); + + $user = new User(); + $user->ulid = (new UlidFactory())->create(); + $bytes = $user->ulid->toBytes(); + + $this->save($user); + + $select = new Select($this->orm->with(heap: new Heap()), User::class); + $data = $select->fetchOne(); + + $this->assertSame($bytes, $data->ulid->toBytes()); + } + + public function testWithNullableTrue(): void + { + $this->withListeners([ + UlidListener::class, + [ + 'field' => 'foo_ulid', + 'nullable' => true, + ], + ]); + + $user = new User(); + $user->ulid = (new UlidFactory())->create(); + + $this->save($user); + + $select = new Select($this->orm->with(heap: new Heap()), User::class); + $data = $select->fetchData(); + + $this->assertNull($data[0]['foo_ulid']); + } + + public function testUlid(): void + { + $this->withListeners(UlidListener::class); + + $user = new User(); + $this->save($user); + + $select = new Select($this->orm->with(heap: new Heap()), User::class); + $data = $select->fetchOne(); + + $this->assertInstanceOf(UlidInterface::class, $data->ulid); + $this->assertIsString($data->ulid->toBytes()); + $this->assertIsString($data->ulid->toString()); + } + + public function withListeners(array|string $listeners): void + { + $this->withSchema(new Schema([ + User::class => [ + SchemaInterface::ROLE => 'user', + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'users', + SchemaInterface::PRIMARY_KEY => 'ulid', + SchemaInterface::COLUMNS => ['ulid', 'foo_ulid'], + SchemaInterface::LISTENERS => [$listeners], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [], + SchemaInterface::TYPECAST => [ + 'ulid' => [Ulid::class, 'fromString'], + 'foo_ulid' => [Ulid::class, 'fromString'], + ], + ], + ])); + } + + public function setUp(): void + { + parent::setUp(); + + $this->makeTable( + 'users', + [ + 'ulid' => 'string', + 'foo_ulid' => 'string,nullable', + ], + ); + } +} diff --git a/tests/Identifier/Functional/Driver/Common/Ulid/UlidTest.php b/tests/Identifier/Functional/Driver/Common/Ulid/UlidTest.php new file mode 100644 index 0000000..b35a08a --- /dev/null +++ b/tests/Identifier/Functional/Driver/Common/Ulid/UlidTest.php @@ -0,0 +1,117 @@ +compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(User::class)->getFields(); + + $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(GeneratedField::BEFORE_INSERT, $fields->get('ulid')->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('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(GeneratedField::BEFORE_INSERT, $fields->get('customUlid')->getGenerated()); + } + + /** + * @dataProvider readersDataProvider + */ + public function testMultipleUlid(ReaderInterface $reader): void + { + $this->compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(MultipleUlid::class)->getFields(); + + $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(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(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(GeneratedField::BEFORE_INSERT, $fields->get('bar')->getGenerated()); + } + + /** + * @dataProvider readersDataProvider + */ + public function testAddNullableColumn(ReaderInterface $reader): void + { + $this->compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(NullableUlid::class)->getFields(); + + $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->assertTrue( + $this->registry + ->getTableSchema($this->registry->getEntity(NullableUlid::class)) + ->column('not_defined_ulid') + ->isNullable(), + ); + $this->assertNull($fields->get('notDefinedUlid')->getGenerated()); + } + + public function setUp(): void + { + parent::setUp(); + + $locator = new ClassLocator((new Finder())->files()->in([\dirname(__DIR__, 4) . '/Fixtures/Ulid'])); + $reader = new AttributeReader(); + $this->tokenizer = new TokenizerEntityLocator($locator, $reader); + } +} diff --git a/tests/Identifier/Functional/Driver/MySQL/Ulid/ListenerTest.php b/tests/Identifier/Functional/Driver/MySQL/Ulid/ListenerTest.php new file mode 100644 index 0000000..7d076a4 --- /dev/null +++ b/tests/Identifier/Functional/Driver/MySQL/Ulid/ListenerTest.php @@ -0,0 +1,17 @@ + [ + [ + ListenerProvider::DEFINITION_CLASS => UlidListener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'ulid', + 'nullable' => false, + ], + ], + ], + ], + [], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => UlidListener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_ulid', + 'nullable' => false, + ], + ], + ], + ], + ['custom_ulid'], + ]; + yield [ + [ + SchemaInterface::LISTENERS => [ + [ + ListenerProvider::DEFINITION_CLASS => UlidListener::class, + ListenerProvider::DEFINITION_ARGS => [ + 'field' => 'custom_ulid', + 'nullable' => true, + ], + ], + ], + ], + ['custom_ulid', null, true], + ]; + } + + /** + * @dataProvider schemaDataProvider + */ + public function testModifySchema(array $expected, array $args): void + { + $schema = []; + $ulid = new Ulid(...$args); + $ulid->modifySchema($schema); + + $this->assertSame($expected, $schema); + } +} From 1991103b2e5651c093928cf7427d60e1fb35c12e Mon Sep 17 00:00:00 2001 From: Adam Dyson Date: Tue, 15 Jul 2025 02:09:49 +1000 Subject: [PATCH 3/7] Added tests for combined identifiers --- .../Fixtures/Combined/MultipleIdentifiers.php | 51 ++++++ .../Driver/Common/Combined/CombinedTest.php | 68 ++++++++ .../Driver/Common/Combined/ListenerTest.php | 151 ++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 tests/Identifier/Fixtures/Combined/MultipleIdentifiers.php create mode 100644 tests/Identifier/Functional/Driver/Common/Combined/CombinedTest.php create mode 100644 tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php diff --git a/tests/Identifier/Fixtures/Combined/MultipleIdentifiers.php b/tests/Identifier/Fixtures/Combined/MultipleIdentifiers.php new file mode 100644 index 0000000..08193fe --- /dev/null +++ b/tests/Identifier/Fixtures/Combined/MultipleIdentifiers.php @@ -0,0 +1,51 @@ +compileWithTokenizer($this->tokenizer, $reader); + + $fields = $this->registry->getEntity(MultipleIdentifiers::class)->getFields(); + + $this->assertSame(4, $fields->count()); + + $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(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(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(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(GeneratedField::BEFORE_INSERT, $fields->get('ulidNullable')->getGenerated()); + } + + public function setUp(): void + { + parent::setUp(); + + $locator = new ClassLocator((new Finder())->files()->in([\dirname(__DIR__, 4) . '/Fixtures/Combined'])); + $reader = new AttributeReader(); + $this->tokenizer = new TokenizerEntityLocator($locator, $reader); + } +} diff --git a/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php b/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php new file mode 100644 index 0000000..9a8ac9c --- /dev/null +++ b/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php @@ -0,0 +1,151 @@ +withListeners([ + Uuid4Listener::class, + UlidListener::class + ]); + + $identifiers = new MultipleIdentifiers(); + $identifiers->uuid = (new UuidFactory())->v4(); + $identifiers->ulid = (new UlidFactory())->create(); + $uuidBytes = $identifiers->uuid->toBytes(); + $ulidBytes = $identifiers->ulid->toBytes(); + + $this->save($identifiers); + + $select = new Select($this->orm->with(heap: new Heap()), MultipleIdentifiers::class); + $data = $select->fetchOne(); + + $this->assertSame($uuidBytes, $data->uuid->toBytes()); + $this->assertSame($ulidBytes, $data->ulid->toBytes()); + } + + public function testWithNullableTrue(): void + { + $this->withListeners([ + Uuid4Listener::class, + UlidListener::class, + ]); + + $identifiers = new MultipleIdentifiers(); + $identifiers->uuid = (new UuidFactory())->v4(); + $identifiers->ulid = (new UlidFactory())->create(); + + $this->save($identifiers); + + $select = new Select($this->orm->with(heap: new Heap()), User::class); + $data = $select->fetchData(); + + $this->assertNull($data[0]['uuid_nullable']); + $this->assertNull($data[0]['ulid_nullable']); + } + + public function testCombined(): void + { + $this->withListeners([ + Uuid4Listener::class, + UlidListener::class, + ]); + + $identifiers = new MultipleIdentifiers(); + $this->save($identifiers); + + $select = new Select($this->orm->with(heap: new Heap()), User::class); + $data = $select->fetchOne(); + + $this->assertInstanceOf(UntypedUuid::class, $data->uuid); + $this->assertInstanceOf(UlidInterface::class, $data->ulid); + $this->assertNull($data->uuidNullable); + $this->assertNull($data->ulidNullable); + $this->assertIsString($data->uuid->toBytes()); + $this->assertIsString($data->uuid->toString()); + $this->assertIsString($data->ulid->toBytes()); + $this->assertIsString($data->ulid->toString()); + } + + public function testComparison(): void + { + $this->withListeners([ + Uuid4Listener::class, + UlidListener::class, + ]); + + $expectedDate = '2025-06-17 03:24:36.160 +00:00'; + + $identifiers = new MultipleIdentifiers(); + $identifiers->uuid = (new UuidFactory())->createFromString('01977bea-d1c0-7154-87bb-6550974155c2'); + $identifiers->ulid = (new UlidFactory())->createFromString('01JXXYNME0E5A8FEV5A2BM2NE2'); + + $this->save($identifiers); + + $select = new Select($this->orm->with(heap: new Heap()), MultipleIdentifiers::class); + $data = $select->fetchOne(); + + $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->assertTrue($data->uuid->equals($data->ulid)); + } + + public function withListeners(array|string $listeners): void + { + $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::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'], + ], + ], + ])); + } + + public function setUp(): void + { + parent::setUp(); + + $this->makeTable( + 'multiple_identifiers', + [ + 'uuid' => 'string', + 'uuid_nullable' => 'string,nullable', + 'ulid' => 'string', + 'ulid_nullable' => 'string,nullable', + ], + ); + } +} From aa7044133fcc9a485e6fa109335c8caa8be5fccd Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 14 Jul 2025 16:10:37 +0000 Subject: [PATCH 4/7] style(php-cs-fixer): fix coding standards --- .../Functional/Driver/Common/Combined/ListenerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php b/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php index 9a8ac9c..95322c3 100644 --- a/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php +++ b/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php @@ -28,7 +28,7 @@ public function testAssignManually(): void { $this->withListeners([ Uuid4Listener::class, - UlidListener::class + UlidListener::class, ]); $identifiers = new MultipleIdentifiers(); From f39ee47bac3a95d4931b27f597c36c9d82db5018 Mon Sep 17 00:00:00 2001 From: Adam Dyson Date: Tue, 15 Jul 2025 10:04:21 +1000 Subject: [PATCH 5/7] Updated code styling --- .../Functional/Driver/Common/Combined/ListenerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php b/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php index 9a8ac9c..95322c3 100644 --- a/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php +++ b/tests/Identifier/Functional/Driver/Common/Combined/ListenerTest.php @@ -28,7 +28,7 @@ public function testAssignManually(): void { $this->withListeners([ Uuid4Listener::class, - UlidListener::class + UlidListener::class, ]); $identifiers = new MultipleIdentifiers(); From 5342d26e4afed37ce76fd6af3f81c3c4e6df86b7 Mon Sep 17 00:00:00 2001 From: Adam Dyson Date: Tue, 15 Jul 2025 10:04:29 +1000 Subject: [PATCH 6/7] Updated dependencies --- composer.json | 10 +++++----- composer.lock | 45 +++++++++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 3a197d3..748ed8c 100644 --- a/composer.json +++ b/composer.json @@ -41,14 +41,14 @@ }, "require": { "php": ">=8.2", - "cycle/entity-behavior": "^1.3.1", - "ramsey/identifier": "^0.1.0" + "cycle/entity-behavior": "^1.6", + "ramsey/identifier": "^0.1" }, "require-dev": { - "cycle/annotated": "^4.3.0", - "doctrine/annotations": "^1.14.4 || ^2.0.2", + "cycle/annotated": "^4.3", + "doctrine/annotations": "^1.14 || ^2.0", "phpunit/phpunit": "^9.5", - "spiral/code-style": "^2.2.0", + "spiral/code-style": "^2.2", "spiral/tokenizer": "^2.14 || ^3.0", "vimeo/psalm": "^5.26 || ^6.6" }, diff --git a/composer.lock b/composer.lock index 5db2ce0..dffb64d 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": "7d66ff1625ef5c0f32210c131bba143c", + "content-hash": "06cc0fca7390f2687878ee8869f101ea", "packages": [ { "name": "brick/math", @@ -68,16 +68,16 @@ }, { "name": "cycle/database", - "version": "2.13.0", + "version": "2.14.0", "source": { "type": "git", "url": "https://github.com/cycle/database.git", - "reference": "0f462319a0e22d05ccf6dc4721514ce609930bf7" + "reference": "876fbc2bc0d068f047388c0bd9b354e4d891af07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/database/zipball/0f462319a0e22d05ccf6dc4721514ce609930bf7", - "reference": "0f462319a0e22d05ccf6dc4721514ce609930bf7", + "url": "https://api.github.com/repos/cycle/database/zipball/876fbc2bc0d068f047388c0bd9b354e4d891af07", + "reference": "876fbc2bc0d068f047388c0bd9b354e4d891af07", "shasum": "" }, "require": { @@ -157,23 +157,24 @@ "type": "github" } ], - "time": "2025-03-27T15:49:53+00:00" + "time": "2025-07-14T11:36:41+00:00" }, { "name": "cycle/entity-behavior", - "version": "1.5.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/cycle/entity-behavior.git", - "reference": "e8ec084d8ed07d496d3086f46e2219f0be976df8" + "reference": "49b0c71485855f16193b0720d637d822d65f688c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cycle/entity-behavior/zipball/e8ec084d8ed07d496d3086f46e2219f0be976df8", - "reference": "e8ec084d8ed07d496d3086f46e2219f0be976df8", + "url": "https://api.github.com/repos/cycle/entity-behavior/zipball/49b0c71485855f16193b0720d637d822d65f688c", + "reference": "49b0c71485855f16193b0720d637d822d65f688c", "shasum": "" }, "require": { + "cycle/database": "^2.14", "cycle/orm": "^2.10", "cycle/schema-builder": "^2.8", "php": ">=8.0", @@ -231,7 +232,7 @@ "type": "github" } ], - "time": "2025-07-13T07:05:33+00:00" + "time": "2025-07-14T19:37:04+00:00" }, { "name": "cycle/orm", @@ -3199,16 +3200,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.82.2", + "version": "v3.83.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "684ed3ab41008a2a4848de8bde17eb168c596247" + "reference": "b83916e79a6386a1ec43fdd72391aeb13b63282f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/684ed3ab41008a2a4848de8bde17eb168c596247", - "reference": "684ed3ab41008a2a4848de8bde17eb168c596247", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b83916e79a6386a1ec43fdd72391aeb13b63282f", + "reference": "b83916e79a6386a1ec43fdd72391aeb13b63282f", "shasum": "" }, "require": { @@ -3292,7 +3293,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.82.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.83.0" }, "funding": [ { @@ -3300,7 +3301,7 @@ "type": "github" } ], - "time": "2025-07-08T21:13:15+00:00" + "time": "2025-07-14T15:41:41+00:00" }, { "name": "kelunik/certificate", @@ -7954,16 +7955,16 @@ }, { "name": "vimeo/psalm", - "version": "6.12.1", + "version": "6.13.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "e71404b0465be25cf7f8a631b298c01c5ddd864f" + "reference": "70cdf647255a1362b426bb0f522a85817b8c791c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/e71404b0465be25cf7f8a631b298c01c5ddd864f", - "reference": "e71404b0465be25cf7f8a631b298c01c5ddd864f", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/70cdf647255a1362b426bb0f522a85817b8c791c", + "reference": "70cdf647255a1362b426bb0f522a85817b8c791c", "shasum": "" }, "require": { @@ -8068,7 +8069,7 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2025-07-04T09:56:28+00:00" + "time": "2025-07-14T09:59:17+00:00" }, { "name": "webmozart/assert", From 7914a722228a9d5a49f6777d76dc6eeb3af61461 Mon Sep 17 00:00:00 2001 From: Adam Dyson Date: Tue, 15 Jul 2025 10:46:59 +1000 Subject: [PATCH 7/7] Updated documentation --- README.md | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 142 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d99f22b..d6c358b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The package provides the ability to use `ramsey/identifier` as various Cycle ORM ## Installation -> Note this package required PHP `8.2` or newer. +> **Note:** Due to a dependency on `ramsey/identifier` this package requires PHP `8.2` or newer. Install this package as a dependency using Composer. @@ -17,23 +17,157 @@ Install this package as a dependency using Composer. composer require cycle/entity-behavior-identifier ``` -## Example +## Snowflake Examples -They are randomly-generated and do not contain any information about the time they are created or the machine that -generated them. +**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. + +> **Note:** Support for Snowflake identifiers will arrive soon, stay tuned. + +## 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. + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Ulid; + +#[Entity] +#[Identifier\Ulid(field: 'id')] +class User +{ + #[Column(type: 'ulid', primary: true)] + private Ulid $id; +} +``` + +## 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. + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Uuid; + +#[Entity] +#[Identifier\Uuid1(field: 'id')] +class User +{ + #[Column(type: 'uuid', primary: true)] + private Uuid $id; +} +``` + +**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. + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Uuid; + +#[Entity] +#[Identifier\Uuid2(field: 'id')] +class User +{ + #[Column(type: 'uuid', primary: true)] + private Uuid $id; +} +``` + +**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; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Uuid; + +#[Entity] +#[Identifier\Uuid3( + field: 'id', + namespace: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + name: 'example.com', +)] +class User +{ + #[Column(type: 'uuid', primary: true)] + private Uuid $id; +} +``` + +**UUID Version 4 (Random):** Generated entirely from random or pseudo-random numbers, offering high unpredictability and uniqueness. + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Uuid; + +#[Entity] +#[Identifier\Uuid4(field: 'id')] +class User +{ + #[Column(type: 'uuid', primary: true)] + private Uuid $id; +} +``` + +**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; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Uuid; + +#[Entity] +#[Identifier\Uuid5( + field: 'id', + namespace: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', + name: 'example.com', +)] +class User +{ + #[Column(type: 'uuid', primary: true)] + private Uuid $id; +} +``` + +**UUID Version 6 (Draft/Upcoming):** An experimental or proposed version focused on improving time-based UUIDs with more sortable properties (not yet widely adopted). + +```php +use Cycle\Annotated\Annotation\Column; +use Cycle\Annotated\Annotation\Entity; +use Cycle\ORM\Entity\Behavior\Identifier; +use Ramsey\Identifier\Uuid; + +#[Entity] +#[Identifier\Uuid6(field: 'id')] +class User +{ + #[Column(type: 'uuid', primary: true)] + private Uuid $id; +} +``` + +**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; use Cycle\Annotated\Annotation\Entity; -use Cycle\ORM\Entity\Behavior\Idetifier\Uuid4; +use Cycle\ORM\Entity\Behavior\Identifier; use Ramsey\Identifier\Uuid; #[Entity] -#[Uuid4] +#[Identifier\Uuid7(field: 'id')] class User { - #[Column(field: 'uuid', type: 'uuid', primary: true)] - private Uuid $uuid; + #[Column(type: 'uuid', primary: true)] + private Uuid $id; } ```