From d3bd987c792a76fbdc2369623d9ac9a942fb0e83 Mon Sep 17 00:00:00 2001 From: Ben H Date: Mon, 2 Mar 2026 16:16:41 -0800 Subject: [PATCH] converting ItemTreasure IDs to GUIDs (ULIDs) as a PoC; we'll let it sit for a bit before deciding which (if any) other tables to make the same migration for --- composer.json | 1 + composer.lock | 163 ++++++++++++++++++- migrations/2026/03/Version20260302120000.php | 82 ++++++++++ src/Command/ExportItemCommand.php | 4 + src/Entity/ItemTreasure.php | 15 +- symfony.lock | 9 + 6 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 migrations/2026/03/Version20260302120000.php diff --git a/composer.json b/composer.json index 520fa212..8849db4c 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "symfony/serializer": "*", "symfony/translation": "*", "symfony/twig-bundle": "*", + "symfony/uid": "7.3.*", "symfony/validator": "*", "symfony/web-link": "*", "symfony/yaml": "*", diff --git a/composer.lock b/composer.lock index baa5f40b..6f93eece 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": "0f1a0c8972de601eb713f09aee2b5c6b", + "content-hash": "daec8012c43a4dd206e01fcb335d8a3d", "packages": [ { "name": "async-aws/core", @@ -6591,6 +6591,89 @@ ], "time": "2025-06-24T13:30:11+00:00" }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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" + }, { "name": "symfony/process", "version": "v7.3.11", @@ -8233,6 +8316,84 @@ ], "time": "2026-01-08T15:19:42+00:00" }, + { + "name": "symfony/uid", + "version": "v7.3.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "ae208d31706a608492ec09af9075dfcdb682be52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/ae208d31706a608492ec09af9075dfcdb682be52", + "reference": "ae208d31706a608492ec09af9075dfcdb682be52", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.3.10" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "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": "2026-01-03T23:21:50+00:00" + }, { "name": "symfony/validator", "version": "v7.3.11", diff --git a/migrations/2026/03/Version20260302120000.php b/migrations/2026/03/Version20260302120000.php new file mode 100644 index 00000000..4083cc1f --- /dev/null +++ b/migrations/2026/03/Version20260302120000.php @@ -0,0 +1,82 @@ +. + */ + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; +use Symfony\Component\Uid\Ulid; + +final class Version20260302120000 extends AbstractMigration +{ + public function getDescription(): string + { + return 'Convert item_treasure.id from auto-increment INT to ULID BINARY(16), and update item.treasure_id FK accordingly.'; + } + + public function up(Schema $schema): void + { + // 1. Drop FK constraint and unique index on item.treasure_id + $this->addSql('ALTER TABLE item DROP FOREIGN KEY FK_1F1B251E4DF05F8E'); + $this->addSql('DROP INDEX UNIQ_1F1B251E4DF05F8E ON item'); + + // 2. Add new BINARY(16) columns + $this->addSql('ALTER TABLE item_treasure ADD new_id BINARY(16) NULL AFTER id'); + $this->addSql('ALTER TABLE item ADD new_treasure_id BINARY(16) NULL AFTER treasure_id'); + } + + public function postUp(Schema $schema): void + { + // 3. Generate ULIDs for each existing item_treasure row + $rows = $this->connection->fetchAllAssociative('SELECT id FROM item_treasure'); + $idMap = []; + + foreach ($rows as $row) { + $oldId = $row['id']; + $ulid = new Ulid(); + $binary = $ulid->toBinary(); + $idMap[$oldId] = $binary; + + $this->connection->executeStatement( + 'UPDATE item_treasure SET new_id = :newId WHERE id = :oldId', + ['newId' => $binary, 'oldId' => $oldId] + ); + } + + // 4. Copy FK mappings: set item.new_treasure_id from the ULID map + foreach ($idMap as $oldId => $binary) { + $this->connection->executeStatement( + 'UPDATE item SET new_treasure_id = :newTreasureId WHERE treasure_id = :oldTreasureId', + ['newTreasureId' => $binary, 'oldTreasureId' => $oldId] + ); + } + + // 5. Drop old columns + $this->connection->executeStatement('ALTER TABLE item DROP COLUMN treasure_id'); + $this->connection->executeStatement('ALTER TABLE item_treasure DROP PRIMARY KEY, DROP COLUMN id'); + + // 6. Rename new columns + $this->connection->executeStatement('ALTER TABLE item_treasure CHANGE new_id id BINARY(16) NOT NULL'); + $this->connection->executeStatement('ALTER TABLE item CHANGE new_treasure_id treasure_id BINARY(16) DEFAULT NULL'); + + // 7. Re-add PK, unique index, and FK constraint + $this->connection->executeStatement('ALTER TABLE item_treasure ADD PRIMARY KEY (id)'); + $this->connection->executeStatement('CREATE UNIQUE INDEX UNIQ_1F1B251E4DF05F8E ON item (treasure_id)'); + $this->connection->executeStatement('ALTER TABLE item ADD CONSTRAINT FK_1F1B251E4DF05F8E FOREIGN KEY (treasure_id) REFERENCES item_treasure (id)'); + } + + public function down(Schema $schema): void + { + throw new \RuntimeException('Cannot reverse ULID migration — original auto-increment IDs are lost.'); + } +} diff --git a/src/Command/ExportItemCommand.php b/src/Command/ExportItemCommand.php index 531ff01c..c831addd 100644 --- a/src/Command/ExportItemCommand.php +++ b/src/Command/ExportItemCommand.php @@ -29,6 +29,7 @@ use App\Functions\ItemRepository; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Uid\AbstractUid; class ExportItemCommand extends PoppySeedPetsCommand { @@ -185,6 +186,9 @@ private static function encodeValueToSql(mixed $value): mixed if(is_array($value)) return '"' . addslashes(json_encode($value)) . '"'; + if($value instanceof AbstractUid) + return '0x' . bin2hex($value->toBinary()); + if(is_object($value)) return $value->getId(); diff --git a/src/Entity/ItemTreasure.php b/src/Entity/ItemTreasure.php index 51d7adb0..2e26a306 100644 --- a/src/Entity/ItemTreasure.php +++ b/src/Entity/ItemTreasure.php @@ -14,16 +14,16 @@ namespace App\Entity; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Types\UlidType; use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Uid\Ulid; #[ORM\Entity] class ItemTreasure { #[ORM\Id] - #[ORM\GeneratedValue] - #[ORM\Column(type: 'integer')] - /** @phpstan-ignore property.unusedType */ - private ?int $id = null; + #[ORM\Column(type: UlidType::NAME)] + private Ulid $id; #[Groups(["dragonTreasure"])] #[ORM\Column(type: 'integer')] @@ -37,7 +37,12 @@ class ItemTreasure #[ORM\Column(type: 'integer')] private int $gems = 0; - public function getId(): ?int + public function __construct() + { + $this->id = new Ulid(); + } + + public function getId(): Ulid { return $this->id; } diff --git a/symfony.lock b/symfony.lock index 7a9f0c5f..5deffd7f 100644 --- a/symfony.lock +++ b/symfony.lock @@ -279,6 +279,15 @@ "./templates/base.html.twig" ] }, + "symfony/uid": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "0df5844274d871b37fc3816c57a768ffc60a43a5" + } + }, "symfony/validator": { "version": "6.4", "recipe": {