31.01.2022・TechStuff
31.01.2022・TechStuff

Symfony 6, Doctrine, DateTime and microseconds

Fabian Blechschmidt

Not tested in production

This is not tested on a stage or production environment yet – and I’m sure I forget to remove this warning, once it is properly tested. Feel free to write a comment, so we can update this post 🙂

Doctrine isn’t yet able to handle microseconds, if I understand correctly, especially because they are not sure how to handle the on the difference database management systems.

But thankfully it is relatively simple to implement our own type (or overwrite the datetime type).

Implement your own type

Thanks to flauschi, I was able to adapt his code for our purpose (especially without using Carbon)

<?php

declare(strict_types=1);

namespace Winkelwagen\Dbal;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\Type;

class DateTimeWithMicroseconds extends Type
{
    private const TYPENAME = 'datetime';

    public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
    {
        if (isset($column['version']) && $column['version'] === true) {
            return 'TIMESTAMP';
        }

        if ($platform instanceof PostgreSqlPlatform) {
            return 'TIMESTAMP(6) WITHOUT TIME ZONE';
        }

        return 'DATETIME(6)';
    }

    public function convertToPHPValue($value, AbstractPlatform $platform): mixed
    {
        if ($value === null || $value instanceof \DateTimeInterface) {
            return $value;
        }

        if (str_contains($value, '.')) {
            return \DateTimeImmutable::createFromFormat('Y-m-d H:i:s.u', $value);
        }

        return \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $value);
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform): mixed
    {
        if (null === $value) {
            return null;
        }

        if ($value instanceof \DateTimeInterface) {
            return $value->format('Y-m-d H:i:s.u');
        }

        throw ConversionException::conversionFailedInvalidType(
            $value,
            $this->getName(),
            ['null', 'DateTime']
        );
    }

    public function getName(): string
    {
        return self::TYPENAME;
    }

    public function requiresSQLCommentHint(AbstractPlatform $platform): bool
    {
        return true;
    }
}

Configure doctrine to use it

With this class and the following configuration, you are set:

# config/packages/doctrine.yaml

doctrine:
    dbal:
        types:
            datetime: \Paddox\Dbal\DateTimeWithMicroseconds

To change the name of the type, you need to change the YAML key and the PHP constant.

gist with everything

https://gist.github.com/Schrank/d1f3243e47e52bc20809a3c20cd0441a