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 existcomposer create-project symfony/skeleton:5.4.* shytcd shyt; composer require webapp- Do you want to include Docker configuration from recipes? YES (default)
bin/console aboutshows Symfony Version 5.4.10 and PHP 7.4
ORM
Taken from https://symfony.com/doc/5.4/doctrine.html :
composer require symfony/orm-packcomposer require --dev symfony/maker-bundledocker-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:entityis 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/uidbin/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 phpunitto programatically create a database entrybin/console --env=test doctrine:migrations:migratebin/console --env=test doctrine:database:create- The file "tests/FooTest.php" contains:
<?php
namespace App\Tests;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Uid\Ulid;
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/phpunitbin/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 --forcebin/console --env=test doctrine:database:createbin/console doctrine:database:drop --forcebin/console --env=test doctrine:database:create- Edit "src/Entity/Product.php" to only contain the second ULID example from the documentation:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Ulid;
use App\Repository\ProductRepository;
/**
* @ORM\Entity(repositoryClass=ProductRepository::class)
*/
class Product
{
/**
* @ORM\Id
* @ORM\Column(type="ulid", unique=true)
* @ORM\GeneratedValue(strategy="CUSTOM")
* @ORM\CustomIdGenerator(class="doctrine.ulid_generator")
*/
private $id;
public function getId(): ?Ulid
{
return $this->id;
}
// ...
}
(example from documentation was missing the repository line)
bin/console make:migrationbin/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