garak/doctrine-postgres-types-bundle
Symfony bundle that registers PostgreSQL-specific Doctrine DBAL types, so you can map Postgres arrays, JSONB, and similar column types directly to PHP values in your entities without writing custom type classes.
composer require garak/doctrine-postgres-types-bundle
Enable it (Flex usually does this for you):
// config/bundles.php
return [
// ...
Garak\DoctrinePostgresTypesBundle\GarakDoctrinePostgresTypesBundle::class => ['all' => true],
];
Where to look first:
DependencyInjection/*Extension.php) and any CompilerPass — this is where the bundle typically calls Type::addType() / registerDoctrineTypeMapping(). That tells you which type names exist and whether registration is automatic.DBAL/Types (or Doctrine/DBAL/Types) directory — one class per Postgres type. Read convertToDatabaseValue, convertToPHPValue, and getSQLDeclaration to learn the PHP <-> Postgres contract precisely.inet, cidr, macaddr, hstore, jsonb, uuid, range types, tsvector, array types, etc.).Sanity-check boot and registration:
php bin/console doctrine:query:sql "SELECT 1::inet"
php -r 'require "vendor/autoload.php"; Doctrine\DBAL\Types\Type::hasType("inet") ?: exit(1);'
First use case — a column Doctrine doesn't ship a type for, say INET:
#[ORM\Entity]
class Login
{
#[ORM\Column(type: 'inet', nullable: true)]
private ?string $remoteIp = null;
}
Then run doctrine:schema:validate / inspect the generated SQL in a migration. If INET shows up, the bundle is wired correctly and you can start mapping the rest of your Postgres-native columns.
Rely on bundle registration, override only when needed. The bundle is meant to make the type name (inet, hstore, …) usable directly in #[ORM\Column(type: ...)]. Only fall back to an explicit doctrine.yaml entry if the type isn't registered:
# config/packages/doctrine.yaml
doctrine:
dbal:
types:
inet: Garak\DoctrinePostgresTypesBundle\DBAL\Types\InetType
(Confirm the class path from the bundle source before copying — aliasing here shadows the bundle and is easy to get wrong.)
Pick the right mapping for the shape you need. A common decision is JSONB vs Postgres array:
jsonb — heterogeneous/nested data, want @> containment queries and GIN indexes.text[], int[]) — homogeneous lists you'll query with = ANY(...), @>, &&, and index with GIN.hstore — legacy key/value; prefer jsonb for new work unless you need hstore operators.Migrations. Doctrine's schema tool understands a custom type only if getSQLDeclaration() is correct. Inside a migration, pass the type name and expect the DBAL platform to render it:
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE login ADD remote_ip INET DEFAULT NULL');
}
For diff-heavy flows, prefer writing the raw ALTER for these columns rather than relying on auto-diff (see gotchas).
Query with native SQL for type-specific operators. Custom DBAL types do not add DQL functions, so use NativeQuery + ResultSetMapping when you need <<, @>, ?, ~, or range operators:
$rsm = new ResultSetMappingBuilder($em);
$rsm->addRootEntityFromClassMetadata(Login::class, 'l');
$query = $em->createNativeQuery(
'SELECT l.* FROM login l WHERE l.remote_ip << :net',
$rsm
);
$query->setParameter('net', '10.0.0.0/8');
Value-object wrapping. Many of these types round-trip as strings/arrays. If you want a VO (e.g. IpAddress), write a small Type that delegates to the bundle's class and casts in convertToPHPValue. This keeps your domain clean while reusing the SQL declaration and database-value conversion.
Test against a real Postgres. SQLite/MySQL will not exercise these types; INET, HSTORE, ranges, etc. don't exist there. Wire a Dockerized Postgres in CI and run functional tests that round-trip each mapped type.
ALTER diffs for custom types unless the type implements requiresSQLCommentHint() (DBAL 3) and your getSQLDeclaration() matches what Postgres reports back. If you see "column already exists" style churn, that's why — a common fix is a comment hint or dropping the column from diff scope in migrations.Type::getName() was dropped in DBAL 3 in favour of name constants, and getSQLDeclaration()'s signature changed (array &$column). Check the doctrine/dbal version constraint in the bundle's composer.json before upgrading your own DBAL — this package is small and may lag.platform instanceof PostgreSQLPlatform checks or a dedicated test env pinned to Postgres.jsonb, uuid, and array are registered by other bundles and by application code. Whichever Type::addType() runs last wins. If you have a custom uuid type, verify it isn't being shadowed — inspect Type::getTypes() in a console command.hstore/arrays/JSON, convertToPHPValue(null) must be handled separately from '[]' / '{}'. Test both; a lot of subtle bugs live here.WHERE x = :y works, but type-specific operators and functions require NativeQuery or a custom DQL function. Don't expect ANY() or @> to be available out of the box.addColumn. Do not let the schema diff author these columns. Write the DDL yourself (INET, HSTORE, INT4RANGE, …) so you control defaults, indexes (GIN/USING gist), and operator classes.Type. Subclass the bundle's class, override convertToPHPValue/convertToDatabaseValue for a VO, and register it under a new name — that's the cleanest way to customize without forking.src/Doctrine/DBAL/Types/, and drop the dependency if you want to decouple from the bundle's release cadence and DBAL version pins. LGPL-3.0 permits this within a larger application, but check your legal guidance for redistribution scenarios.How can I help you explore Laravel packages today?