jsor/doctrine-postgis
Adds PostGIS spatial support to Doctrine DBAL/ORM: geometry and geography column types, schema helpers, SQL and DQL functions, and spatial query integration for PostgreSQL/PostGIS.
jsor/doctrine-postgis is a Doctrine DBAL/ORM extension — it does not integrate with Eloquent. In a Laravel app you almost always wire it in through laravel-doctrine/orm (or use the DBAL only, via DB::connection()->getDoctrineConnection()).
Minimal steps:
Enable PostGIS on the database: CREATE EXTENSION IF NOT EXISTS postgis;
composer require jsor/doctrine-postgis
Register the DBAL types in config/doctrine.php under dbal.types:
'dbal' => [
'types' => [
'geometry' => Jsor\Doctrine\PostGIS\Types\GeometryType::class,
'geography' => Jsor\Doctrine\PostGIS\Types\GeographyType::class,
'geometry_collection' => Jsor\Doctrine\PostGIS\Types\GeometryCollectionType::class,
'point' => Jsor\Doctrine\PostGIS\Types\PointType::class,
'linestring' => Jsor\Doctrine\PostGIS\Types\LineStringType::class,
'polygon' => Jsor\Doctrine\PostGIS\Types\PolygonType::class,
// ...multipoint, multilinestring, multipolygon
],
],
Register the PostGIS DQL functions and platform (see below) on the connection config, or you'll get "unknown function ST_*" errors.
First use case: a places table with a location GEOMETRY(Point, 4326) column, queried with ST_Distance / ordered by proximity.
If you only need a couple of columns, you can also skip the full ORM and define a Doctrine type directly via Doctrine\DBAL\Types\Type::addType(...) inside a service provider — useful when you're migrating an Eloquent app one table at a time.
Entity mapping. Use options to declare the PostGIS type and SRID, because the generic geometry type needs a concrete geometry subtype in the column definition:
#[ORM\Column(type: 'geometry', options: ['geometry_type' => 'Point', 'srid' => 4326])]
private $location;
#[ORM\Column(type: 'geography', options: ['geometry_type' => 'Point', 'srid' => 4326])]
private $area;
geography gives you meters/degrees-aware distance out of the box; geometry gives you a flat Cartesian plane. Pick per column, not per app.
Queries with DQL functions. Register the function set on the connection (either a subset or the whole ST_* group) and use them in createQueryBuilder():
$qb->select('p')
->where('ST_DWithin(p.location, ST_GeomFromText(:wkt, 4326), :meters) = true')
->setParameter('wkt', 'POINT(13.4 52.5)')
->setParameter('meters', 500);
Prefer ST_DWithin(...) over ST_Distance(...) < x — the former uses the GiST index; the latter does not.
Write path. Doctrine will serialize the value you assign through PointType/GeometryType; typically you build WKT strings (ST_GeomFromText) in DQL, or use ST_SetSRID in raw executeStatement() calls for bulk imports.
Platform extension. To make the schema tool emit GEOMETRY(Point, 4326) columns and USING GIST indexes instead of generic varchar/text:
'platform' => Jsor\Doctrine\PostGIS\PostGISPlatform::class,
// or wrap it: new PostGISPlatform(new PostgreSqlPlatform()) in older versions
This is what lets doctrine:migrations:diff produce a correct migration instead of VARCHAR(255) you then have to hand-edit.
Migrations. Register Jsor\Doctrine\PostGIS\Schema\PostGISSchemaManager so SchemaTool knows about spatial indexes and geometry subtypes. Common pattern: generate the migration, then hand-edit the spatial index to CREATE INDEX ... ON places USING GIST (location).
Bulk / analytical queries. For map endpoints, select ST_AsGeoJSON(location) directly and hydrate into arrays — round-tripping geometries through entities is lossy and slower than the DB-side serializer.
mixed SRID errors. Set SRID explicitly everywhere (ST_GeomFromText(:wkt, 4326), column options['srid']) and never rely on the database default.GeoJSON helpers from this package with Model::where(). Either use Laravel Doctrine (laravel-doctrine/orm), the raw DBAL connection, or keep PostGIS tables in a Doctrine-managed schema and join through a read-only view.ST_AsText. Registering the entire ST_* set is fine; just pin the package version so function signatures don't drift.GeometryType returns WKB by default (a string). If you expected a WKT string or an object, use ST_AsText(location) (or ST_AsEWKT) in your select, or ST_AsGeoJSON. There is no automatic __toString.ST_DWithin queries fall back to sequential scans silently and get slow at a million rows.precision/scale options on geometry columns are ignored; use the geometry_type and srid options instead.doctrine:migrations:diff will try to convert your geometry columns to varchar. Register both or neither.DB::listen / Doctrine's SQLLogger, then run the query in psql with EXPLAIN ANALYZE to confirm the GiST index is being used. SELECT postgis_full_version() and SELECT ST_IsValid(...) are your first stops for bad geometries; a 4326 polygon with self-intersections breaks ST_Contains without an obvious error.Jsor\Doctrine\PostGIS\Functions\FunctionNode — useful when you need a PostGIS 3.x function the package hasn't shipped yet.How can I help you explore Laravel packages today?