Yesterday I tried to do what Symfony shouted out in some blog post (https://symfony.com/blog/new-in-symfony-5-2-doctrine-types-for-uuid-and-ulid) but failed. I want to store ULIDs (in the format "TTTTTTTTTTRRRRRRRRRRRR") in the database because not only are they sortable, but they also contain a timestamp which is perfect for my application. However, when I tell the attribute to be "type=ulid", it is stored in the database as a UUID (format: "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx").
I debugged it for a long time and was so annoyed by it that I started from scratch and the problem still exists.
Where did I go wrong?
(Skip to the ULID header if you wish, the following may seem long but 50% of it is just the basics)
Things I do over and over again Taken from https://symfony.com/doc/5.4/setup.html:
stat shyt # does not exist
composer Create project symfony/sculpture:5.4.* shyt
cd shyt; composer requires webapp
bin/console about
Shows Symfony version 5.4.10 and PHP 7.4Taken from https://symfony.com/doc/5.4/doctrine.html:
composer requires symfony/orm-pack
composer requires --dev symfony/maker-bundle
docker-compose up -d
Error: No available, non-overlapping IPv4 address pool found in the default address pool assigned to the network
So I added some lines in the docker-compose.override.yml file:
networks: default: ipam: config: - subnet: 10.1.2.1/24 services: database: # [...] doctrine/doctrine-bundle stuff [...] networks: default: ipv4_address: 10.1.2.3
bin/console Doctrine: database: create
(silly, but as documented) Cannot create database "app" for the connection named default Exception occurred while executing query: SQLSTATE[42P04]: Duplicate database: 7 Error: Database "app" already exists
um, yes. Docker already does this.
make:entity
is deferred until we have the ULID functionality. We've leaned towards https://symfony.com/doc/5.4/components/uid.html (especially the ULID part):
composer requires symfony/uid
bin/console make:entity Product
Looks almost the same as in the documentation, except it has an additional field (primary key, an integer) and some getters/setters.
In between, we use tests to create database entries programmatically:
composer requires phpunit
Create database entries programmaticallybin/console --env=testism:migrations:migrate
bin/console --env=testism:database:create
<?php namespace AppTests; use AppEntityProduct; use AppRepositoryProductRepository; use DoctrineORMEntityManager; use SymfonyBundleFrameworkBundleTestKernelTestCase; use SymfonyComponentUidUlid; class FooTest extends KernelTestCase { public function testFoo(): void { $product = new Product(); $product->setSomeProperty(new Ulid()); static::assertNotNull($product->getSomeProperty()); self::getContainer()->get(ProductRepository::class) ->add($product); self::getContainer()->get('doctrine.orm.entity_manager') ->flush(); } }
bin/console --env=test ism:query:sql 'TRUNCATE Product'
Just to be surebin/phpunit
bin/console --env=testism:query:sql 'SELECT * FROM product'
Show UUID, not ULID.
Display ULID instead of UUID in database
Clean up first, then execute the example shown at https://symfony.com/doc/5.4/components/uid.html#ulids:
rm migrate/*
Start overbin/console --env=testism:database:drop --force
bin/console --env=testism:database:create
bin/console-ism:database:drop --force
bin/console --env=testism:database:create
<?php namespace AppEntity; use DoctrineORMMapping as ORM; use SymfonyComponentUidUlid; use AppRepositoryProductRepository; /** * @ORMEntity(repositoryClass=ProductRepository::class) */ class Product { /** * @ORMId * @ORMColumn(type="ulid", unique=true) * @ORMGeneratedValue(strategy="CUSTOM") * @ORMCustomIdGenerator(class="doctrine.ulid_generator") */ private $id; public function getId(): ?Ulid { return $this->id; } // ... }
(The example in the documentation is missing the repository line)
bin/console make:migration
bin/console --env=testism:migrations:migrate
public function testFoo(): void { self::getContainer()->get(ProductRepository::class) ->add(new Product()); self::getContainer()->get('doctrine.orm.entity_manager') ->flush(); }
bin/phpunit
(It’s okay if there is risk)bin/console --env=testism:query:sql 'SELECT * FROM product'
Use UUID again instead of ULID
Database shows UUID instead of ULID
A bit late, but for anyone facing this problem, I also have databases that show UUIDs instead of ULIDs, which seems to be the expected behavior in Doctrine since you can use UUIDs/ULIDs interchangeably, meaning even You store UUID in database but your entities are mapped to ULID, when retrieving object from database you will have ULID, you can also retrieve the same object using ULID or UUID.
For example, I have a user entity whose identifier has a ULID, so the stored object will have a uuid like this:
If I retrieve my user using that UUID I will get:
Now if you use that ULID to retrieve your user, it will work too!
If you check the UUID, you will find that the returned object has the uuid converted to base 32:
Finally, you can get the stored uuid by converting it to refc4122 like this:
I'm not sure why Principle doesn't just store ULIDs, but its current behavior doesn't prevent you from using ULIDs in your project. Hope this helps!