martin-georgiev/postgresql-for-doctrine
Adds PostgreSQL-specific power to Doctrine DBAL/ORM: rich native types (jsonb, arrays, ranges, network, geometric, etc.) plus DQL functions/operators for JSON and array querying. Supports PostgreSQL 9.4+ and PHP 8.2+.
| PostgreSQL type in practical use | PostgreSQL internal system catalogue name | Implemented by |
|---|---|---|
| bytea | bytea | MartinGeorgiev\Doctrine\DBAL\Types\Bytea |
| bytea[] | _bytea | MartinGeorgiev\Doctrine\DBAL\Types\ByteaArray |
| --- | --- | --- |
| bit | bit | MartinGeorgiev\Doctrine\DBAL\Types\Bit (see note |
| bit[] | _bit | MartinGeorgiev\Doctrine\DBAL\Types\BitArray |
| bit varying | varbit | MartinGeorgiev\Doctrine\DBAL\Types\BitVarying (see note |
| bit varying[] | _varbit | MartinGeorgiev\Doctrine\DBAL\Types\BitVaryingArray |
| --- | --- | --- |
| bool[] | _bool | MartinGeorgiev\Doctrine\DBAL\Types\BooleanArray |
| smallint[] | _int2 | MartinGeorgiev\Doctrine\DBAL\Types\SmallIntArray |
| integer[] | _int4 | MartinGeorgiev\Doctrine\DBAL\Types\IntegerArray |
| bigint[] | _int8 | MartinGeorgiev\Doctrine\DBAL\Types\BigIntArray |
| real[] | _float4 | MartinGeorgiev\Doctrine\DBAL\Types\RealArray |
| double precision[] | _float8 | MartinGeorgiev\Doctrine\DBAL\Types\DoublePrecisionArray |
| --- | --- | --- |
| date[] | _date | MartinGeorgiev\Doctrine\DBAL\Types\DateArray |
| interval | interval | MartinGeorgiev\Doctrine\DBAL\Types\Interval |
| interval[] | _interval | MartinGeorgiev\Doctrine\DBAL\Types\IntervalArray |
| timestamp[] | _timestamp | MartinGeorgiev\Doctrine\DBAL\Types\TimestampArray |
| timestamptz[] | _timestamptz | MartinGeorgiev\Doctrine\DBAL\Types\TimestampTzArray |
| timetz | timetz | MartinGeorgiev\Doctrine\DBAL\Types\Timetz |
| timetz[] | _timetz | MartinGeorgiev\Doctrine\DBAL\Types\TimetzArray |
| --- | --- | --- |
| jsonb | jsonb | MartinGeorgiev\Doctrine\DBAL\Types\Jsonb |
| jsonb[] | _jsonb | MartinGeorgiev\Doctrine\DBAL\Types\JsonbArray |
| text[] | _text | MartinGeorgiev\Doctrine\DBAL\Types\TextArray |
| uuid[] | _uuid | MartinGeorgiev\Doctrine\DBAL\Types\UuidArray (see note) |
| citext | citext | MartinGeorgiev\Doctrine\DBAL\Types\Citext (see note) |
| citext[] | _citext | MartinGeorgiev\Doctrine\DBAL\Types\CitextArray |
| --- | --- | --- |
| cidr | cidr | MartinGeorgiev\Doctrine\DBAL\Types\Cidr |
| cidr[] | _cidr | MartinGeorgiev\Doctrine\DBAL\Types\CidrArray |
| inet | inet | MartinGeorgiev\Doctrine\DBAL\Types\Inet |
| inet[] | _inet | MartinGeorgiev\Doctrine\DBAL\Types\InetArray |
| macaddr | macaddr | MartinGeorgiev\Doctrine\DBAL\Types\Macaddr |
| macaddr[] | _macaddr | MartinGeorgiev\Doctrine\DBAL\Types\MacaddrArray |
| macaddr8 | macaddr8 | MartinGeorgiev\Doctrine\DBAL\Types\Macaddr8 |
| macaddr8[] | _macaddr8 | MartinGeorgiev\Doctrine\DBAL\Types\Macaddr8Array |
| --- | --- | --- |
| tsquery | tsquery | MartinGeorgiev\Doctrine\DBAL\Types\Tsquery |
| tsquery[] | _tsquery | MartinGeorgiev\Doctrine\DBAL\Types\TsqueryArray |
| tsvector | tsvector | MartinGeorgiev\Doctrine\DBAL\Types\Tsvector |
| tsvector[] | _tsvector | MartinGeorgiev\Doctrine\DBAL\Types\TsvectorArray |
| --- | --- | --- |
| daterange | daterange | MartinGeorgiev\Doctrine\DBAL\Types\DateRange |
| daterange[] | _daterange | MartinGeorgiev\Doctrine\DBAL\Types\DateRangeArray |
| int4range | int4range | MartinGeorgiev\Doctrine\DBAL\Types\Int4Range |
| int4range[] | _int4range | MartinGeorgiev\Doctrine\DBAL\Types\Int4RangeArray |
| int8range | int8range | MartinGeorgiev\Doctrine\DBAL\Types\Int8Range |
| int8range[] | _int8range | MartinGeorgiev\Doctrine\DBAL\Types\Int8RangeArray |
| numrange | numrange | MartinGeorgiev\Doctrine\DBAL\Types\NumRange |
| numrange[] | _numrange | MartinGeorgiev\Doctrine\DBAL\Types\NumRangeArray |
| tsrange | tsrange | MartinGeorgiev\Doctrine\DBAL\Types\TsRange |
| tsrange[] | _tsrange | MartinGeorgiev\Doctrine\DBAL\Types\TsRangeArray |
| tstzrange | tstzrange | MartinGeorgiev\Doctrine\DBAL\Types\TstzRange |
| tstzrange[] | _tstzrange | MartinGeorgiev\Doctrine\DBAL\Types\TstzRangeArray |
| --- | --- | --- |
| datemultirange | datemultirange | MartinGeorgiev\Doctrine\DBAL\Types\DateMultirange |
| datemultirange[] | _datemultirange | MartinGeorgiev\Doctrine\DBAL\Types\DateMultirangeArray |
| int4multirange | int4multirange | MartinGeorgiev\Doctrine\DBAL\Types\Int4Multirange |
| int4multirange[] | _int4multirange | MartinGeorgiev\Doctrine\DBAL\Types\Int4MultirangeArray |
| int8multirange | int8multirange | MartinGeorgiev\Doctrine\DBAL\Types\Int8Multirange |
| int8multirange[] | _int8multirange | MartinGeorgiev\Doctrine\DBAL\Types\Int8MultirangeArray |
| nummultirange | nummultirange | MartinGeorgiev\Doctrine\DBAL\Types\NumMultirange |
| nummultirange[] | _nummultirange | MartinGeorgiev\Doctrine\DBAL\Types\NumMultirangeArray |
| tsmultirange | tsmultirange | MartinGeorgiev\Doctrine\DBAL\Types\TsMultirange |
| tsmultirange[] | _tsmultirange | MartinGeorgiev\Doctrine\DBAL\Types\TsMultirangeArray |
| tstzmultirange | tstzmultirange | MartinGeorgiev\Doctrine\DBAL\Types\TstzMultirange |
| tstzmultirange[] | _tstzmultirange | MartinGeorgiev\Doctrine\DBAL\Types\TstzMultirangeArray |
| --- | --- | --- |
| box | box | MartinGeorgiev\Doctrine\DBAL\Types\Box |
| box[] | _box | MartinGeorgiev\Doctrine\DBAL\Types\BoxArray |
| circle | circle | MartinGeorgiev\Doctrine\DBAL\Types\Circle |
| circle[] | _circle | MartinGeorgiev\Doctrine\DBAL\Types\CircleArray |
| line | line | MartinGeorgiev\Doctrine\DBAL\Types\Line |
| line[] | _line | MartinGeorgiev\Doctrine\DBAL\Types\LineArray |
| lseg | lseg | MartinGeorgiev\Doctrine\DBAL\Types\Lseg |
| lseg[] | _lseg | MartinGeorgiev\Doctrine\DBAL\Types\LsegArray |
| path | path | MartinGeorgiev\Doctrine\DBAL\Types\Path |
| path[] | _path | MartinGeorgiev\Doctrine\DBAL\Types\PathArray |
| point | point | MartinGeorgiev\Doctrine\DBAL\Types\Point |
| point[] | _point | MartinGeorgiev\Doctrine\DBAL\Types\PointArray |
| polygon | polygon | MartinGeorgiev\Doctrine\DBAL\Types\Polygon |
| polygon[] | _polygon | MartinGeorgiev\Doctrine\DBAL\Types\PolygonArray |
| --- | --- | --- |
| geography | geography | MartinGeorgiev\Doctrine\DBAL\Types\Geography |
| geography[] | _geography | MartinGeorgiev\Doctrine\DBAL\Types\GeographyArray |
| geometry | geometry | MartinGeorgiev\Doctrine\DBAL\Types\Geometry |
| geometry[] | _geometry | MartinGeorgiev\Doctrine\DBAL\Types\GeometryArray |
| --- | --- | --- |
| hstore | hstore | MartinGeorgiev\Doctrine\DBAL\Types\Hstore (see note) |
| hstore[] | _hstore | MartinGeorgiev\Doctrine\DBAL\Types\HstoreArray |
| --- | --- | --- |
| ltree | ltree | MartinGeorgiev\Doctrine\DBAL\Types\Ltree |
| ltree[] | _ltree | MartinGeorgiev\Doctrine\DBAL\Types\LtreeArray |
| --- | --- | --- |
| money | money | MartinGeorgiev\Doctrine\DBAL\Types\Money (see note) |
| money[] | _money | MartinGeorgiev\Doctrine\DBAL\Types\MoneyArray |
| --- | --- | --- |
| xml | xml | MartinGeorgiev\Doctrine\DBAL\Types\Xml |
| xml[] | _xml | MartinGeorgiev\Doctrine\DBAL\Types\XmlArray |
| --- | --- | --- |
| halfvec | halfvec | 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) |
The vector, halfvec, and sparsevec types use the length column option to specify the number of dimensions:
use Doctrine\ORM\Mapping as ORM;
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\Sparsevec;
#[ORM\Entity]
class Embedding
{
// VECTOR(1536) — fixed 1536-dimensional float vector
#[ORM\Column(type: 'vector', length: 1536)]
private array $embedding;
// HALFVEC(1024) — half-precision float vector
#[ORM\Column(type: 'halfvec', length: 1024)]
private array $smallEmbedding;
// SPARSEVEC(4096) — sparse vector with up to 4096 dimensions
#[ORM\Column(type: 'sparsevec', length: 4096)]
private Sparsevec $sparseEmbedding;
}
Important: Omitting length produces a dimensionless column (VECTOR with no size), which is valid DDL but cannot be indexed with HNSW or IVFFlat indexes. Always specify length for production use.
The bit and bit varying types support an optional length parameter via column attribute:
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Permissions
{
// BIT(1) — fixed single bit (default when no length specified)
#[ORM\Column(type: 'bit')]
private string $active;
// BIT(8) — fixed 8-bit flags
#[ORM\Column(type: 'bit', length: 8)]
private string $flags;
// BIT VARYING — unlimited length (default when no length specified)
#[ORM\Column(type: 'bit varying')]
private string $mask;
// BIT VARYING(64) — variable length, up to 64 bits
#[ORM\Column(type: 'bit varying', length: 64)]
private string $features;
}
Important: 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).
The uuid[] type validates UUID format and returns string[] rather than UUID value objects. This design decision keeps the library lightweight and framework-agnostic:
ramsey/uuid or symfony/uidTextArray, IntegerArray, etc.If you need UUID objects, you can easily convert the strings:
// With ramsey/uuid
use Ramsey\Uuid\Uuid;
$uuids = array_map(fn(string $uuid) => Uuid::fromString($uuid), $entity->getUuidArray());
// With symfony/uid
use Symfony\Component\Uid\Uuid;
$uuids = array_map(fn(string $uuid) => Uuid::fromString($uuid), $entity->getUuidArray());
The money type maps PostgreSQL's money data type and returns locale-formatted strings (e.g. $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:
money type does not store currency information — the currency symbol is purely a formatting artifact of the server localenumeric with application-level currency handling insteadIf 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)
use Money\Money;
use Money\Currency;
$amount = (int) round((float) preg_replace('/[^0-9.\-]/', '', $entity->getPrice()) * 100);
$money = new Money($amount, new Currency('USD'));
// With brick/money
use Brick\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.
The hstore type requires the PostgreSQL hstore extension. Enable it with:
CREATE EXTENSION IF NOT EXISTS hstore;
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.
The citext type requires the PostgreSQL citext extension. Enable it with:
CREATE EXTENSION IF NOT EXISTS citext;
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 (e.g. usernames, email addresses) without lowercasing values on write.
How can I help you explore Laravel packages today?