Skip to content

DBAL Types

Edit this page

Custom DBAL types let you map a database column to a PHP value of your choice. The recommended way to register one is the #[AsDbalType] attribute, which declares the type directly on its class.

Registering a Type with the Attribute

Create a class that extends Doctrine\DBAL\Types\Type and add the #[AsDbalType] attribute to it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/Doctrine/Type/MoneyType.php
namespace App\Doctrine\Type;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDbalType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;

#[AsDbalType]
class MoneyType extends Type
{
    public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
    {
        return $platform->getDecimalTypeDeclarationSQL($column);
    }

    public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
    {
        return $value;
    }

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

The type is then available in your mappings. When no name is given, it defaults to the fully-qualified class name, so you can reference the type by its class:

1
2
3
4
5
6
7
8
9
10
11
12
// src/Entity/Product.php
namespace App\Entity;

use App\Doctrine\Type\MoneyType;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Product
{
    #[ORM\Column(type: MoneyType::class)]
    private Money $price;
}

To use a shorter name instead, pass it to the attribute and reference that name in the mapping:

1
2
3
4
5
#[AsDbalType(name: 'money')]
class MoneyType extends Type
{
    // ...
}
1
2
#[ORM\Column(type: 'money')]
private Money $price;

Note

The #[AsDbalType] attribute and the doctrine.dbal.type tag require DoctrineBundle >= 3.3. Dependency injection, the connection argument and repeatable attributes require DoctrineBundle >= 3.4.

Note

With DBAL >= 4.6 the type is registered on the connections instead of the global type registry. Doctrine ORM >= 3.8 resolves it when it reads the type provider of the connection. Types that other code registers globally with Type::addType() stay available, including the built-in types overridden with Type::overrideType(), because the connection provider falls back to the global registry. A type declared for the connection takes precedence over the global registry.

With an older ORM, which reads the types from the global registry, the bundle registers the tagged types there instead, when the first connection is created, and the connections receive no type provider. It registers the instances built by the container, so dependency injection keeps working. The global registry is shared by every connection, so the connection argument does not apply there, and two services cannot declare the same type name.

Dependency Injection

The type is registered as a service, so its constructor can declare dependencies that are resolved by the container:

1
2
3
4
5
6
7
8
9
#[AsDbalType]
class MoneyType extends Type
{
    public function __construct(private readonly ExchangeRateProvider $rates)
    {
    }

    // ...
}

Note

Dependency injection requires DBAL >= 4.6, where the type is resolved lazily from a per-connection registry. On older DBAL versions the type is registered in the global registry and created by DBAL, so a constructor with required parameters fails when a connection is created.

With Doctrine ORM before 3.8, the instance built by the container is registered in the global registry, so the dependencies are injected there too.

Restricting a Type to a Connection

By default the type is registered on every connection. Use the connection parameter to restrict it to a single connection. The attribute is repeatable, so a type can be registered on several connections with different names:

1
2
3
4
5
6
#[AsDbalType(name: 'money', connection: 'default')]
#[AsDbalType(name: 'money', connection: 'reporting')]
class MoneyType extends Type
{
    // ...
}

Note

Restricting a type to a connection requires DBAL >= 4.6. On older versions the connection is ignored and the type is registered globally.

With Doctrine ORM before 3.8, the type is registered in the global registry instead of the connections, so the connection argument does not keep it away from the other connections.

Registering a Type as a Service

The attribute is autoconfigured to the doctrine.dbal.type tag, so tagging a service is equivalent to using the attribute. This is useful when autoconfiguration is disabled, or to register the same class several times with different dependencies. The type_name attribute sets the type name; when omitted, the service id is used:

1
2
3
4
5
# config/services.yaml
services:
    App\Doctrine\Type\MoneyType:
        tags:
            - { name: doctrine.dbal.type, type_name: money, connection: reporting }

Registering a Type with the Configuration

When you do not need dependency injection, a type can be registered by mapping a name to its class name under doctrine.dbal.types. This is supported on all DBAL versions and does not turn the type into a service. With DBAL >= 4.6 the type is registered both in the global registry and in the type provider of each connection, so the two registrations hold separate instances of the type. With Doctrine ORM before 3.8 the global registry is the only registration:

1
2
3
4
5
# config/packages/doctrine.yaml
doctrine:
    dbal:
        types:
            money: App\Doctrine\Type\MoneyType
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version