MartinGeorgiev\Doctrine\DBAL\Types\Halfvec (see note)
sparsevec
sparsevec
MartinGeorgiev\Doctrine\DBAL\Types\Sparsevec (see note)
vector
vector
MartinGeorgiev\Doctrine\DBAL\Types\Vector (see note)
(user-defined enum)
(any)
MartinGeorgiev\Doctrine\DBAL\Types\Enum (see Enum types)
(user-defined enum)[]
(any)
MartinGeorgiev\Doctrine\DBAL\Types\EnumArray (see Enum types)
(user-defined composite)
(any)
MartinGeorgiev\Doctrine\DBAL\Types\Composite (see Composite types)
(user-defined composite)[]
(any)
MartinGeorgiev\Doctrine\DBAL\Types\CompositeArray (see Composite types)
PostGIS spatial types
The geometry and geography types and their arrays accept the geometry_type and srid column options, which emit a PostGIS type modifier so the subtype and spatial reference system are enforced by the database:
useDoctrine\ORM\MappingasORM;useMartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;#[ORM\Entity]classPlace{// GEOGRAPHY(POINT,4326) - only WGS 84 points are accepted#[ORM\Column(type: 'geography', options: ['geometry_type' => 'Point', 'srid' => 4326])]privateWktSpatialData$location;// GEOMETRY - unconstrained, accepts any geometry#[ORM\Column(type: 'geometry')]privateWktSpatialData$shape;}
Omitting both options keeps the bare GEOMETRY / GEOGRAPHY declaration. On an array type the modifier goes on the item type: GEOMETRY(POINT,4326)[].
The bit and bit varying types support an optional length parameter via column attribute:
useDoctrine\ORM\MappingasORM;#[ORM\Entity]classPermissions{// BIT(1) - fixed single bit (default when no length specified)#[ORM\Column(type: 'bit')]privatestring$active;// BIT(8) - fixed 8-bit flags#[ORM\Column(type: 'bit', length: 8)]privatestring$flags;// BIT VARYING - unlimited length (default when no length specified)#[ORM\Column(type: 'bit varying')]privatestring$mask;// BIT VARYING(64) - variable length, up to 64 bits#[ORM\Column(type: 'bit varying', length: 64)]privatestring$features;}
BIT without a length defaults to BIT(1) in PostgreSQL, which stores exactly one bit. Use BIT VARYING for variable-length bit strings, or specify an explicit length with BIT(n).
Numeric array type
The numeric[] type maps array items to PHP strings such as '502.00' rather than floats. PostgreSQL’s numeric is an arbitrary-precision type, and converting its values to PHP floats would silently lose precision and trailing zeros - the same reason Doctrine’s own decimal type uses strings.
Array items written to the database must be numeric strings (or null); PHP integers and floats are rejected
decimal[] is a PostgreSQL alias of numeric[] - columns declared as DECIMAL[] are reported by PostgreSQL as numeric[], so this type covers both
numeric also carries the non-finite values 'NaN', 'Infinity' and '-Infinity', which are items like any other here:
Each is accepted in the single spelling PostgreSQL prints. It reads 'nan' and 'inf' too, but emits NaN and Infinity, so allowing the other spellings would break the string round-trip the same way scientific notation would. Infinity needs PostgreSQL 14 or newer; earlier servers store NaN only.
Datetime array types
Items of date[], timestamp[] and timestamptz[] map to \DateTimeImmutable, with one exception: PostgreSQL’s infinity and -infinity sort before and after every other value of their type and have no \DateTimeImmutable counterpart, so they map to the MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\DateTimeInfinity enum instead. Reading a column that may hold them means widening the item check:
useMartinGeorgiev\Doctrine\DBAL\Types\ValueObject\DateTimeInfinity;foreach($entity->getValidOn()as$item){match(true){$item===null=>$this->skip(),// SQL NULL - the value is unknown$item===DateTimeInfinity::POSITIVE=>$this->open(),// later than every other date$item===DateTimeInfinity::NEGATIVE=>$this->open(),// earlier than every other datedefault=>$this->useDate($item),};}
Both are written back by putting the same enum case into the array. They are deliberately kept apart from null, which stays the SQL NULL: an unbounded date is not an unknown one.
Two further ranges PostgreSQL accepts are mapped without a sentinel, because \DateTimeImmutable can hold them:
Years before 1 AD. PostgreSQL numbers them in the BC era and has no year zero, while PHP numbers them astronomically and does. 0001-01-15 BC therefore reads back as PHP year 0000, and 0002-01-15 BC as PHP year -0001. Format such values with X rather than Y to keep the sign visible.
Years past 9999. They round-trip unchanged; Y prints them in full.
See also:Infinity values for why an array item uses this enum while a float uses INF and a range bound uses a flag.
UUID array type
The uuid[] type validates UUID format and returns string[] rather than UUID value objects. This design decision keeps the library lightweight and framework-agnostic:
No additional dependencies - Works without requiring ramsey/uuid or symfony/uid
Consistent with other array types - Follows the same pattern as TextArray and IntegerArray
Framework agnostic - Compatible with any UUID library of your choice
If you need UUID objects, convert the strings:
// With ramsey/uuiduseRamsey\Uuid\Uuid;$uuids=array_map(fn(string$uuid)=>Uuid::fromString($uuid),$entity->getUuidArray());// With symfony/uiduseSymfony\Component\Uid\Uuid;$uuids=array_map(fn(string$uuid)=>Uuid::fromString($uuid),$entity->getUuidArray());
Money type
The money type maps PostgreSQL’s money data type and returns locale-formatted strings such as $1,234.56. PostgreSQL formats money values according to the server’s lc_monetary locale setting, so the exact output format depends on your database configuration.
Important considerations:
PostgreSQL’s money type does not store currency information - the currency symbol is purely a formatting artifact of the server locale
If you need multi-currency support, consider using numeric with application-level currency handling instead
Values written to the database must contain at least one digit; full format validation is deferred to PostgreSQL
If you need rich money objects for arithmetic or multi-currency support, you can convert the string after retrieval:
// With moneyphp/money (requires parsing the locale-formatted string)useMoney\Money;useMoney\Currency;$amount=(int)round((float)preg_replace('/[^0-9.\-]/','',$entity->getPrice())*100);$money=newMoney($amount,newCurrency('USD'));// With brick/moneyuseBrick\Money\Money;$money=Money::of(preg_replace('/[^0-9.\-]/','',$entity->getPrice()),'USD');
Note that both examples above assume USD - you must know the currency independently since PostgreSQL does not store it.
XML type
The xml and xml[] types follow PostgreSQL, which by default (xmloption set to content) accepts more than a single XML document: a fragment such as <a/><b/>, plain text, or an empty string. Any value PostgreSQL returns is read as it is.
Before writing, PHP checks that it can parse the value as a document or as a fragment. A few values PostgreSQL accepts fail that check, such as an XML declaration followed by more than one element, or a namespace prefix that is not declared in the value. If PHP rejects a value, wrap it in one root element and declare the namespace there:
'<?xml version="1.0"?><a/><b/>'// rejected on write'<?xml version="1.0"?><root><a/><b/></root>'// accepted'<p:a/>'// rejected on write'<root xmlns:p="urn:example"><p:a/></root>'// accepted
Hstore type
The hstore type requires the PostgreSQL hstore extension. Enable it with:
CREATEEXTENSIONIFNOTEXISTShstore;
It maps to array<string, string|null> in PHP. Keys and values are always strings; a NULL value in hstore is represented as null in PHP.
Citext type
The citext type requires the PostgreSQL citext extension. Enable it with:
CREATEEXTENSIONIFNOTEXISTScitext;
It is a case-insensitive text type: comparisons are case-insensitive in PostgreSQL while the original casing of values is preserved. It maps to string in PHP and behaves identically to text for storage and retrieval - the difference is purely in how PostgreSQL evaluates equality and ordering.
Use citext when you want case-insensitive lookups, such as usernames and email addresses, without lowercasing values on write.
ULID type
The ulid type requires the third-party pgx_ulid PostgreSQL extension. Enable it with:
CREATEEXTENSIONIFNOTEXISTSulid;
A ULID is a 26-character Crockford base32 identifier (uppercase, first character 0 to 7) stored as a compact 128-bit binary value. It maps to string in PHP. PostgreSQL outputs the canonical uppercase form on retrieval; the DBAL type normalizes values to uppercase on write as well, so round-trips are stable even for lowercase input.
Use ulid when you want sortable, timestamp-prefixed identifiers that are shorter and more index-friendly than UUIDs.
Cube type
The cube type requires the PostgreSQL cube extension. Enable it with:
CREATEEXTENSIONIFNOTEXISTScube;
A cube is a multidimensional value that is either a point - (1, 2, 3) - or a box spanned by two opposite corners - (1, 2, 3),(4, 5, 6). Both corners always carry the same number of dimensions. It maps to the MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\Cube value object in PHP: