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 hard. I want to store ULID (format "TTTTTTTTTTRRRRRRRRRRRRRRRR") in the database because they are not only sorteable but also contain a timestamp which is just perfect for my application. But when I tell a property to be "type=ulid" then it is stored as UUID (format: "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx") in the database.
I debugged half day long and got so annoyed by this that I started from scratch and still the problem exists.
Where did I go wrong?
(skip to the ULID heading if you like, the following may look long but 50% of it is just basic stuff)
Symfony
The thing 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/skeleton:5.4.* shyt
cd shyt; composer require webapp
- Do you want to include Docker configuration from recipes? YES (default)
bin/console about
shows Symfony Version 5.4.10 and PHP 7.4
ORM
Taken from https://symfony.com/doc/5.4/doctrine.html :
composer require symfony/orm-pack
composer require --dev symfony/maker-bundle
docker-compose up -d
ERROR: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network
So I add some lines to 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
- In ".env" set DATABASE_URL for the host "10.1.2.3"
bin/console doctrine:database:create
(silly but as documented)
Could not create database "app" for connection named default
An exception occurred while executing a query: SQLSTATE[42P04]: Duplicate database: 7 ERROR: database "app" already exists
Well, yes. Docker already did this.
- The
make:entity
is postponed until we have ULID capabilities.
ULID
We already lean into https://symfony.com/doc/5.4/components/uid.html (especially section ULIDs) :
composer require symfony/uid
bin/console make:entity Product
- "someProperty" as "ulid" not nullable
- Inspect the Product entity
Looks almost like in the documentation except it has an additional field (the primary key, an integer) and some getter/setter.
- bin/console make:migration
Test the ULID entity
In between we use tests to programatically create a DB entry:
composer require phpunit
to programatically create a database entrybin/console --env=test doctrine:migrations:migrate
bin/console --env=test doctrine:database:create
- The file "tests/FooTest.php" contains:
<?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 doctrine:query:sql 'TRUNCATE product'
just to be surebin/phpunit
bin/console --env=test doctrine:query:sql 'SELECT * FROM product'
A UUID is shown instead of a ULID.
Shows ULID instead of UUID in the database
Using ULID as Primary Key
First clean up a bit then do the example shown in https://symfony.com/doc/5.4/components/uid.html#ulids :
rm migrations/*
to start againbin/console --env=test doctrine:database:drop --force
bin/console --env=test doctrine:database:create
bin/console doctrine:database:drop --force
bin/console --env=test doctrine:database:create
- Edit "src/Entity/Product.php" to only contain the second ULID example from the documentation:
<?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;
}
// ...
}
(example from documentation was missing the repository line)
bin/console make:migration
bin/console --env=test doctrine:migrations:migrate
- Test is now a bit simpler:
public function testFoo(): void
{
self::getContainer()->get(ProductRepository::class)
->add(new Product());
self::getContainer()->get('doctrine.orm.entity_manager')
->flush();
}
bin/phpunit
(Risky is ok)bin/console --env=test doctrine:query:sql 'SELECT * FROM product'
Again those UUID instead of ULID
2
Answers
I’m trying out the ULID as well and followed the same documentation (https://symfony.com/blog/new-in-symfony-5-2-doctrine-types-for-uuid-and-ulid). I noticed you did one thing different in your code compared to the documentation.
In the entity change:
to
And add the following
This should the make the ULID work.
The dump of this would be something like this (sorry this is a dump in the console as I was testing the ulid, but I hope it gives you the information you need):
In the database it looks as followed:
Ulid in mariaDB
I hope this helps.
It’s bit late but for anyone whom facing this issue, I too have the database showing UUID instead of ULID and it seems to be the expected behavior from doctrine since you can use UUID/ULID interchangeably meaning even if you have UUID stored in the database but your entity is mapped on ULID you will have an ULID when retrieving the object from the database, also you can retrieve your same object by using ULID or UUID.
so for instance I have a User entity having ULID for its identifier so the stored object will have uuid as below :
if I retrieve my user with that UUID I’ll get :
now if you use that ULID to retrieve your user it will work too !
going further if you inspect that UUID you’ll see that the returned object has that uuid converted to base 32 :
and finally you can get that stored uuid by converting it to refc4122 as you can see below:
I’m not sure why doctrine doesn’t just store the ULID but its current behavior is not blocking you from using ULID in your project.
Hope this help !