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+.
This document describes the core primitives used by the spatial DBAL types: parsing, normalization, and enum-driven patterns.
SpatialDataArray is the base for GeometryArray and GeographyArray. It provides:
POINTZ(...) → POINT Z(...)LINESTRINGM(...) → LINESTRING M(...)POLYGONZM(...) → POLYGON ZM(...)POINT Z (...) → POINT Z(...)SRID=4326;POINT Z (...) → SRID=4326;POINT Z(...)Parsing outputs a list of WktSpatialData value objects that Doctrine DBAL can bind.
📖 See also: PostGIS Spatial Functions and Operators for working with spatial data in queries
Two enums drive normalization so the code and docs remain consistent:
GeometryType – set of supported geometry type names (POINT, LINESTRING, POLYGON, etc.)DimensionalModifier – dimensional markers (Z, M, ZM)Regex patterns for geometry type detection and dimensional modifier handling are built from these enums instead of hardcoded strings.
The WktSpatialData value object provides multiple ways to create spatial data:
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;
// Parse complete WKT/EWKT strings
$point = WktSpatialData::fromWkt('POINT(1 2)');
$pointWithSrid = WktSpatialData::fromWkt('SRID=4326;POINT(-122.4194 37.7749)');
$line = WktSpatialData::fromWkt('LINESTRING(0 0, 1 1, 2 2)');
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\GeometryType;
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\DimensionalModifier;
// Build from individual components
$point = WktSpatialData::fromComponents(
GeometryType::POINT,
'1 2'
);
// With SRID
$pointWithSrid = WktSpatialData::fromComponents(
GeometryType::POINT,
'-122.4194 37.7749',
4326
);
// With dimensional modifier
$line3d = WktSpatialData::fromComponents(
GeometryType::LINESTRING,
'0 0 1, 1 1 2, 2 2 3',
null,
DimensionalModifier::Z
);
// With all parameters
$polygon4d = WktSpatialData::fromComponents(
GeometryType::POLYGON,
'0 0 0 1, 0 1 0 1, 1 1 0 1, 1 0 0 1, 0 0 0 1',
4326,
DimensionalModifier::ZM
);
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;
// Simple 2D point
$point = WktSpatialData::point(1, 2);
// Result: POINT(1 2)
// Point with SRID (common for geographic coordinates)
$location = WktSpatialData::point(-122.4194, 37.7749, 4326);
// Result: SRID=4326;POINT(-122.4194 37.7749)
// 3D point with elevation
$point3d = WktSpatialData::point3d(-122.4194, 37.7749, 100);
// Result: POINT Z(-122.4194 37.7749 100)
// 3D point with SRID
$location3d = WktSpatialData::point3d(-122.4194, 37.7749, 100, 4326);
// Result: SRID=4326;POINT Z(-122.4194 37.7749 100)
The library supports all PostGIS geometry types through the GeometryType enum:
// Point geometry
$point = WktSpatialData::fromWkt('POINT(1 2)');
$point3d = WktSpatialData::fromWkt('POINT Z(1 2 3)');
$pointMeasured = WktSpatialData::fromWkt('POINT M(1 2 4)');
$point4d = WktSpatialData::fromWkt('POINT ZM(1 2 3 4)');
// Line geometry
$line = WktSpatialData::fromWkt('LINESTRING(0 0, 1 1, 2 2)');
$line3d = WktSpatialData::fromWkt('LINESTRING Z(0 0 0, 1 1 1, 2 2 2)');
// Polygon geometry
$polygon = WktSpatialData::fromWkt('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))');
$polygonWithHoles = WktSpatialData::fromWkt('POLYGON((0 0, 0 3, 3 3, 3 0, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))');
// Multi-point
$multiPoint = WktSpatialData::fromWkt('MULTIPOINT((1 2), (3 4), (5 6))');
// Multi-line
$multiLine = WktSpatialData::fromWkt('MULTILINESTRING((0 0, 1 1), (2 2, 3 3))');
// Multi-polygon
$multiPolygon = WktSpatialData::fromWkt('MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)), ((2 2, 2 3, 3 3, 3 2, 2 2)))');
// Geometry collection
$collection = WktSpatialData::fromWkt('GEOMETRYCOLLECTION(POINT(1 2), LINESTRING(0 0, 1 1))');
// Circular string
$circularString = WktSpatialData::fromWkt('CIRCULARSTRING(0 0, 1 1, 2 0)');
// Compound curve
$compoundCurve = WktSpatialData::fromWkt('COMPOUNDCURVE((0 0, 1 1), CIRCULARSTRING(1 1, 2 0, 3 1))');
// Curve polygon
$curvePolygon = WktSpatialData::fromWkt('CURVEPOLYGON(CIRCULARSTRING(0 0, 1 1, 2 0, 0 0))');
// Multi-curve
$multiCurve = WktSpatialData::fromWkt('MULTICURVE((0 0, 1 1), CIRCULARSTRING(1 1, 2 0, 3 1))');
// Multi-surface
$multiSurface = WktSpatialData::fromWkt('MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0, 1 1, 2 0, 0 0)))');
// Triangle
$triangle = WktSpatialData::fromWkt('TRIANGLE((0 0, 1 0, 0.5 1, 0 0))');
// TIN (Triangulated Irregular Network)
$tin = WktSpatialData::fromWkt('TIN(((0 0, 1 0, 0.5 1, 0 0)), ((1 0, 2 0, 1.5 1, 1 0)))');
// Polyhedral surface
$polyhedralSurface = WktSpatialData::fromWkt('POLYHEDRALSURFACE(((0 0, 0 1, 1 1, 1 0, 0 0)), ((0 0, 0 1, 0 0 1, 0 0)))');
SRID=...;...).SRID=4326;POINT(...)).GEOMETRY[] and GEOGRAPHY[] arrays work with DBAL parameter binding.ARRAY[?::geometry, ?::geometry]See GEOMETRY-ARRAYS.md for details, workarounds, and examples.
use Doctrine\DBAL\Types\Type;
Type::addType('geometry', \MartinGeorgiev\Doctrine\DBAL\Types\Geometry::class);
Type::addType('geometry[]', \MartinGeorgiev\Doctrine\DBAL\Types\GeometryArray::class);
Type::addType('geography', \MartinGeorgiev\Doctrine\DBAL\Types\Geography::class);
Type::addType('geography[]', \MartinGeorgiev\Doctrine\DBAL\Types\GeographyArray::class);
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\WktSpatialData;
$qb = $connection->createQueryBuilder();
$qb->insert('places')->values(['location' => ':location']);
$qb->setParameter('location', WktSpatialData::fromWkt('SRID=4326;POINT(-122.4194 37.7749)'), 'geography');
$qb->executeStatement();
$qb = $connection->createQueryBuilder();
$qb->insert('locations')->values(['geometries' => ':geometries']);
$qb->setParameter('geometries', [WktSpatialData::fromWkt('POINT(0 0)')], 'geometry[]');
$qb->executeStatement();
For multi-item arrays, see the raw SQL ARRAY constructor examples in GEOMETRY-ARRAYS.md.
The spatial types provide error handling for invalid spatial data:
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\Exceptions\InvalidWktSpatialDataException;
try {
// Invalid WKT format
$invalid = WktSpatialData::fromWkt('INVALID(1 2)');
} catch (InvalidWktSpatialDataException $e) {
// Throws: "Unsupported geometry type: INVALID"
}
try {
// Empty coordinate section
$empty = WktSpatialData::fromWkt('POINT()');
} catch (InvalidWktSpatialDataException $e) {
// Throws: "Empty coordinate section in WKT"
}
try {
// Invalid SRID format
$invalidSrid = WktSpatialData::fromWkt('SRID=abc;POINT(1 2)');
} catch (InvalidWktSpatialDataException $e) {
// Throws: "Invalid SRID value: abc"
}
try {
// Missing semicolon in EWKT
$missingSemicolon = WktSpatialData::fromWkt('SRID=4326POINT(1 2)');
} catch (InvalidWktSpatialDataException $e) {
// Throws: "Missing semicolon in EWKT format"
}
use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidGeometryForPHPException;
use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidGeographyForPHPException;
// Invalid type passed to geometry column
try {
$qb->setParameter('geom', 'not a geometry', 'geometry');
} catch (InvalidGeometryForPHPException $e) {
// Throws: "Invalid type for geometry column"
}
// Invalid format from database
try {
$geometryType->convertToPHPValue('invalid wkt from db', $platform);
} catch (InvalidGeometryForDatabaseException $e) {
// Throws: "Invalid format for geometry value"
}
// Validate WKT before database operations
function validateSpatialData(string $wkt): bool {
try {
WktSpatialData::fromWkt($wkt);
return true;
} catch (InvalidWktSpatialDataException) {
return false;
}
}
// Check geometry type before processing
$spatialData = WktSpatialData::fromWkt('POINT(1 2)');
if ($spatialData->getGeometryType() === GeometryType::POINT) {
// Process point-specific logic
}
// Validate SRID for geography operations
$geographyData = WktSpatialData::fromWkt('SRID=4326;POINT(-122 37)');
if ($geographyData->getSrid() === 4326) {
// Valid for geography operations
}
How can I help you explore Laravel packages today?