DBAL Types
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